jiren 3.1.0 → 3.2.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-node-native.ts +150 -292
- package/components/client.ts +385 -286
- package/components/index.ts +0 -2
- package/components/native-cache-node.ts +0 -3
- package/components/native-cache.ts +0 -8
- package/components/native-node.ts +137 -4
- package/components/native.ts +29 -65
- package/components/types.ts +25 -41
- package/dist/components/client-node-native.d.ts +0 -1
- package/dist/components/client-node-native.d.ts.map +1 -1
- package/dist/components/client-node-native.js +84 -202
- package/dist/components/client-node-native.js.map +1 -1
- package/dist/components/native-cache-node.d.ts.map +1 -1
- package/dist/components/native-cache-node.js +0 -1
- package/dist/components/native-cache-node.js.map +1 -1
- package/dist/components/native-node.d.ts +78 -0
- package/dist/components/native-node.d.ts.map +1 -1
- package/dist/components/native-node.js +122 -2
- package/dist/components/native-node.js.map +1 -1
- package/dist/components/types.d.ts +5 -13
- package/dist/components/types.d.ts.map +1 -1
- package/lib/libhttpclient.dylib +0 -0
- package/package.json +1 -1
- package/components/cache.ts +0 -451
- package/components/native-json.ts +0 -195
- package/components/persistent-worker.ts +0 -67
- package/components/subprocess-worker.ts +0 -60
- package/components/worker-pool.ts +0 -153
- package/components/worker.ts +0 -154
- package/dist/components/cache.d.ts +0 -32
- package/dist/components/cache.d.ts.map +0 -1
- package/dist/components/cache.js +0 -374
- package/dist/components/cache.js.map +0 -1
|
@@ -43,7 +43,7 @@ const STATUS_TEXT: Record<number, string> = {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
export function defineUrls<const T extends readonly TargetUrlConfig[]>(
|
|
46
|
-
urls: T
|
|
46
|
+
urls: T,
|
|
47
47
|
): T {
|
|
48
48
|
return urls;
|
|
49
49
|
}
|
|
@@ -59,9 +59,8 @@ const clientRegistry = new FinalizationRegistry<any>((ptr) => {
|
|
|
59
59
|
export class JirenClient<
|
|
60
60
|
T extends readonly TargetUrlConfig[] | Record<string, UrlConfig> =
|
|
61
61
|
| readonly TargetUrlConfig[]
|
|
62
|
-
| Record<string, UrlConfig
|
|
63
|
-
> implements Disposable
|
|
64
|
-
{
|
|
62
|
+
| Record<string, UrlConfig>,
|
|
63
|
+
> implements Disposable {
|
|
65
64
|
private ptr: any = null; // Koffi pointer
|
|
66
65
|
private urlMap: Map<string, string> = new Map();
|
|
67
66
|
private cacheConfig: Map<string, { enabled: boolean; ttl: number }> =
|
|
@@ -101,7 +100,6 @@ export class JirenClient<
|
|
|
101
100
|
/** Type-safe URL accessor for warmed-up URLs */
|
|
102
101
|
public readonly url: UrlAccessor<T>;
|
|
103
102
|
/** Alias for url property */
|
|
104
|
-
public readonly targets: UrlAccessor<T>;
|
|
105
103
|
|
|
106
104
|
private metricsCollector: MetricsCollector;
|
|
107
105
|
public readonly metrics: MetricsAPI;
|
|
@@ -150,7 +148,7 @@ export class JirenClient<
|
|
|
150
148
|
}
|
|
151
149
|
|
|
152
150
|
// Process target URLs
|
|
153
|
-
const targets = options?.urls
|
|
151
|
+
const targets = options?.urls;
|
|
154
152
|
if (targets) {
|
|
155
153
|
const urls: string[] = [];
|
|
156
154
|
|
|
@@ -179,7 +177,7 @@ export class JirenClient<
|
|
|
179
177
|
} else {
|
|
180
178
|
for (const [key, urlConfig] of Object.entries(targets) as [
|
|
181
179
|
string,
|
|
182
|
-
UrlConfig
|
|
180
|
+
UrlConfig,
|
|
183
181
|
][]) {
|
|
184
182
|
if (typeof urlConfig === "string") {
|
|
185
183
|
urls.push(urlConfig);
|
|
@@ -203,7 +201,7 @@ export class JirenClient<
|
|
|
203
201
|
}
|
|
204
202
|
}
|
|
205
203
|
|
|
206
|
-
if (urls.length > 0 &&
|
|
204
|
+
if (urls.length > 0 && options?.preconnect !== false) {
|
|
207
205
|
this.targetsPromise = this.preconnect(urls).then(() => {
|
|
208
206
|
urls.forEach((url) => this.targetsComplete.add(url));
|
|
209
207
|
this.targetsPromise = null;
|
|
@@ -223,8 +221,6 @@ export class JirenClient<
|
|
|
223
221
|
|
|
224
222
|
// Create proxy for type-safe URL access
|
|
225
223
|
this.url = this.createUrlAccessor();
|
|
226
|
-
// Alias for targets
|
|
227
|
-
this.targets = this.url;
|
|
228
224
|
|
|
229
225
|
// Store global retry config
|
|
230
226
|
if (options?.retry) {
|
|
@@ -263,8 +259,8 @@ export class JirenClient<
|
|
|
263
259
|
if (!baseUrl) {
|
|
264
260
|
throw new Error(
|
|
265
261
|
`URL key "${prop}" not found. Available keys: ${Array.from(
|
|
266
|
-
self.urlMap.keys()
|
|
267
|
-
).join(", ")}
|
|
262
|
+
self.urlMap.keys(),
|
|
263
|
+
).join(", ")}`,
|
|
268
264
|
);
|
|
269
265
|
}
|
|
270
266
|
|
|
@@ -275,7 +271,7 @@ export class JirenClient<
|
|
|
275
271
|
|
|
276
272
|
return {
|
|
277
273
|
get: async <R = any>(
|
|
278
|
-
options?: UrlRequestOptions
|
|
274
|
+
options?: UrlRequestOptions,
|
|
279
275
|
): Promise<JirenResponse<R> | R | string | ArrayBuffer | Blob> => {
|
|
280
276
|
if (self.targetsPromise) {
|
|
281
277
|
await self.targetsPromise;
|
|
@@ -330,7 +326,7 @@ export class JirenClient<
|
|
|
330
326
|
|
|
331
327
|
// Deduplication
|
|
332
328
|
const dedupKey = `GET:${buildUrl(options?.path)}:${JSON.stringify(
|
|
333
|
-
options?.headers || {}
|
|
329
|
+
options?.headers || {},
|
|
334
330
|
)}`;
|
|
335
331
|
|
|
336
332
|
if (self.inflightRequests.has(dedupKey)) {
|
|
@@ -369,7 +365,7 @@ export class JirenClient<
|
|
|
369
365
|
responseType: options?.responseType,
|
|
370
366
|
antibot: useAntibot,
|
|
371
367
|
timeout: options?.timeout,
|
|
372
|
-
}
|
|
368
|
+
},
|
|
373
369
|
);
|
|
374
370
|
|
|
375
371
|
if (
|
|
@@ -382,7 +378,7 @@ export class JirenClient<
|
|
|
382
378
|
response as JirenResponse,
|
|
383
379
|
cacheConfig.ttl,
|
|
384
380
|
options?.path,
|
|
385
|
-
options
|
|
381
|
+
options,
|
|
386
382
|
);
|
|
387
383
|
}
|
|
388
384
|
|
|
@@ -440,7 +436,7 @@ export class JirenClient<
|
|
|
440
436
|
|
|
441
437
|
post: async <R = any>(
|
|
442
438
|
body?: any,
|
|
443
|
-
options?: UrlRequestOptions & { responseType?: "json" }
|
|
439
|
+
options?: UrlRequestOptions & { responseType?: "json" },
|
|
444
440
|
): Promise<JirenResponse<R> | R | string | ArrayBuffer | Blob> => {
|
|
445
441
|
const { headers: preparedHeaders, serializedBody } =
|
|
446
442
|
self.prepareBody(body, options?.headers);
|
|
@@ -454,13 +450,13 @@ export class JirenClient<
|
|
|
454
450
|
responseType: options?.responseType,
|
|
455
451
|
antibot: options?.antibot,
|
|
456
452
|
timeout: options?.timeout,
|
|
457
|
-
}
|
|
453
|
+
},
|
|
458
454
|
);
|
|
459
455
|
},
|
|
460
456
|
|
|
461
457
|
put: async <R = any>(
|
|
462
458
|
body?: any,
|
|
463
|
-
options?: UrlRequestOptions & { responseType?: "json" }
|
|
459
|
+
options?: UrlRequestOptions & { responseType?: "json" },
|
|
464
460
|
): Promise<JirenResponse<R> | R | string | ArrayBuffer | Blob> => {
|
|
465
461
|
const { headers: preparedHeaders, serializedBody } =
|
|
466
462
|
self.prepareBody(body, options?.headers);
|
|
@@ -474,13 +470,13 @@ export class JirenClient<
|
|
|
474
470
|
responseType: options?.responseType,
|
|
475
471
|
antibot: options?.antibot,
|
|
476
472
|
timeout: options?.timeout,
|
|
477
|
-
}
|
|
473
|
+
},
|
|
478
474
|
);
|
|
479
475
|
},
|
|
480
476
|
|
|
481
477
|
patch: async <R = any>(
|
|
482
478
|
body?: any,
|
|
483
|
-
options?: UrlRequestOptions & { responseType?: "json" }
|
|
479
|
+
options?: UrlRequestOptions & { responseType?: "json" },
|
|
484
480
|
): Promise<JirenResponse<R> | R | string | ArrayBuffer | Blob> => {
|
|
485
481
|
const { headers: preparedHeaders, serializedBody } =
|
|
486
482
|
self.prepareBody(body, options?.headers);
|
|
@@ -494,12 +490,12 @@ export class JirenClient<
|
|
|
494
490
|
responseType: options?.responseType,
|
|
495
491
|
antibot: options?.antibot,
|
|
496
492
|
timeout: options?.timeout,
|
|
497
|
-
}
|
|
493
|
+
},
|
|
498
494
|
);
|
|
499
495
|
},
|
|
500
496
|
|
|
501
497
|
delete: async <R = any>(
|
|
502
|
-
options?: UrlRequestOptions & { responseType?: "json" }
|
|
498
|
+
options?: UrlRequestOptions & { responseType?: "json" },
|
|
503
499
|
): Promise<JirenResponse<R> | R | string | ArrayBuffer | Blob> => {
|
|
504
500
|
const { headers: preparedHeaders, serializedBody } =
|
|
505
501
|
self.prepareBody(options?.body, options?.headers);
|
|
@@ -513,12 +509,12 @@ export class JirenClient<
|
|
|
513
509
|
responseType: options?.responseType,
|
|
514
510
|
antibot: options?.antibot,
|
|
515
511
|
timeout: options?.timeout,
|
|
516
|
-
}
|
|
512
|
+
},
|
|
517
513
|
);
|
|
518
514
|
},
|
|
519
515
|
|
|
520
516
|
head: async (
|
|
521
|
-
options?: UrlRequestOptions
|
|
517
|
+
options?: UrlRequestOptions,
|
|
522
518
|
): Promise<JirenResponse<any>> => {
|
|
523
519
|
return self._request("HEAD", buildUrl(options?.path), null, {
|
|
524
520
|
headers: options?.headers,
|
|
@@ -529,7 +525,7 @@ export class JirenClient<
|
|
|
529
525
|
},
|
|
530
526
|
|
|
531
527
|
options: async (
|
|
532
|
-
options?: UrlRequestOptions
|
|
528
|
+
options?: UrlRequestOptions,
|
|
533
529
|
): Promise<JirenResponse<any>> => {
|
|
534
530
|
return self._request("OPTIONS", buildUrl(options?.path), null, {
|
|
535
531
|
headers: options?.headers,
|
|
@@ -540,7 +536,7 @@ export class JirenClient<
|
|
|
540
536
|
},
|
|
541
537
|
|
|
542
538
|
trace: async <R = any>(
|
|
543
|
-
options?: UrlRequestOptions
|
|
539
|
+
options?: UrlRequestOptions,
|
|
544
540
|
): Promise<JirenResponse<R> | R | string | ArrayBuffer | Blob> => {
|
|
545
541
|
// Trace method
|
|
546
542
|
return self._request<R>("TRACE", buildUrl(options?.path), null, {
|
|
@@ -565,31 +561,6 @@ export class JirenClient<
|
|
|
565
561
|
}
|
|
566
562
|
},
|
|
567
563
|
|
|
568
|
-
getJsonFields: async <T extends Record<string, any>>(
|
|
569
|
-
fields: (keyof T)[],
|
|
570
|
-
options?: UrlRequestOptions
|
|
571
|
-
): Promise<Partial<T>> => {
|
|
572
|
-
const { headers: preparedHeaders } = self.prepareBody(
|
|
573
|
-
options?.body,
|
|
574
|
-
options?.headers
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
const response = (await self._request<any>(
|
|
578
|
-
"GET",
|
|
579
|
-
buildUrl(options?.path),
|
|
580
|
-
null,
|
|
581
|
-
{
|
|
582
|
-
headers: preparedHeaders,
|
|
583
|
-
maxRedirects: options?.maxRedirects,
|
|
584
|
-
antibot: options?.antibot,
|
|
585
|
-
timeout: options?.timeout,
|
|
586
|
-
retry: options?.retry ?? self.globalRetry,
|
|
587
|
-
}
|
|
588
|
-
)) as JirenResponse<any>;
|
|
589
|
-
|
|
590
|
-
return response.body.jsonFields(fields);
|
|
591
|
-
},
|
|
592
|
-
|
|
593
564
|
/**
|
|
594
565
|
* Download with progress tracking
|
|
595
566
|
* Note: For Node.js, HTTPS uses fetch-based streaming. HTTP uses native streaming.
|
|
@@ -602,7 +573,7 @@ export class JirenClient<
|
|
|
602
573
|
* });
|
|
603
574
|
*/
|
|
604
575
|
download: async <R = any>(
|
|
605
|
-
options?: ProgressRequestOptions
|
|
576
|
+
options?: ProgressRequestOptions,
|
|
606
577
|
): Promise<JirenResponse<R>> => {
|
|
607
578
|
const url = buildUrl(options?.path);
|
|
608
579
|
|
|
@@ -617,132 +588,34 @@ export class JirenClient<
|
|
|
617
588
|
const isHttps = url.startsWith("https://");
|
|
618
589
|
|
|
619
590
|
if (isHttps) {
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
|
|
591
|
+
// HTTPS: Fall back to buffered request with simulated progress
|
|
592
|
+
// (Native streaming for HTTPS requires more work on HTTP/2 layer)
|
|
593
|
+
const response = await self._request<R>("GET", url, null, {
|
|
623
594
|
headers: options?.headers,
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
const response = await fetch(url, fetchOptions);
|
|
627
|
-
const contentLength = parseInt(
|
|
628
|
-
response.headers.get("content-length") || "0",
|
|
629
|
-
10
|
|
630
|
-
);
|
|
631
|
-
const reader = response.body?.getReader();
|
|
632
|
-
|
|
633
|
-
if (!reader) {
|
|
634
|
-
throw new Error("Failed to get response reader");
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const chunks: Uint8Array[] = [];
|
|
638
|
-
let loaded = 0;
|
|
639
|
-
const startTime = performance.now();
|
|
640
|
-
let lastLoaded = 0;
|
|
641
|
-
let lastTime = startTime;
|
|
642
|
-
|
|
643
|
-
while (true) {
|
|
644
|
-
const { done, value } = await reader.read();
|
|
645
|
-
if (done) break;
|
|
646
|
-
|
|
647
|
-
chunks.push(value);
|
|
648
|
-
loaded += value.length;
|
|
649
|
-
|
|
650
|
-
// Calculate progress
|
|
651
|
-
const now = performance.now();
|
|
652
|
-
const elapsed = now - lastTime;
|
|
653
|
-
const bytesThisInterval = loaded - lastLoaded;
|
|
654
|
-
const speed =
|
|
655
|
-
elapsed > 0 ? (bytesThisInterval / elapsed) * 1000 : 0;
|
|
656
|
-
const remaining =
|
|
657
|
-
contentLength > 0 ? contentLength - loaded : 0;
|
|
658
|
-
const eta = speed > 0 ? (remaining / speed) * 1000 : 0;
|
|
659
|
-
|
|
660
|
-
options.onDownloadProgress({
|
|
661
|
-
loaded,
|
|
662
|
-
total: contentLength,
|
|
663
|
-
percent:
|
|
664
|
-
contentLength > 0
|
|
665
|
-
? Math.round((loaded / contentLength) * 100)
|
|
666
|
-
: 0,
|
|
667
|
-
speed: Math.round(speed),
|
|
668
|
-
eta: Math.round(eta),
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
lastLoaded = loaded;
|
|
672
|
-
lastTime = now;
|
|
673
|
-
}
|
|
595
|
+
});
|
|
674
596
|
|
|
675
|
-
//
|
|
597
|
+
// Fire final progress event
|
|
598
|
+
const bodyText = await response.body.text();
|
|
599
|
+
const bodySize = Buffer.byteLength(bodyText);
|
|
676
600
|
options.onDownloadProgress({
|
|
677
|
-
loaded,
|
|
678
|
-
total:
|
|
601
|
+
loaded: bodySize,
|
|
602
|
+
total: bodySize,
|
|
679
603
|
percent: 100,
|
|
680
604
|
speed: 0,
|
|
681
605
|
eta: 0,
|
|
682
606
|
});
|
|
683
607
|
|
|
684
|
-
//
|
|
685
|
-
const totalLength = chunks.reduce(
|
|
686
|
-
(acc, chunk) => acc + chunk.length,
|
|
687
|
-
0
|
|
688
|
-
);
|
|
689
|
-
const buffer = new Uint8Array(totalLength);
|
|
690
|
-
let offset = 0;
|
|
691
|
-
for (const chunk of chunks) {
|
|
692
|
-
buffer.set(chunk, offset);
|
|
693
|
-
offset += chunk.length;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// Parse headers
|
|
697
|
-
const headersObj: Record<string, string> = {};
|
|
698
|
-
response.headers.forEach((value, key) => {
|
|
699
|
-
headersObj[key.toLowerCase()] = value;
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
// Build response
|
|
703
|
-
const decoder = new TextDecoder();
|
|
704
|
-
let bodyUsed = false;
|
|
705
|
-
|
|
706
|
-
const bodyObj: JirenResponseBody<R> = {
|
|
707
|
-
bodyUsed: false,
|
|
708
|
-
arrayBuffer: async () => buffer.buffer as ArrayBuffer,
|
|
709
|
-
blob: async () => new Blob([buffer]),
|
|
710
|
-
text: async () => {
|
|
711
|
-
bodyUsed = true;
|
|
712
|
-
return decoder.decode(buffer);
|
|
713
|
-
},
|
|
714
|
-
json: async <T = R>(): Promise<T> => {
|
|
715
|
-
bodyUsed = true;
|
|
716
|
-
return JSON.parse(decoder.decode(buffer));
|
|
717
|
-
},
|
|
718
|
-
jsonFields: async <T extends Record<string, any> = any>(
|
|
719
|
-
fields: (keyof T)[]
|
|
720
|
-
): Promise<Partial<T>> => {
|
|
721
|
-
bodyUsed = true;
|
|
722
|
-
const fullObj = JSON.parse(decoder.decode(buffer));
|
|
723
|
-
const result: Partial<T> = {};
|
|
724
|
-
for (const field of fields) {
|
|
725
|
-
if (field in fullObj) {
|
|
726
|
-
result[field] = fullObj[field];
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
return result;
|
|
730
|
-
},
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
Object.defineProperty(bodyObj, "bodyUsed", {
|
|
734
|
-
get: () => bodyUsed,
|
|
735
|
-
});
|
|
736
|
-
|
|
608
|
+
// Recreate response with body
|
|
737
609
|
return {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
610
|
+
...response,
|
|
611
|
+
body: {
|
|
612
|
+
...response.body,
|
|
613
|
+
text: async () => bodyText,
|
|
614
|
+
json: async () => JSON.parse(bodyText),
|
|
615
|
+
arrayBuffer: async () => Buffer.from(bodyText).buffer,
|
|
616
|
+
blob: async () => new Blob([bodyText]),
|
|
617
|
+
bodyUsed: true,
|
|
618
|
+
},
|
|
746
619
|
} as JirenResponse<R>;
|
|
747
620
|
}
|
|
748
621
|
|
|
@@ -770,40 +643,14 @@ export class JirenClient<
|
|
|
770
643
|
arrayBuffer: async () => Buffer.from(bodyText).buffer,
|
|
771
644
|
blob: async () => new Blob([bodyText]),
|
|
772
645
|
bodyUsed: true,
|
|
773
|
-
jsonFields: async <T extends Record<string, any> = any>(
|
|
774
|
-
fields: (keyof T)[]
|
|
775
|
-
): Promise<Partial<T>> => {
|
|
776
|
-
const fullObj = JSON.parse(bodyText);
|
|
777
|
-
const result: Partial<T> = {};
|
|
778
|
-
for (const field of fields) {
|
|
779
|
-
if (field in fullObj) {
|
|
780
|
-
result[field] = fullObj[field];
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
return result;
|
|
784
|
-
},
|
|
785
646
|
},
|
|
786
647
|
} as JirenResponse<R>;
|
|
787
648
|
},
|
|
788
|
-
|
|
789
|
-
/**
|
|
790
|
-
* Upload with progress tracking
|
|
791
|
-
* @param options - Request options with onUploadProgress callback
|
|
792
|
-
* @example
|
|
793
|
-
* await client.url.api.upload({
|
|
794
|
-
* method: 'POST',
|
|
795
|
-
* path: '/upload',
|
|
796
|
-
* body: largeData,
|
|
797
|
-
* onUploadProgress: (progress) => {
|
|
798
|
-
* console.log(`${progress.percent}%`);
|
|
799
|
-
* }
|
|
800
|
-
* });
|
|
801
|
-
*/
|
|
802
649
|
upload: async <R = any>(
|
|
803
650
|
options?: ProgressRequestOptions & {
|
|
804
651
|
method?: "POST" | "PUT" | "PATCH";
|
|
805
652
|
body?: string | object | null;
|
|
806
|
-
}
|
|
653
|
+
},
|
|
807
654
|
): Promise<JirenResponse<R>> => {
|
|
808
655
|
const url = buildUrl(options?.path);
|
|
809
656
|
const method = options?.method || "POST";
|
|
@@ -841,86 +688,110 @@ export class JirenClient<
|
|
|
841
688
|
});
|
|
842
689
|
|
|
843
690
|
// Prepare headers
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
691
|
+
// HTTPS: Use fast path with simulated progress
|
|
692
|
+
// (Native layer handles the upload in one call)
|
|
693
|
+
const { headers: preparedHeaders, serializedBody } =
|
|
694
|
+
self.prepareBody(options.body, options.headers);
|
|
848
695
|
|
|
849
|
-
|
|
850
|
-
// Since fetch doesn't support upload progress natively, we simulate it
|
|
851
|
-
const response = await fetch(url, {
|
|
696
|
+
const response = await self._request<R>(
|
|
852
697
|
method,
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
698
|
+
url,
|
|
699
|
+
serializedBody,
|
|
700
|
+
{
|
|
701
|
+
headers: preparedHeaders,
|
|
702
|
+
},
|
|
703
|
+
);
|
|
856
704
|
|
|
857
|
-
// Fire final progress (100%)
|
|
858
|
-
const elapsed = performance.now() - startTime;
|
|
859
|
-
const speed = elapsed > 0 ? (bodyLength / elapsed) * 1000 : 0;
|
|
705
|
+
// Fire final progress event (100%)
|
|
860
706
|
options.onUploadProgress({
|
|
861
707
|
loaded: bodyLength,
|
|
862
708
|
total: bodyLength,
|
|
863
709
|
percent: 100,
|
|
864
|
-
speed:
|
|
710
|
+
speed: 0,
|
|
865
711
|
eta: 0,
|
|
866
712
|
});
|
|
867
713
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
714
|
+
return response;
|
|
715
|
+
},
|
|
716
|
+
getJsonFields: async <R = any>(
|
|
717
|
+
fields: string[],
|
|
718
|
+
options?: UrlRequestOptions,
|
|
719
|
+
): Promise<R> => {
|
|
720
|
+
if (fields.length > 8) {
|
|
721
|
+
throw new Error("Maximum 8 fields supported per call");
|
|
722
|
+
}
|
|
873
723
|
|
|
874
|
-
const
|
|
875
|
-
let bodyUsed = false;
|
|
724
|
+
const url = buildUrl(options?.path);
|
|
876
725
|
|
|
877
|
-
|
|
878
|
-
bodyUsed: false,
|
|
879
|
-
text: async () => {
|
|
880
|
-
bodyUsed = true;
|
|
881
|
-
return responseBody;
|
|
882
|
-
},
|
|
883
|
-
json: async <T = R>(): Promise<T> => {
|
|
884
|
-
bodyUsed = true;
|
|
885
|
-
return JSON.parse(responseBody);
|
|
886
|
-
},
|
|
887
|
-
jsonFields: async <T extends Record<string, any> = any>(
|
|
888
|
-
fields: (keyof T)[]
|
|
889
|
-
): Promise<Partial<T>> => {
|
|
890
|
-
bodyUsed = true;
|
|
891
|
-
const fullObj = JSON.parse(responseBody);
|
|
892
|
-
const result: Partial<T> = {};
|
|
893
|
-
for (const field of fields) {
|
|
894
|
-
if (field in fullObj) {
|
|
895
|
-
result[field] = fullObj[field];
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
return result;
|
|
899
|
-
},
|
|
900
|
-
arrayBuffer: async () => {
|
|
901
|
-
bodyUsed = true;
|
|
902
|
-
return Buffer.from(responseBody).buffer as ArrayBuffer;
|
|
903
|
-
},
|
|
904
|
-
blob: async () => {
|
|
905
|
-
bodyUsed = true;
|
|
906
|
-
return new Blob([responseBody]);
|
|
907
|
-
},
|
|
908
|
-
};
|
|
726
|
+
let resultPtr: any = null;
|
|
909
727
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
728
|
+
if (options?.headers || options?.body) {
|
|
729
|
+
const { headers, serializedBody } = self.prepareBody(
|
|
730
|
+
options.body,
|
|
731
|
+
options.headers,
|
|
732
|
+
);
|
|
733
|
+
const headersStr = Object.entries({
|
|
734
|
+
...self.defaultHeaders,
|
|
735
|
+
...headers,
|
|
736
|
+
})
|
|
737
|
+
.map(([k, v]) => `${k.toLowerCase()}: ${v}`)
|
|
738
|
+
.join("\r\n");
|
|
739
|
+
|
|
740
|
+
resultPtr = lib.symbols.zclient_request_json_fields(
|
|
741
|
+
self.ptr,
|
|
742
|
+
"GET", // Default to GET
|
|
743
|
+
url,
|
|
744
|
+
headersStr,
|
|
745
|
+
serializedBody,
|
|
746
|
+
fields,
|
|
747
|
+
fields.length,
|
|
748
|
+
);
|
|
749
|
+
} else {
|
|
750
|
+
resultPtr = lib.symbols.zclient_get_json_fields(
|
|
751
|
+
self.ptr,
|
|
752
|
+
url,
|
|
753
|
+
fields,
|
|
754
|
+
fields.length,
|
|
755
|
+
);
|
|
756
|
+
}
|
|
913
757
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
758
|
+
if (!resultPtr) {
|
|
759
|
+
throw new Error("Native getJsonFields failed");
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
try {
|
|
763
|
+
const result: any = {};
|
|
764
|
+
const decoder = new TextDecoder();
|
|
765
|
+
|
|
766
|
+
for (let i = 0; i < fields.length; i++) {
|
|
767
|
+
const key = fields[i]!;
|
|
768
|
+
const type = lib.symbols.zjson_field_type(resultPtr, i);
|
|
769
|
+
|
|
770
|
+
if (type === 0) {
|
|
771
|
+
result[key] = null;
|
|
772
|
+
} else if (type === 1) {
|
|
773
|
+
const len = Number(lib.symbols.zjson_field_len(resultPtr, i));
|
|
774
|
+
result[key] = len === 1;
|
|
775
|
+
} else if (type === 2) {
|
|
776
|
+
const len = Number(lib.symbols.zjson_field_len(resultPtr, i));
|
|
777
|
+
result[key] = len;
|
|
778
|
+
} else if (type === 3) {
|
|
779
|
+
const ptrVal = lib.symbols.zjson_field_str(resultPtr, i);
|
|
780
|
+
const lenVal = Number(
|
|
781
|
+
lib.symbols.zjson_field_len(resultPtr, i),
|
|
782
|
+
);
|
|
783
|
+
if (ptrVal && lenVal > 0) {
|
|
784
|
+
const bytes = koffi.decode(ptrVal, "uint8_t", lenVal);
|
|
785
|
+
result[key] = decoder.decode(bytes);
|
|
786
|
+
} else {
|
|
787
|
+
result[key] = "";
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return result as R;
|
|
792
|
+
} finally {
|
|
793
|
+
lib.symbols.zclient_json_fields_free(resultPtr);
|
|
794
|
+
}
|
|
924
795
|
},
|
|
925
796
|
} as UrlEndpoint;
|
|
926
797
|
},
|
|
@@ -960,8 +831,8 @@ export class JirenClient<
|
|
|
960
831
|
new Promise<void>((resolve) => {
|
|
961
832
|
lib.symbols.zclient_prefetch(this.ptr, url);
|
|
962
833
|
resolve();
|
|
963
|
-
})
|
|
964
|
-
)
|
|
834
|
+
}),
|
|
835
|
+
),
|
|
965
836
|
);
|
|
966
837
|
}
|
|
967
838
|
|
|
@@ -977,37 +848,37 @@ export class JirenClient<
|
|
|
977
848
|
method: string,
|
|
978
849
|
url: string,
|
|
979
850
|
body?: string | null,
|
|
980
|
-
options?: RequestOptions & { responseType: "json" }
|
|
851
|
+
options?: RequestOptions & { responseType: "json" },
|
|
981
852
|
): Promise<T>;
|
|
982
853
|
protected async _request<T = any>(
|
|
983
854
|
method: string,
|
|
984
855
|
url: string,
|
|
985
856
|
body?: string | null,
|
|
986
|
-
options?: RequestOptions & { responseType: "text" }
|
|
857
|
+
options?: RequestOptions & { responseType: "text" },
|
|
987
858
|
): Promise<string>;
|
|
988
859
|
protected async _request<T = any>(
|
|
989
860
|
method: string,
|
|
990
861
|
url: string,
|
|
991
862
|
body?: string | null,
|
|
992
|
-
options?: RequestOptions & { responseType: "arraybuffer" }
|
|
863
|
+
options?: RequestOptions & { responseType: "arraybuffer" },
|
|
993
864
|
): Promise<ArrayBuffer>;
|
|
994
865
|
protected async _request<T = any>(
|
|
995
866
|
method: string,
|
|
996
867
|
url: string,
|
|
997
868
|
body?: string | null,
|
|
998
|
-
options?: RequestOptions & { responseType: "blob" }
|
|
869
|
+
options?: RequestOptions & { responseType: "blob" },
|
|
999
870
|
): Promise<Blob>;
|
|
1000
871
|
protected async _request<T = any>(
|
|
1001
872
|
method: string,
|
|
1002
873
|
url: string,
|
|
1003
874
|
body?: string | null,
|
|
1004
|
-
options?: RequestOptions
|
|
875
|
+
options?: RequestOptions,
|
|
1005
876
|
): Promise<JirenResponse<T>>;
|
|
1006
877
|
protected async _request<T = any>(
|
|
1007
878
|
method: string,
|
|
1008
879
|
url: string,
|
|
1009
880
|
body?: string | null,
|
|
1010
|
-
options?: RequestOptions | Record<string, string> | null
|
|
881
|
+
options?: RequestOptions | Record<string, string> | null,
|
|
1011
882
|
): Promise<JirenResponse<T> | T | string | ArrayBuffer | Blob> {
|
|
1012
883
|
if (!this.ptr) throw new Error("Client is closed");
|
|
1013
884
|
|
|
@@ -1095,7 +966,7 @@ export class JirenClient<
|
|
|
1095
966
|
// Retry logic
|
|
1096
967
|
let retryConfig = this.globalRetry;
|
|
1097
968
|
if (options && typeof options === "object" && "retry" in options) {
|
|
1098
|
-
const userRetry =
|
|
969
|
+
const userRetry = options?.retry;
|
|
1099
970
|
if (typeof userRetry === "number") {
|
|
1100
971
|
retryConfig = { count: userRetry, delay: 100, backoff: 2 };
|
|
1101
972
|
} else if (typeof userRetry === "object") {
|
|
@@ -1123,7 +994,7 @@ export class JirenClient<
|
|
|
1123
994
|
headerStr.length > 0 ? headerStr : null,
|
|
1124
995
|
body || null,
|
|
1125
996
|
maxRedirects,
|
|
1126
|
-
antibot
|
|
997
|
+
antibot,
|
|
1127
998
|
);
|
|
1128
999
|
|
|
1129
1000
|
if (!respPtr) {
|
|
@@ -1203,7 +1074,7 @@ export class JirenClient<
|
|
|
1203
1074
|
const bodyPtr = lib.symbols.zclient_response_body(respPtr);
|
|
1204
1075
|
|
|
1205
1076
|
const headersLen = Number(
|
|
1206
|
-
lib.symbols.zclient_response_headers_len(respPtr)
|
|
1077
|
+
lib.symbols.zclient_response_headers_len(respPtr),
|
|
1207
1078
|
);
|
|
1208
1079
|
let headersObj: Record<string, string> | NativeHeaders = {};
|
|
1209
1080
|
|
|
@@ -1211,7 +1082,7 @@ export class JirenClient<
|
|
|
1211
1082
|
const rawHeadersPtr = lib.symbols.zclient_response_headers(respPtr);
|
|
1212
1083
|
if (rawHeadersPtr) {
|
|
1213
1084
|
const raw = Buffer.from(
|
|
1214
|
-
koffi.decode(rawHeadersPtr, "uint8_t", headersLen)
|
|
1085
|
+
koffi.decode(rawHeadersPtr, "uint8_t", headersLen),
|
|
1215
1086
|
);
|
|
1216
1087
|
headersObj = new NativeHeaders(raw);
|
|
1217
1088
|
}
|
|
@@ -1228,7 +1099,7 @@ export class JirenClient<
|
|
|
1228
1099
|
}
|
|
1229
1100
|
return Reflect.get(target, prop);
|
|
1230
1101
|
},
|
|
1231
|
-
}
|
|
1102
|
+
},
|
|
1232
1103
|
) as unknown as Record<string, string>;
|
|
1233
1104
|
|
|
1234
1105
|
let buffer: Buffer = Buffer.alloc(0);
|
|
@@ -1267,7 +1138,7 @@ export class JirenClient<
|
|
|
1267
1138
|
consumeBody();
|
|
1268
1139
|
return buffer.buffer.slice(
|
|
1269
1140
|
buffer.byteOffset,
|
|
1270
|
-
buffer.byteOffset + buffer.byteLength
|
|
1141
|
+
buffer.byteOffset + buffer.byteLength,
|
|
1271
1142
|
) as ArrayBuffer;
|
|
1272
1143
|
},
|
|
1273
1144
|
blob: async () => {
|
|
@@ -1282,19 +1153,6 @@ export class JirenClient<
|
|
|
1282
1153
|
consumeBody();
|
|
1283
1154
|
return JSON.parse(buffer.toString("utf-8"));
|
|
1284
1155
|
},
|
|
1285
|
-
jsonFields: async <T extends Record<string, any> = any>(
|
|
1286
|
-
fields: (keyof T)[]
|
|
1287
|
-
): Promise<Partial<T>> => {
|
|
1288
|
-
consumeBody();
|
|
1289
|
-
const fullObj = JSON.parse(buffer.toString("utf-8"));
|
|
1290
|
-
const result: Partial<T> = {};
|
|
1291
|
-
for (const field of fields) {
|
|
1292
|
-
if (field in fullObj) {
|
|
1293
|
-
result[field] = fullObj[field];
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
return result;
|
|
1297
|
-
},
|
|
1298
1156
|
};
|
|
1299
1157
|
|
|
1300
1158
|
Object.defineProperty(bodyObj, "bodyUsed", {
|
|
@@ -1321,7 +1179,7 @@ export class JirenClient<
|
|
|
1321
1179
|
*/
|
|
1322
1180
|
private prepareBody(
|
|
1323
1181
|
body: string | object | null | undefined,
|
|
1324
|
-
userHeaders?: Record<string, string
|
|
1182
|
+
userHeaders?: Record<string, string>,
|
|
1325
1183
|
): { headers: Record<string, string>; serializedBody: string | null } {
|
|
1326
1184
|
let serializedBody: string | null = null;
|
|
1327
1185
|
const headers = { ...userHeaders };
|
|
@@ -1330,7 +1188,7 @@ export class JirenClient<
|
|
|
1330
1188
|
if (typeof body === "object") {
|
|
1331
1189
|
serializedBody = JSON.stringify(body);
|
|
1332
1190
|
const hasContentType = Object.keys(headers).some(
|
|
1333
|
-
(k) => k.toLowerCase() === "content-type"
|
|
1191
|
+
(k) => k.toLowerCase() === "content-type",
|
|
1334
1192
|
);
|
|
1335
1193
|
if (!hasContentType) {
|
|
1336
1194
|
headers["Content-Type"] = "application/json";
|