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.
@@ -77,6 +77,7 @@ export class JirenClient<
77
77
  private urlMap: Map<string, string> = new Map();
78
78
  private cacheConfig: Map<string, { enabled: boolean; ttl: number }> =
79
79
  new Map();
80
+ private antibotConfig: Map<string, boolean> = new Map();
80
81
  private cache: ResponseCache;
81
82
 
82
83
  /** Type-safe URL accessor for warmed-up URLs */
@@ -117,6 +118,11 @@ export class JirenClient<
117
118
  : { enabled: true, ttl: config.cache.ttl || 60000 };
118
119
  this.cacheConfig.set(config.key, cacheConfig);
119
120
  }
121
+
122
+ // Store antibot config
123
+ if (config.antibot) {
124
+ this.antibotConfig.set(config.key, true);
125
+ }
120
126
  }
121
127
  }
122
128
  } else {
@@ -139,6 +145,11 @@ export class JirenClient<
139
145
  : { enabled: true, ttl: urlConfig.cache.ttl || 60000 };
140
146
  this.cacheConfig.set(key, cacheConfig);
141
147
  }
148
+
149
+ // Store antibot config
150
+ if ((urlConfig as { antibot?: boolean }).antibot) {
151
+ this.antibotConfig.set(key, true);
152
+ }
142
153
  }
143
154
  }
144
155
  }
@@ -146,12 +157,30 @@ export class JirenClient<
146
157
  if (urls.length > 0) {
147
158
  this.warmup(urls);
148
159
  }
160
+
161
+ // Preload L2 disk cache entries into L1 memory for cached endpoints
162
+ // This ensures user's first request hits L1 (~0.001ms) instead of L2 (~5ms)
163
+ for (const [key, config] of this.cacheConfig.entries()) {
164
+ if (config.enabled) {
165
+ const url = this.urlMap.get(key);
166
+ if (url) {
167
+ this.cache.preloadL1(url);
168
+ }
169
+ }
170
+ }
149
171
  }
150
172
 
151
173
  // Create proxy for type-safe URL access
152
174
  this.url = this.createUrlAccessor();
153
175
  }
154
176
 
177
+ /**
178
+ * Wait for warmup to complete
179
+ */
180
+ public async waitForWarmup(): Promise<void> {
181
+ // Native warmup is synchronous, so this is effectively a no-op
182
+ }
183
+
155
184
  /**
156
185
  * Creates a proxy-based URL accessor for type-safe access.
157
186
  */
@@ -180,10 +209,14 @@ export class JirenClient<
180
209
  ): Promise<JirenResponse<R> | R | string | ArrayBuffer | Blob> => {
181
210
  const cacheConfig = self.cacheConfig.get(prop);
182
211
 
212
+ // Check if antibot is enabled for this URL (from warmup config or per-request)
213
+ const useAntibot =
214
+ options?.antibot ?? self.antibotConfig.get(prop) ?? false;
215
+
183
216
  if (cacheConfig?.enabled) {
184
217
  const cached = self.cache.get(baseUrl, options?.path, options);
185
218
  if (cached) {
186
- return self.rehydrateResponse(cached);
219
+ return cached as any;
187
220
  }
188
221
  }
189
222
 
@@ -195,7 +228,7 @@ export class JirenClient<
195
228
  headers: options?.headers,
196
229
  maxRedirects: options?.maxRedirects,
197
230
  responseType: options?.responseType,
198
- antibot: options?.antibot,
231
+ antibot: useAntibot,
199
232
  }
200
233
  );
201
234
 
@@ -535,39 +568,42 @@ export class JirenClient<
535
568
  // Copy body content
536
569
  buffer = Buffer.from(koffi.decode(bodyPtr, "uint8_t", len));
537
570
 
538
- // Handle GZIP compression
571
+ // Handle GZIP compression - search for magic bytes in first 16 bytes
572
+ // (handles chunked encoding or other framing that may add prefix bytes)
539
573
  const contentEncoding = headersProxy["content-encoding"]?.toLowerCase();
574
+ let gzipOffset = -1;
540
575
 
541
- if (
542
- contentEncoding === "gzip" ||
543
- (buffer.length > 2 && buffer[0] === 0x1f && buffer[1] === 0x8b)
544
- ) {
576
+ // Search for gzip magic bytes (0x1f 0x8b) in first 16 bytes
577
+ for (let i = 0; i < Math.min(16, buffer.length - 1); i++) {
578
+ if (buffer[i] === 0x1f && buffer[i + 1] === 0x8b) {
579
+ gzipOffset = i;
580
+ break;
581
+ }
582
+ }
583
+
584
+ if (contentEncoding === "gzip" || gzipOffset >= 0) {
545
585
  try {
546
- console.log("[Jiren] Attempting gunzip...");
547
- buffer = zlib.gunzipSync(buffer);
548
- console.log("[Jiren] Gunzip success!");
586
+ // If we found gzip at an offset, slice from there
587
+ const gzipData = gzipOffset > 0 ? buffer.slice(gzipOffset) : buffer;
588
+ console.log(
589
+ `[Jiren] Decompressing gzip (offset: ${
590
+ gzipOffset >= 0 ? gzipOffset : 0
591
+ }, size: ${gzipData.length})`
592
+ );
593
+ buffer = zlib.gunzipSync(gzipData);
549
594
  } catch (e) {
550
595
  console.warn("Failed to gunzip response body:", e);
551
596
  }
552
597
  }
553
598
  }
554
599
 
555
- // Convert to base64 for caching persistence
556
- let base64Data = "";
557
- try {
558
- base64Data = buffer.toString("base64");
559
- } catch (e) {
560
- // Should not happen
561
- }
562
-
563
600
  let bodyUsed = false;
564
601
  const consumeBody = () => {
565
602
  bodyUsed = true;
566
603
  };
567
604
 
568
- const bodyObj: JirenResponseBody<T> & { _raw?: string } = {
605
+ const bodyObj: JirenResponseBody<T> = {
569
606
  bodyUsed: false,
570
- _raw: base64Data,
571
607
  arrayBuffer: async () => {
572
608
  consumeBody();
573
609
  return buffer.buffer.slice(
@@ -607,63 +643,6 @@ export class JirenClient<
607
643
  lib.symbols.zclient_response_free(respPtr);
608
644
  }
609
645
  }
610
-
611
- /**
612
- * Rehydrates a cached response object by restoring the body interface methods.
613
- * This is necessary because JSON serialization strips functions.
614
- */
615
- private rehydrateResponse(cached: any): JirenResponse {
616
- // If it already has methods, return as is
617
- if (typeof cached.body.text === "function") return cached;
618
-
619
- // Retrieve raw data
620
- const rawData = cached.body._raw;
621
- let buffer: Buffer;
622
-
623
- if (rawData) {
624
- buffer = Buffer.from(rawData, "base64");
625
- } else {
626
- buffer = Buffer.from("");
627
- }
628
-
629
- let bodyUsed = cached.body.bodyUsed || false;
630
- const consumeBody = () => {
631
- bodyUsed = true;
632
- };
633
-
634
- const bodyObj: JirenResponseBody = {
635
- _raw: rawData, // Keep it for re-caching if needed
636
- bodyUsed,
637
- arrayBuffer: async () => {
638
- consumeBody();
639
- return buffer.buffer.slice(
640
- buffer.byteOffset,
641
- buffer.byteOffset + buffer.byteLength
642
- ) as ArrayBuffer;
643
- },
644
- blob: async () => {
645
- consumeBody();
646
- return new Blob([buffer as any]);
647
- },
648
- text: async () => {
649
- consumeBody();
650
- return buffer.toString("utf-8");
651
- },
652
- json: async <R = any>(): Promise<R> => {
653
- consumeBody();
654
- return JSON.parse(buffer.toString("utf-8"));
655
- },
656
- } as any;
657
-
658
- Object.defineProperty(bodyObj, "bodyUsed", {
659
- get: () => bodyUsed,
660
- });
661
-
662
- return {
663
- ...cached,
664
- body: bodyObj,
665
- };
666
- }
667
646
  }
668
647
 
669
648
  class NativeHeaders {
@@ -58,6 +58,7 @@ export class JirenClient<
58
58
  private urlMap: Map<string, string> = new Map();
59
59
  private cacheConfig: Map<string, { enabled: boolean; ttl: number }> =
60
60
  new Map();
61
+ private antibotConfig: Map<string, boolean> = new Map();
61
62
  private cache: ResponseCache;
62
63
 
63
64
  /** Type-safe URL accessor for warmed-up URLs */
@@ -90,6 +91,11 @@ export class JirenClient<
90
91
  : { enabled: true, ttl: config.cache.ttl || 60000 };
91
92
  this.cacheConfig.set(config.key, cacheConfig);
92
93
  }
94
+
95
+ // Store antibot config
96
+ if (config.antibot) {
97
+ this.antibotConfig.set(config.key, true);
98
+ }
93
99
  }
94
100
  }
95
101
  } else {
@@ -112,6 +118,11 @@ export class JirenClient<
112
118
  : { enabled: true, ttl: urlConfig.cache.ttl || 60000 };
113
119
  this.cacheConfig.set(key, cacheConfig);
114
120
  }
121
+
122
+ // Store antibot config
123
+ if ((urlConfig as { antibot?: boolean }).antibot) {
124
+ this.antibotConfig.set(key, true);
125
+ }
115
126
  }
116
127
  }
117
128
  }
@@ -119,12 +130,30 @@ export class JirenClient<
119
130
  if (urls.length > 0) {
120
131
  this.warmup(urls);
121
132
  }
133
+
134
+ // Preload L2 disk cache entries into L1 memory for cached endpoints
135
+ // This ensures user's first request hits L1 (~0.001ms) instead of L2 (~5ms)
136
+ for (const [key, config] of this.cacheConfig.entries()) {
137
+ if (config.enabled) {
138
+ const url = this.urlMap.get(key);
139
+ if (url) {
140
+ this.cache.preloadL1(url);
141
+ }
142
+ }
143
+ }
122
144
  }
123
145
 
124
146
  // Create proxy for type-safe URL access
125
147
  this.url = this.createUrlAccessor();
126
148
  }
127
149
 
150
+ /**
151
+ * Wait for warmup to complete (no-op for fetch client)
152
+ */
153
+ public async waitForWarmup(): Promise<void> {
154
+ // No-op - fetch client warmup is fire-and-forget
155
+ }
156
+
128
157
  /**
129
158
  * Creates a proxy-based URL accessor for type-safe access.
130
159
  */
@@ -153,6 +182,10 @@ export class JirenClient<
153
182
  ): Promise<JirenResponse<R> | R | string | ArrayBuffer | Blob> => {
154
183
  const cacheConfig = self.cacheConfig.get(prop);
155
184
 
185
+ // Check if antibot is enabled for this URL (from warmup config or per-request)
186
+ const useAntibot =
187
+ options?.antibot ?? self.antibotConfig.get(prop) ?? false;
188
+
156
189
  if (cacheConfig?.enabled) {
157
190
  const cached = self.cache.get(baseUrl, options?.path, options);
158
191
  if (cached) return cached as any;
@@ -166,7 +199,7 @@ export class JirenClient<
166
199
  headers: options?.headers,
167
200
  maxRedirects: options?.maxRedirects,
168
201
  responseType: options?.responseType,
169
- antibot: options?.antibot,
202
+ antibot: useAntibot,
170
203
  }
171
204
  );
172
205
 
@@ -10,6 +10,12 @@ import type {
10
10
  UrlEndpoint,
11
11
  CacheConfig,
12
12
  RetryConfig,
13
+ Interceptors,
14
+ RequestInterceptor,
15
+ ResponseInterceptor,
16
+ ErrorInterceptor,
17
+ InterceptorRequestContext,
18
+ InterceptorResponseContext,
13
19
  } from "./types";
14
20
 
15
21
  const STATUS_TEXT: Record<number, string> = {
@@ -27,8 +33,10 @@ const STATUS_TEXT: Record<number, string> = {
27
33
  503: "Service Unavailable",
28
34
  };
29
35
 
30
- /** URL configuration with optional cache */
31
- export type UrlConfig = string | { url: string; cache?: boolean | CacheConfig };
36
+ /** URL configuration with optional cache and antibot */
37
+ export type UrlConfig =
38
+ | string
39
+ | { url: string; cache?: boolean | CacheConfig; antibot?: boolean };
32
40
 
33
41
  /** Options for JirenClient constructor */
34
42
  export interface JirenClientOptions<
@@ -44,6 +52,9 @@ export interface JirenClientOptions<
44
52
 
45
53
  /** Global retry configuration */
46
54
  retry?: number | RetryConfig;
55
+
56
+ /** Request/response interceptors */
57
+ interceptors?: Interceptors;
47
58
  }
48
59
 
49
60
  /** Helper to extract keys from Warmup Config */
@@ -96,9 +107,15 @@ export class JirenClient<
96
107
  private urlMap: Map<string, string> = new Map();
97
108
  private cacheConfig: Map<string, { enabled: boolean; ttl: number }> =
98
109
  new Map();
110
+ private antibotConfig: Map<string, boolean> = new Map();
99
111
  private cache: ResponseCache;
100
112
  private inflightRequests: Map<string, Promise<any>> = new Map();
101
113
  private globalRetry?: RetryConfig;
114
+ private requestInterceptors: RequestInterceptor[] = [];
115
+ private responseInterceptors: ResponseInterceptor[] = [];
116
+ private errorInterceptors: ErrorInterceptor[] = [];
117
+ private warmupPromise: Promise<void> | null = null;
118
+ private warmupComplete: Set<string> = new Set();
102
119
 
103
120
  /** Type-safe URL accessor for warmed-up URLs */
104
121
  public readonly url: UrlAccessor<T>;
@@ -138,6 +155,11 @@ export class JirenClient<
138
155
  : { enabled: true, ttl: config.cache.ttl || 60000 };
139
156
  this.cacheConfig.set(config.key, cacheConfig);
140
157
  }
158
+
159
+ // Store antibot config
160
+ if (config.antibot) {
161
+ this.antibotConfig.set(config.key, true);
162
+ }
141
163
  }
142
164
  }
143
165
  } else {
@@ -160,12 +182,31 @@ export class JirenClient<
160
182
  : { enabled: true, ttl: urlConfig.cache.ttl || 60000 };
161
183
  this.cacheConfig.set(key, cacheConfig);
162
184
  }
185
+
186
+ // Store antibot config
187
+ if ((urlConfig as { antibot?: boolean }).antibot) {
188
+ this.antibotConfig.set(key, true);
189
+ }
163
190
  }
164
191
  }
165
192
  }
166
193
 
167
194
  if (urls.length > 0) {
168
- this.warmup(urls);
195
+ // Lazy warmup in background (always - it's faster)
196
+ this.warmupPromise = this.warmup(urls).then(() => {
197
+ urls.forEach((url) => this.warmupComplete.add(url));
198
+ });
199
+ }
200
+
201
+ // Preload L2 disk cache entries into L1 memory for cached endpoints
202
+ // This ensures user's first request hits L1 (~0.001ms) instead of L2 (~5ms)
203
+ for (const [key, config] of this.cacheConfig.entries()) {
204
+ if (config.enabled) {
205
+ const url = this.urlMap.get(key);
206
+ if (url) {
207
+ this.cache.preloadL1(url);
208
+ }
209
+ }
169
210
  }
170
211
  }
171
212
 
@@ -179,12 +220,27 @@ export class JirenClient<
179
220
  ? { count: options.retry, delay: 100, backoff: 2 }
180
221
  : options.retry;
181
222
  }
223
+
224
+ // Initialize interceptors
225
+ if (options?.interceptors) {
226
+ this.requestInterceptors = options.interceptors.request || [];
227
+ this.responseInterceptors = options.interceptors.response || [];
228
+ this.errorInterceptors = options.interceptors.error || [];
229
+ }
182
230
  }
183
231
 
184
232
  private async waitFor(ms: number) {
185
233
  return new Promise((resolve) => setTimeout(resolve, ms));
186
234
  }
187
235
 
236
+ /**
237
+ * Wait for lazy warmup to complete.
238
+ * Only needed if using lazyWarmup: true and want to ensure warmup is done.
239
+ */
240
+ public async waitForWarmup(): Promise<void> {
241
+ if (this.warmupPromise) await this.warmupPromise;
242
+ }
243
+
188
244
  /**
189
245
  * Creates a proxy-based URL accessor for type-safe access to warmed-up URLs.
190
246
  */
@@ -213,14 +269,23 @@ export class JirenClient<
213
269
  get: async <R = any>(
214
270
  options?: UrlRequestOptions
215
271
  ): Promise<JirenResponse<R> | R | string | ArrayBuffer | Blob> => {
272
+ // Wait for warmup to complete if not yet done
273
+ if (self.warmupPromise && !self.warmupComplete.has(baseUrl)) {
274
+ await self.warmupPromise;
275
+ }
276
+
216
277
  // Check if caching is enabled for this URL
217
278
  const cacheConfig = self.cacheConfig.get(prop);
218
279
 
280
+ // Check if antibot is enabled for this URL (from warmup config or per-request)
281
+ const useAntibot =
282
+ options?.antibot ?? self.antibotConfig.get(prop) ?? false;
283
+
219
284
  if (cacheConfig?.enabled) {
220
285
  // Try to get from cache
221
286
  const cached = self.cache.get(baseUrl, options?.path, options);
222
287
  if (cached) {
223
- return self.rehydrateResponse(cached);
288
+ return cached as any;
224
289
  }
225
290
  }
226
291
 
@@ -247,7 +312,7 @@ export class JirenClient<
247
312
  headers: options?.headers,
248
313
  maxRedirects: options?.maxRedirects,
249
314
  responseType: options?.responseType,
250
- antibot: options?.antibot,
315
+ antibot: useAntibot,
251
316
  }
252
317
  );
253
318
 
@@ -409,6 +474,20 @@ export class JirenClient<
409
474
  }
410
475
  }
411
476
 
477
+ /**
478
+ * Register interceptors dynamically.
479
+ * @param interceptors - Interceptor configuration to add
480
+ * @returns this for chaining
481
+ */
482
+ public use(interceptors: Interceptors): this {
483
+ if (interceptors.request)
484
+ this.requestInterceptors.push(...interceptors.request);
485
+ if (interceptors.response)
486
+ this.responseInterceptors.push(...interceptors.response);
487
+ if (interceptors.error) this.errorInterceptors.push(...interceptors.error);
488
+ return this;
489
+ }
490
+
412
491
  /**
413
492
  * Warm up connections to URLs (DNS resolve + QUIC handshake) in parallel.
414
493
  * Call this early (e.g., at app startup) so subsequent requests are fast.
@@ -510,6 +589,20 @@ export class JirenClient<
510
589
  }
511
590
  }
512
591
 
592
+ // Build interceptor request context
593
+ let ctx: InterceptorRequestContext = { method, url, headers, body };
594
+
595
+ // Run request interceptors
596
+ for (const interceptor of this.requestInterceptors) {
597
+ ctx = await interceptor(ctx);
598
+ }
599
+
600
+ // Apply interceptor modifications
601
+ method = ctx.method;
602
+ url = ctx.url;
603
+ headers = ctx.headers;
604
+ body = ctx.body ?? null;
605
+
513
606
  const methodBuffer = Buffer.from(method + "\0");
514
607
  const urlBuffer = Buffer.from(url + "\0");
515
608
 
@@ -619,6 +712,16 @@ export class JirenClient<
619
712
 
620
713
  const response = this.parseResponse<T>(respPtr, url);
621
714
 
715
+ // Run response interceptors
716
+ let responseCtx: InterceptorResponseContext<T> = {
717
+ request: ctx,
718
+ response,
719
+ };
720
+ for (const interceptor of this.responseInterceptors) {
721
+ responseCtx = await interceptor(responseCtx);
722
+ }
723
+ const finalResponse = responseCtx.response;
724
+
622
725
  // Optional: Retry on specific status codes (e.g., 500, 502, 503, 504)
623
726
  // For now, we only retry on actual exceptions/network failures (null ptr)
624
727
  // or if we decide to throw on 5xx here.
@@ -626,15 +729,19 @@ export class JirenClient<
626
729
 
627
730
  // Auto-parse if requested
628
731
  if (responseType) {
629
- if (responseType === "json") return response.body.json();
630
- if (responseType === "text") return response.body.text();
732
+ if (responseType === "json") return finalResponse.body.json();
733
+ if (responseType === "text") return finalResponse.body.text();
631
734
  if (responseType === "arraybuffer")
632
- return response.body.arrayBuffer();
633
- if (responseType === "blob") return response.body.blob();
735
+ return finalResponse.body.arrayBuffer();
736
+ if (responseType === "blob") return finalResponse.body.blob();
634
737
  }
635
738
 
636
- return response;
739
+ return finalResponse;
637
740
  } catch (err) {
741
+ // Run error interceptors
742
+ for (const interceptor of this.errorInterceptors) {
743
+ await interceptor(err as Error, ctx);
744
+ }
638
745
  lastError = err;
639
746
  if (attempts < maxAttempts) {
640
747
  // Wait before retrying
@@ -697,14 +804,27 @@ export class JirenClient<
697
804
  if (len > 0 && bodyPtr) {
698
805
  // Create a copy of the buffer because the native response is freed immediately after
699
806
  buffer = toArrayBuffer(bodyPtr, 0, len).slice(0);
700
- }
701
807
 
702
- // Convert to base64 for caching persistence
703
- let base64Data = "";
704
- try {
705
- base64Data = Buffer.from(buffer).toString("base64");
706
- } catch (e) {
707
- // Should not happen for valid buffer
808
+ // Handle GZIP decompression if needed
809
+ const bufferView = new Uint8Array(buffer);
810
+ // Check for gzip magic bytes (0x1f 0x8b)
811
+ if (
812
+ bufferView.length >= 2 &&
813
+ bufferView[0] === 0x1f &&
814
+ bufferView[1] === 0x8b
815
+ ) {
816
+ try {
817
+ // Use Bun's built-in gzip decompression
818
+ const decompressed = Bun.gunzipSync(bufferView);
819
+ buffer = decompressed.buffer.slice(
820
+ decompressed.byteOffset,
821
+ decompressed.byteOffset + decompressed.byteLength
822
+ );
823
+ } catch (e) {
824
+ // Decompression failed, keep original buffer
825
+ console.warn("[Jiren] gzip decompression failed:", e);
826
+ }
827
+ }
708
828
  }
709
829
 
710
830
  let bodyUsed = false;
@@ -714,9 +834,8 @@ export class JirenClient<
714
834
  bodyUsed = true;
715
835
  };
716
836
 
717
- const bodyObj: JirenResponseBody<T> & { _raw?: string } = {
837
+ const bodyObj: JirenResponseBody<T> = {
718
838
  bodyUsed: false,
719
- _raw: base64Data,
720
839
  arrayBuffer: async () => {
721
840
  consumeBody();
722
841
  if (Buffer.isBuffer(buffer)) {
@@ -791,64 +910,6 @@ export class JirenClient<
791
910
 
792
911
  return { headers, serializedBody };
793
912
  }
794
-
795
- /**
796
- * Rehydrates a cached response object by restoring the body interface methods.
797
- * This is necessary because JSON serialization strips functions.
798
- */
799
- private rehydrateResponse(cached: any): JirenResponse {
800
- // If it already has methods, return as is
801
- if (typeof cached.body.text === "function") return cached;
802
-
803
- // Retrieve raw data
804
- const rawData = cached.body._raw;
805
- let buffer: Buffer;
806
-
807
- if (rawData) {
808
- buffer = Buffer.from(rawData, "base64");
809
- } else {
810
- buffer = Buffer.from("");
811
- }
812
-
813
- let bodyUsed = cached.body.bodyUsed || false;
814
- const consumeBody = () => {
815
- bodyUsed = true;
816
- };
817
-
818
- const bodyObj: JirenResponseBody = {
819
- _raw: rawData, // Keep it for re-caching if needed
820
- bodyUsed,
821
- arrayBuffer: async () => {
822
- consumeBody();
823
- return buffer.buffer.slice(
824
- buffer.byteOffset,
825
- buffer.byteOffset + buffer.byteLength
826
- ) as ArrayBuffer;
827
- },
828
- blob: async () => {
829
- consumeBody();
830
- return new Blob([buffer]);
831
- },
832
- text: async () => {
833
- consumeBody();
834
- return new TextDecoder().decode(buffer);
835
- },
836
- json: async <R = any>(): Promise<R> => {
837
- consumeBody();
838
- const text = new TextDecoder().decode(buffer);
839
- return JSON.parse(text);
840
- },
841
- } as any;
842
-
843
- Object.defineProperty(bodyObj, "bodyUsed", {
844
- get: () => bodyUsed,
845
- });
846
-
847
- return {
848
- ...cached,
849
- body: bodyObj,
850
- };
851
- }
852
913
  }
853
914
 
854
915
  class NativeHeaders {
@@ -20,6 +20,12 @@ export type {
20
20
  UrlEndpoint,
21
21
  JirenResponse,
22
22
  JirenResponseBody,
23
+ Interceptors,
24
+ RequestInterceptor,
25
+ ResponseInterceptor,
26
+ ErrorInterceptor,
27
+ InterceptorRequestContext,
28
+ InterceptorResponseContext,
23
29
  } from "./types";
24
30
 
25
31
  // Remove broken exports