@vex-chat/libvex 6.6.0 → 6.6.2

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/src/http.ts ADDED
@@ -0,0 +1,563 @@
1
+ /**
2
+ * Copyright (c) 2020-2026 Vex Heavy Industries LLC
3
+ * Licensed under AGPL-3.0. See LICENSE for details.
4
+ * Commercial licenses available at vex.wtf
5
+ */
6
+
7
+ export interface FetchHttpClientDefaults {
8
+ headers: {
9
+ common: {
10
+ [name: string]: string | undefined;
11
+ Authorization?: string;
12
+ };
13
+ };
14
+ }
15
+
16
+ export interface FetchHttpClientOptions {
17
+ readonly responseType?: HttpResponseType;
18
+ readonly signal?: AbortSignal;
19
+ }
20
+
21
+ export interface HttpErrorOptions {
22
+ readonly cause?: unknown;
23
+ readonly code?: string;
24
+ readonly config: HttpRequestRecord;
25
+ readonly response?: HttpResponse<unknown>;
26
+ }
27
+
28
+ export type HttpHeadersInit = Readonly<Record<string, string | undefined>>;
29
+
30
+ export interface HttpProgressEvent {
31
+ readonly loaded: number;
32
+ readonly total?: number;
33
+ }
34
+
35
+ export interface HttpRequestConfig {
36
+ readonly headers?: HttpHeadersInit;
37
+ readonly onDownloadProgress?: (event: HttpProgressEvent) => void;
38
+ readonly onUploadProgress?: (event: HttpProgressEvent) => void;
39
+ readonly responseType?: HttpResponseType;
40
+ readonly signal?: AbortSignal;
41
+ readonly timeout?: number;
42
+ readonly validateStatus?: (status: number) => boolean;
43
+ }
44
+
45
+ export interface HttpRequestRecord {
46
+ readonly headers: Readonly<Record<string, string>>;
47
+ readonly method: string;
48
+ readonly url: string;
49
+ }
50
+
51
+ export interface HttpResponse<T = ArrayBuffer> {
52
+ readonly config: HttpRequestRecord;
53
+ readonly data: T;
54
+ readonly headers: Readonly<Record<string, string>>;
55
+ readonly status: number;
56
+ readonly statusText: string;
57
+ }
58
+
59
+ export type HttpResponseType = "arraybuffer" | "json" | "text";
60
+
61
+ export class FetchHttpClient {
62
+ public readonly defaults: FetchHttpClientDefaults = {
63
+ headers: { common: {} },
64
+ };
65
+
66
+ private readonly defaultResponseType: HttpResponseType;
67
+ private readonly defaultSignal?: AbortSignal;
68
+
69
+ public constructor(options: FetchHttpClientOptions = {}) {
70
+ this.defaultResponseType = options.responseType ?? "arraybuffer";
71
+ if (options.signal !== undefined) {
72
+ this.defaultSignal = options.signal;
73
+ }
74
+ }
75
+
76
+ public async delete(
77
+ url: string,
78
+ config?: HttpRequestConfig,
79
+ ): Promise<HttpResponse<unknown>> {
80
+ return await this.request("DELETE", url, undefined, config);
81
+ }
82
+
83
+ public async get(
84
+ url: string,
85
+ config: HttpRequestConfig & { readonly responseType: "json" },
86
+ ): Promise<HttpResponse<unknown>>;
87
+ public async get(
88
+ url: string,
89
+ config: HttpRequestConfig & { readonly responseType: "text" },
90
+ ): Promise<HttpResponse<string>>;
91
+ public async get(
92
+ url: string,
93
+ config?: HttpRequestConfig,
94
+ ): Promise<HttpResponse>;
95
+ public async get(
96
+ url: string,
97
+ config?: HttpRequestConfig,
98
+ ): Promise<HttpResponse<unknown>> {
99
+ return await this.request("GET", url, undefined, config);
100
+ }
101
+
102
+ public async patch(
103
+ url: string,
104
+ data?: unknown,
105
+ config?: HttpRequestConfig,
106
+ ): Promise<HttpResponse<unknown>> {
107
+ return await this.request("PATCH", url, data, config);
108
+ }
109
+
110
+ public async post(
111
+ url: string,
112
+ data: unknown,
113
+ config: HttpRequestConfig & { readonly responseType: "json" },
114
+ ): Promise<HttpResponse<unknown>>;
115
+ public async post(
116
+ url: string,
117
+ data: unknown,
118
+ config: HttpRequestConfig & { readonly responseType: "text" },
119
+ ): Promise<HttpResponse<string>>;
120
+ public async post(
121
+ url: string,
122
+ data?: unknown,
123
+ config?: HttpRequestConfig,
124
+ ): Promise<HttpResponse>;
125
+ public async post(
126
+ url: string,
127
+ data?: unknown,
128
+ config?: HttpRequestConfig,
129
+ ): Promise<HttpResponse<unknown>> {
130
+ return await this.request("POST", url, data, config);
131
+ }
132
+
133
+ private async request(
134
+ method: string,
135
+ url: string,
136
+ data: unknown,
137
+ config: HttpRequestConfig = {},
138
+ ): Promise<HttpResponse<unknown>> {
139
+ const headers = new Headers();
140
+ appendHeaders(headers, this.defaults.headers.common);
141
+ appendHeaders(headers, config.headers);
142
+
143
+ const body = makeBody(data, headers);
144
+ maybeReportUploadProgress(data, config.onUploadProgress);
145
+
146
+ const requestRecord: HttpRequestRecord = {
147
+ headers: headersToRecord(headers),
148
+ method,
149
+ url,
150
+ };
151
+ const abort = composeAbortSignal(
152
+ [this.defaultSignal, config.signal],
153
+ config.timeout,
154
+ );
155
+
156
+ try {
157
+ const requestInit: RequestInit = { headers, method };
158
+ if (body !== undefined) {
159
+ requestInit.body = body;
160
+ }
161
+ if (abort.signal !== undefined) {
162
+ requestInit.signal = abort.signal;
163
+ }
164
+ const response = await fetch(url, requestInit);
165
+ const responseType =
166
+ config.responseType ?? this.defaultResponseType;
167
+ const validateStatus =
168
+ config.validateStatus ??
169
+ ((status: number) => status >= 200 && status < 300);
170
+ if (!validateStatus(response.status)) {
171
+ const { cause, data: responseData } =
172
+ await readErrorResponseData(
173
+ response,
174
+ responseType,
175
+ config.onDownloadProgress,
176
+ );
177
+ const httpResponse: HttpResponse<unknown> = {
178
+ config: requestRecord,
179
+ data: responseData,
180
+ headers: headersToRecord(response.headers),
181
+ status: response.status,
182
+ statusText: response.statusText,
183
+ };
184
+ const errorOptions: HttpErrorOptions =
185
+ cause === undefined
186
+ ? { config: requestRecord, response: httpResponse }
187
+ : {
188
+ cause,
189
+ config: requestRecord,
190
+ response: httpResponse,
191
+ };
192
+ throw new HttpError(
193
+ `Request failed with status code ${String(response.status)}`,
194
+ errorOptions,
195
+ );
196
+ }
197
+ const responseData = await readResponseData(
198
+ response,
199
+ responseType,
200
+ config.onDownloadProgress,
201
+ );
202
+ const httpResponse: HttpResponse<unknown> = {
203
+ config: requestRecord,
204
+ data: responseData,
205
+ headers: headersToRecord(response.headers),
206
+ status: response.status,
207
+ statusText: response.statusText,
208
+ };
209
+ return httpResponse;
210
+ } catch (err: unknown) {
211
+ if (isHttpError(err)) {
212
+ throw err;
213
+ }
214
+ const code = abort.didTimeout()
215
+ ? "ETIMEDOUT"
216
+ : abort.signal?.aborted === true
217
+ ? "ERR_CANCELED"
218
+ : undefined;
219
+ const errorOptions: HttpErrorOptions =
220
+ code === undefined
221
+ ? { cause: err, config: requestRecord }
222
+ : { cause: err, code, config: requestRecord };
223
+ throw new HttpError(errorMessage(err), errorOptions);
224
+ } finally {
225
+ abort.cleanup();
226
+ }
227
+ }
228
+ }
229
+
230
+ export class HttpError extends Error {
231
+ public readonly code?: string;
232
+ public readonly config: HttpRequestRecord;
233
+ public readonly isHttpError = true;
234
+ public readonly response?: HttpResponse<unknown>;
235
+
236
+ public constructor(message: string, options: HttpErrorOptions) {
237
+ super(message);
238
+ this.name = "HttpError";
239
+ this.config = options.config;
240
+ if (options.code !== undefined) {
241
+ this.code = options.code;
242
+ }
243
+ if (options.cause !== undefined) {
244
+ Object.defineProperty(this, "cause", {
245
+ configurable: true,
246
+ value: options.cause,
247
+ writable: true,
248
+ });
249
+ }
250
+ if (options.response !== undefined) {
251
+ this.response = options.response;
252
+ }
253
+ }
254
+ }
255
+
256
+ export function createFetchHttpClient(
257
+ options?: FetchHttpClientOptions,
258
+ ): FetchHttpClient {
259
+ return new FetchHttpClient(options);
260
+ }
261
+
262
+ export function isHttpError(err: unknown): err is HttpError {
263
+ return (
264
+ typeof err === "object" &&
265
+ err !== null &&
266
+ (err as { readonly isHttpError?: unknown }).isHttpError === true
267
+ );
268
+ }
269
+
270
+ function appendHeaders(target: Headers, source: HttpHeadersInit | undefined) {
271
+ if (source === undefined) {
272
+ return;
273
+ }
274
+ const headerRecord: Readonly<Record<string, string | undefined>> = source;
275
+ for (const key of Object.keys(headerRecord)) {
276
+ const value = headerRecord[key];
277
+ if (value !== undefined) {
278
+ target.set(key, value);
279
+ }
280
+ }
281
+ }
282
+
283
+ function bodyLength(data: unknown): number | undefined {
284
+ if (typeof data === "string") {
285
+ return new TextEncoder().encode(data).byteLength;
286
+ }
287
+ if (data instanceof ArrayBuffer) {
288
+ return data.byteLength;
289
+ }
290
+ if (ArrayBuffer.isView(data)) {
291
+ return data.byteLength;
292
+ }
293
+ if (typeof Blob !== "undefined" && data instanceof Blob) {
294
+ return data.size;
295
+ }
296
+ if (isFormDataValue(data)) {
297
+ return formDataPayloadLength(data);
298
+ }
299
+ return undefined;
300
+ }
301
+
302
+ function composeAbortSignal(
303
+ signals: readonly (AbortSignal | undefined)[],
304
+ timeout: number | undefined,
305
+ ): {
306
+ readonly cleanup: () => void;
307
+ readonly didTimeout: () => boolean;
308
+ readonly signal?: AbortSignal;
309
+ } {
310
+ const activeSignals = signals.filter(
311
+ (s): s is AbortSignal => s !== undefined,
312
+ );
313
+ if (activeSignals.length === 0 && timeout === undefined) {
314
+ return {
315
+ cleanup: () => {},
316
+ didTimeout: () => false,
317
+ };
318
+ }
319
+
320
+ const controller = new AbortController();
321
+ const cleanups: (() => void)[] = [];
322
+ let timedOut = false;
323
+
324
+ for (const signal of activeSignals) {
325
+ if (signal.aborted) {
326
+ controller.abort(signal.reason);
327
+ continue;
328
+ }
329
+ const onAbort = () => {
330
+ controller.abort(signal.reason);
331
+ };
332
+ signal.addEventListener("abort", onAbort, { once: true });
333
+ cleanups.push(() => {
334
+ signal.removeEventListener("abort", onAbort);
335
+ });
336
+ }
337
+
338
+ if (timeout !== undefined) {
339
+ const timeoutID = setTimeout(() => {
340
+ timedOut = true;
341
+ controller.abort();
342
+ }, timeout);
343
+ cleanups.push(() => {
344
+ clearTimeout(timeoutID);
345
+ });
346
+ }
347
+
348
+ return {
349
+ cleanup: () => {
350
+ for (const cleanup of cleanups) {
351
+ cleanup();
352
+ }
353
+ },
354
+ didTimeout: () => timedOut,
355
+ signal: controller.signal,
356
+ };
357
+ }
358
+
359
+ function errorMessage(err: unknown): string {
360
+ if (err instanceof Error) {
361
+ return err.message;
362
+ }
363
+ return String(err);
364
+ }
365
+
366
+ function formDataPayloadLength(data: FormData): number {
367
+ let total = 0;
368
+ data.forEach((value: unknown) => {
369
+ total += formDataValueLength(value);
370
+ });
371
+ return total;
372
+ }
373
+
374
+ function formDataValueLength(value: unknown): number {
375
+ if (typeof value === "string") {
376
+ return new TextEncoder().encode(value).byteLength;
377
+ }
378
+ if (typeof Blob !== "undefined" && value instanceof Blob) {
379
+ return value.size;
380
+ }
381
+ return 0;
382
+ }
383
+
384
+ function headersToRecord(headers: Headers): Record<string, string> {
385
+ const out: Record<string, string> = {};
386
+ headers.forEach((value, key) => {
387
+ out[key] = value;
388
+ });
389
+ return out;
390
+ }
391
+
392
+ function isFormDataValue(value: unknown): value is FormData {
393
+ return typeof FormData !== "undefined" && value instanceof FormData;
394
+ }
395
+
396
+ function isJsonBodyCandidate(value: unknown): boolean {
397
+ if (value === null || typeof value !== "object") {
398
+ return false;
399
+ }
400
+ if (isFormDataValue(value)) {
401
+ return false;
402
+ }
403
+ if (typeof Blob !== "undefined" && value instanceof Blob) {
404
+ return false;
405
+ }
406
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
407
+ return false;
408
+ }
409
+ if (
410
+ typeof URLSearchParams !== "undefined" &&
411
+ value instanceof URLSearchParams
412
+ ) {
413
+ return false;
414
+ }
415
+ return true;
416
+ }
417
+
418
+ function makeBody(data: unknown, headers: Headers): BodyInit | undefined {
419
+ if (data === undefined) {
420
+ return undefined;
421
+ }
422
+ if (isFormDataValue(data)) {
423
+ const contentType = headers.get("Content-Type");
424
+ if (
425
+ contentType?.toLowerCase().startsWith("multipart/form-data") ===
426
+ true
427
+ ) {
428
+ headers.delete("Content-Type");
429
+ }
430
+ return data;
431
+ }
432
+ if (isJsonBodyCandidate(data)) {
433
+ if (!headers.has("Content-Type")) {
434
+ headers.set("Content-Type", "application/json");
435
+ }
436
+ return JSON.stringify(data);
437
+ }
438
+ if (typeof data === "string") {
439
+ return data;
440
+ }
441
+ if (typeof Blob !== "undefined" && data instanceof Blob) {
442
+ return data;
443
+ }
444
+ if (data instanceof ArrayBuffer) {
445
+ return data;
446
+ }
447
+ if (ArrayBuffer.isView(data)) {
448
+ if (data.buffer instanceof ArrayBuffer) {
449
+ return new Uint8Array(
450
+ data.buffer,
451
+ data.byteOffset,
452
+ data.byteLength,
453
+ );
454
+ }
455
+ throw new TypeError("SharedArrayBuffer HTTP bodies are not supported");
456
+ }
457
+ if (
458
+ typeof URLSearchParams !== "undefined" &&
459
+ data instanceof URLSearchParams
460
+ ) {
461
+ return data;
462
+ }
463
+ throw new TypeError("Unsupported HTTP request body type");
464
+ }
465
+
466
+ function maybeReportUploadProgress(
467
+ data: unknown,
468
+ onUploadProgress: ((event: HttpProgressEvent) => void) | undefined,
469
+ ): void {
470
+ if (onUploadProgress === undefined) {
471
+ return;
472
+ }
473
+ const total = bodyLength(data);
474
+ if (total !== undefined) {
475
+ onUploadProgress({ loaded: total, total });
476
+ }
477
+ }
478
+
479
+ function progressEvent(
480
+ loaded: number,
481
+ total: number | undefined,
482
+ ): HttpProgressEvent {
483
+ return total === undefined ? { loaded } : { loaded, total };
484
+ }
485
+
486
+ async function readArrayBuffer(
487
+ response: Response,
488
+ onDownloadProgress: ((event: HttpProgressEvent) => void) | undefined,
489
+ ): Promise<ArrayBuffer> {
490
+ if (response.body === null || onDownloadProgress === undefined) {
491
+ return await response.arrayBuffer();
492
+ }
493
+
494
+ const rawTotal = response.headers.get("content-length");
495
+ const parsedTotal = rawTotal === null ? Number.NaN : Number(rawTotal);
496
+ const total = Number.isFinite(parsedTotal) ? parsedTotal : undefined;
497
+ const chunks: Uint8Array[] = [];
498
+ let loaded = 0;
499
+ const reader = response.body.getReader();
500
+
501
+ for (;;) {
502
+ const { done, value } = await reader.read();
503
+ if (done) {
504
+ break;
505
+ }
506
+ chunks.push(value);
507
+ loaded += value.byteLength;
508
+ onDownloadProgress(progressEvent(loaded, total));
509
+ }
510
+
511
+ const out = new Uint8Array(loaded);
512
+ let offset = 0;
513
+ for (const chunk of chunks) {
514
+ out.set(chunk, offset);
515
+ offset += chunk.byteLength;
516
+ }
517
+ return out.buffer;
518
+ }
519
+
520
+ async function readErrorResponseData(
521
+ response: Response,
522
+ responseType: HttpResponseType,
523
+ onDownloadProgress: ((event: HttpProgressEvent) => void) | undefined,
524
+ ): Promise<{ readonly cause?: unknown; readonly data: unknown }> {
525
+ try {
526
+ if (responseType === "json") {
527
+ const text = await response.text();
528
+ if (text.length === 0) {
529
+ return { data: null };
530
+ }
531
+ try {
532
+ const data: unknown = JSON.parse(text);
533
+ return { data };
534
+ } catch {
535
+ return { data: text };
536
+ }
537
+ }
538
+ return {
539
+ data: await readResponseData(
540
+ response,
541
+ responseType,
542
+ onDownloadProgress,
543
+ ),
544
+ };
545
+ } catch (cause: unknown) {
546
+ return { cause, data: null };
547
+ }
548
+ }
549
+
550
+ async function readResponseData(
551
+ response: Response,
552
+ responseType: HttpResponseType,
553
+ onDownloadProgress: ((event: HttpProgressEvent) => void) | undefined,
554
+ ): Promise<unknown> {
555
+ if (responseType === "json") {
556
+ const data: unknown = await response.json();
557
+ return data;
558
+ }
559
+ if (responseType === "text") {
560
+ return await response.text();
561
+ }
562
+ return await readArrayBuffer(response, onDownloadProgress);
563
+ }
package/src/index.ts CHANGED
@@ -41,6 +41,12 @@ export type {
41
41
  VexFile,
42
42
  } from "./Client.js";
43
43
  export { createCodec, msgpack } from "./codec.js";
44
+ export { HttpError, isHttpError } from "./http.js";
45
+ export type {
46
+ HttpErrorOptions,
47
+ HttpRequestRecord,
48
+ HttpResponse,
49
+ } from "./http.js";
44
50
  export {
45
51
  clampLocalMessageRetentionDays,
46
52
  effectiveMessageRetentionHintDays,
@@ -1,20 +0,0 @@
1
- /**
2
- * Copyright (c) 2020-2026 Vex Heavy Industries LLC
3
- * Licensed under AGPL-3.0. See LICENSE for details.
4
- * Commercial licenses available at vex.wtf
5
- */
6
- import type { AxiosInstance } from "axios";
7
- /**
8
- * Node-only HTTP(S) agents for libvex axios — lives under `storage/node/` so the
9
- * platform-guard plugin allows `node:http` / `node:https` (see poison-node-imports).
10
- */
11
- import * as nodeHttp from "node:http";
12
- import * as nodeHttps from "node:https";
13
- export interface NodeHttpAgentPair {
14
- readonly http: nodeHttp.Agent;
15
- readonly https: nodeHttps.Agent;
16
- }
17
- export declare function attachNodeAgentsToAxios(instance: AxiosInstance, agents: NodeHttpAgentPair): void;
18
- export declare function createNodeHttpAgents(): NodeHttpAgentPair;
19
- export declare function destroyNodeHttpAgents(agents: NodeHttpAgentPair): void;
20
- //# sourceMappingURL=http-agents.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"http-agents.d.ts","sourceRoot":"","sources":["../../../src/storage/node/http-agents.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,SAAS,MAAM,YAAY,CAAC;AAExC,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC;CACnC;AAED,wBAAgB,uBAAuB,CACnC,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,iBAAiB,GAC1B,IAAI,CAGN;AAED,wBAAgB,oBAAoB,IAAI,iBAAiB,CAKxD;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAGrE"}
@@ -1,26 +0,0 @@
1
- /**
2
- * Copyright (c) 2020-2026 Vex Heavy Industries LLC
3
- * Licensed under AGPL-3.0. See LICENSE for details.
4
- * Commercial licenses available at vex.wtf
5
- */
6
- /**
7
- * Node-only HTTP(S) agents for libvex axios — lives under `storage/node/` so the
8
- * platform-guard plugin allows `node:http` / `node:https` (see poison-node-imports).
9
- */
10
- import * as nodeHttp from "node:http";
11
- import * as nodeHttps from "node:https";
12
- export function attachNodeAgentsToAxios(instance, agents) {
13
- instance.defaults.httpAgent = agents.http;
14
- instance.defaults.httpsAgent = agents.https;
15
- }
16
- export function createNodeHttpAgents() {
17
- return {
18
- http: new nodeHttp.Agent({ keepAlive: true }),
19
- https: new nodeHttps.Agent({ keepAlive: true }),
20
- };
21
- }
22
- export function destroyNodeHttpAgents(agents) {
23
- agents.http.destroy();
24
- agents.https.destroy();
25
- }
26
- //# sourceMappingURL=http-agents.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"http-agents.js","sourceRoot":"","sources":["../../../src/storage/node/http-agents.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,SAAS,MAAM,YAAY,CAAC;AAOxC,MAAM,UAAU,uBAAuB,CACnC,QAAuB,EACvB,MAAyB;IAEzB,QAAQ,CAAC,QAAQ,CAAC,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,oBAAoB;IAChC,OAAO;QACH,IAAI,EAAE,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC7C,KAAK,EAAE,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;KAClD,CAAC;AACN,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAyB;IAC3D,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;IACtB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;AAC3B,CAAC"}
@@ -1,39 +0,0 @@
1
- /**
2
- * Copyright (c) 2020-2026 Vex Heavy Industries LLC
3
- * Licensed under AGPL-3.0. See LICENSE for details.
4
- * Commercial licenses available at vex.wtf
5
- */
6
-
7
- import type { AxiosInstance } from "axios";
8
-
9
- /**
10
- * Node-only HTTP(S) agents for libvex axios — lives under `storage/node/` so the
11
- * platform-guard plugin allows `node:http` / `node:https` (see poison-node-imports).
12
- */
13
- import * as nodeHttp from "node:http";
14
- import * as nodeHttps from "node:https";
15
-
16
- export interface NodeHttpAgentPair {
17
- readonly http: nodeHttp.Agent;
18
- readonly https: nodeHttps.Agent;
19
- }
20
-
21
- export function attachNodeAgentsToAxios(
22
- instance: AxiosInstance,
23
- agents: NodeHttpAgentPair,
24
- ): void {
25
- instance.defaults.httpAgent = agents.http;
26
- instance.defaults.httpsAgent = agents.https;
27
- }
28
-
29
- export function createNodeHttpAgents(): NodeHttpAgentPair {
30
- return {
31
- http: new nodeHttp.Agent({ keepAlive: true }),
32
- https: new nodeHttps.Agent({ keepAlive: true }),
33
- };
34
- }
35
-
36
- export function destroyNodeHttpAgents(agents: NodeHttpAgentPair): void {
37
- agents.http.destroy();
38
- agents.https.destroy();
39
- }