@harborclient/http 0.1.1

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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -0
  3. package/dist/Body.d.ts +28 -0
  4. package/dist/Body.d.ts.map +1 -0
  5. package/dist/Body.js +72 -0
  6. package/dist/Headers.d.ts +25 -0
  7. package/dist/Headers.d.ts.map +1 -0
  8. package/dist/Headers.js +62 -0
  9. package/dist/IBody.d.ts +32 -0
  10. package/dist/IBody.d.ts.map +1 -0
  11. package/dist/IBody.js +1 -0
  12. package/dist/IHeaders.d.ts +40 -0
  13. package/dist/IHeaders.d.ts.map +1 -0
  14. package/dist/IHeaders.js +1 -0
  15. package/dist/IQueryString.d.ts +20 -0
  16. package/dist/IQueryString.d.ts.map +1 -0
  17. package/dist/IQueryString.js +1 -0
  18. package/dist/IRequester.d.ts +17 -0
  19. package/dist/IRequester.d.ts.map +1 -0
  20. package/dist/IRequester.js +1 -0
  21. package/dist/IResponseReader.d.ts +29 -0
  22. package/dist/IResponseReader.d.ts.map +1 -0
  23. package/dist/IResponseReader.js +1 -0
  24. package/dist/QueryString.d.ts +40 -0
  25. package/dist/QueryString.d.ts.map +1 -0
  26. package/dist/QueryString.js +78 -0
  27. package/dist/Requester.d.ts +100 -0
  28. package/dist/Requester.d.ts.map +1 -0
  29. package/dist/Requester.js +403 -0
  30. package/dist/ResponseReader.d.ts +24 -0
  31. package/dist/ResponseReader.d.ts.map +1 -0
  32. package/dist/ResponseReader.js +66 -0
  33. package/dist/formData.d.ts +27 -0
  34. package/dist/formData.d.ts.map +1 -0
  35. package/dist/formData.js +58 -0
  36. package/dist/httpHeaders.d.ts +23 -0
  37. package/dist/httpHeaders.d.ts.map +1 -0
  38. package/dist/httpHeaders.js +61 -0
  39. package/dist/index.d.ts +21 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +9 -0
  42. package/dist/logger.d.ts +19 -0
  43. package/dist/logger.d.ts.map +1 -0
  44. package/dist/logger.js +34 -0
  45. package/dist/settings.d.ts +42 -0
  46. package/dist/settings.d.ts.map +1 -0
  47. package/dist/settings.js +26 -0
  48. package/dist/types.d.ts +231 -0
  49. package/dist/types.d.ts.map +1 -0
  50. package/dist/types.js +1 -0
  51. package/dist/urlencoded.d.ts +27 -0
  52. package/dist/urlencoded.d.ts.map +1 -0
  53. package/dist/urlencoded.js +53 -0
  54. package/package.json +101 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) HarborClient
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @harborclient/http
2
+
3
+ **Full documentation:** [https://harborclient.github.io/http/](https://harborclient.github.io/http/)
4
+
5
+ **Outbound HTTP utilities for HarborClient.**
6
+
7
+ @harborclient/http is a library for URL and header validation, request body encoding, fetch execution (with optional undici dispatcher for SSL and proxy settings), response size limits, and redirect following:
8
+
9
+ - **Request execution:** `Requester` with configurable timeouts, SSL verification, proxy support, and redirect following.
10
+ - **Validation and encoding:** URL/header validation and request body encoding via `QueryString` and related helpers.
11
+ - **Safety limits:** Built-in response size caps via `HARD_MAX_RESPONSE_SIZE_MB`.
12
+
13
+ ## Documentation
14
+
15
+ | Topic | Link |
16
+ | --------------- | ---------------------------------------------------------------- |
17
+ | Getting started | [Introduction](https://harborclient.github.io/http/) |
18
+ | Installation | [Installation](https://harborclient.github.io/http/installation) |
19
+ | Usage | [Usage](https://harborclient.github.io/http/usage) |
20
+
21
+ Canonical docs live in [`docs/`](./docs/). Edit those pages directly, then run `pnpm docs:build:nav` to refresh the VitePress sidebar.
22
+
23
+ ## Development
24
+
25
+ ```bash
26
+ pnpm install
27
+ pnpm test
28
+ pnpm docs:serve # VitePress dev server with nav watcher
29
+ pnpm docs:build # production docs build
30
+ ```
31
+
32
+ ## License
33
+
34
+ MIT
package/dist/Body.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import type { BuildMultipartResult, IBody } from './IBody.js';
2
+ /**
3
+ * Encodes request bodies for multipart, urlencoded, and preview display.
4
+ */
5
+ export declare class Body implements IBody {
6
+ /**
7
+ * Builds a human-readable summary of multipart form parts for request preview.
8
+ *
9
+ * @param body - Serialized multipart form parts JSON.
10
+ * @returns Summary string for SentRequest.body.
11
+ */
12
+ summarizeFormParts(body: string): string;
13
+ /**
14
+ * Builds a FormData body from serialized multipart form parts.
15
+ *
16
+ * @param body - Serialized multipart form parts JSON.
17
+ * @returns FormData ready for fetch, or an error message when a file cannot be read.
18
+ */
19
+ buildMultipart(body: string): Promise<BuildMultipartResult>;
20
+ /**
21
+ * Builds an application/x-www-form-urlencoded body from serialized key-value rows.
22
+ *
23
+ * @param body - JSON array stored in the request body field.
24
+ * @returns URL-encoded query string for the request body.
25
+ */
26
+ buildUrlEncoded(body: string): string;
27
+ }
28
+ //# sourceMappingURL=Body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Body.d.ts","sourceRoot":"","sources":["../src/Body.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAE9D;;GAEG;AACH,qBAAa,IAAK,YAAW,KAAK;IAChC;;;;;OAKG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAkBxC;;;;;OAKG;IACG,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAwBjE;;;;;OAKG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAQtC"}
package/dist/Body.js ADDED
@@ -0,0 +1,72 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { basename } from 'path';
3
+ import { parseFormParts } from './formData.js';
4
+ import { parseUrlEncodedParts } from './urlencoded.js';
5
+ /**
6
+ * Encodes request bodies for multipart, urlencoded, and preview display.
7
+ */
8
+ export class Body {
9
+ /**
10
+ * Builds a human-readable summary of multipart form parts for request preview.
11
+ *
12
+ * @param body - Serialized multipart form parts JSON.
13
+ * @returns Summary string for SentRequest.body.
14
+ */
15
+ summarizeFormParts(body) {
16
+ const parts = parseFormParts(body).filter((part) => part.enabled && part.key.trim());
17
+ if (parts.length === 0) {
18
+ return '';
19
+ }
20
+ return parts
21
+ .map((part) => {
22
+ const key = part.key.trim();
23
+ if (part.type === 'file') {
24
+ const names = part.files.map((filePath) => basename(filePath)).join(', ');
25
+ return `${key}: [${names || 'no files'}]`;
26
+ }
27
+ return `${key}: ${part.value}`;
28
+ })
29
+ .join('\n');
30
+ }
31
+ /**
32
+ * Builds a FormData body from serialized multipart form parts.
33
+ *
34
+ * @param body - Serialized multipart form parts JSON.
35
+ * @returns FormData ready for fetch, or an error message when a file cannot be read.
36
+ */
37
+ async buildMultipart(body) {
38
+ const parts = parseFormParts(body).filter((part) => part.enabled && part.key.trim());
39
+ const formData = new FormData();
40
+ for (const part of parts) {
41
+ const key = part.key.trim();
42
+ if (part.type === 'file') {
43
+ for (const filePath of part.files) {
44
+ try {
45
+ const data = await readFile(filePath);
46
+ formData.append(key, new Blob([Uint8Array.from(data)]), basename(filePath));
47
+ }
48
+ catch {
49
+ return { error: `Failed to read file: ${filePath}` };
50
+ }
51
+ }
52
+ continue;
53
+ }
54
+ formData.append(key, part.value);
55
+ }
56
+ return { formData };
57
+ }
58
+ /**
59
+ * Builds an application/x-www-form-urlencoded body from serialized key-value rows.
60
+ *
61
+ * @param body - JSON array stored in the request body field.
62
+ * @returns URL-encoded query string for the request body.
63
+ */
64
+ buildUrlEncoded(body) {
65
+ const rows = parseUrlEncodedParts(body).filter((row) => row.enabled && row.key.trim());
66
+ const params = new URLSearchParams();
67
+ for (const row of rows) {
68
+ params.append(row.key.trim(), row.value);
69
+ }
70
+ return params.toString();
71
+ }
72
+ }
@@ -0,0 +1,25 @@
1
+ import type { BodyType, KeyValue } from './types.js';
2
+ import type { ApplyCookieResult, BuildHeadersResult, IHeaders } from './IHeaders.js';
3
+ /**
4
+ * Builds request headers and merges cookie jar values for outbound requests.
5
+ */
6
+ export declare class Headers implements IHeaders {
7
+ /**
8
+ * Builds request headers from enabled key-value pairs and body type defaults.
9
+ *
10
+ * Rejects hop-by-hop headers and fields containing control characters or CRLF.
11
+ *
12
+ * @param headers - User-defined headers.
13
+ * @param bodyType - Body type used to infer Content-Type when absent.
14
+ * @returns Header map ready for fetch, or an error when a field is invalid.
15
+ */
16
+ build(headers: KeyValue[], bodyType: BodyType): BuildHeadersResult;
17
+ /**
18
+ * Validates and merges a cookie jar header when no Cookie header is already present.
19
+ *
20
+ * @param headers - Mutable header map to update on success.
21
+ * @param cookieHeader - Optional Cookie header value from the cookie jar.
22
+ */
23
+ applyCookie(headers: Record<string, string>, cookieHeader: string | undefined): ApplyCookieResult;
24
+ }
25
+ //# sourceMappingURL=Headers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Headers.d.ts","sourceRoot":"","sources":["../src/Headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGrF;;GAEG;AACH,qBAAa,OAAQ,YAAW,QAAQ;IACtC;;;;;;;;OAQG;IACH,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,kBAAkB;IAgClE;;;;;OAKG;IACH,WAAW,CACT,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,iBAAiB;CAcrB"}
@@ -0,0 +1,62 @@
1
+ import { validateHeaderField } from './httpHeaders.js';
2
+ /**
3
+ * Builds request headers and merges cookie jar values for outbound requests.
4
+ */
5
+ export class Headers {
6
+ /**
7
+ * Builds request headers from enabled key-value pairs and body type defaults.
8
+ *
9
+ * Rejects hop-by-hop headers and fields containing control characters or CRLF.
10
+ *
11
+ * @param headers - User-defined headers.
12
+ * @param bodyType - Body type used to infer Content-Type when absent.
13
+ * @returns Header map ready for fetch, or an error when a field is invalid.
14
+ */
15
+ build(headers, bodyType) {
16
+ const result = {};
17
+ for (const header of headers) {
18
+ if (header.enabled && header.key.trim()) {
19
+ const key = header.key.trim();
20
+ if (bodyType === 'multipart' && key.toLowerCase() === 'content-type') {
21
+ continue;
22
+ }
23
+ const validationError = validateHeaderField(key, header.value);
24
+ if (validationError) {
25
+ return { ok: false, error: validationError };
26
+ }
27
+ result[key] = header.value;
28
+ }
29
+ }
30
+ const hasContentType = Object.keys(result).some((key) => key.toLowerCase() === 'content-type');
31
+ if (!hasContentType) {
32
+ if (bodyType === 'json') {
33
+ result['Content-Type'] = 'application/json';
34
+ }
35
+ else if (bodyType === 'text') {
36
+ result['Content-Type'] = 'text/plain';
37
+ }
38
+ else if (bodyType === 'urlencoded') {
39
+ result['Content-Type'] = 'application/x-www-form-urlencoded';
40
+ }
41
+ }
42
+ return { ok: true, headers: result };
43
+ }
44
+ /**
45
+ * Validates and merges a cookie jar header when no Cookie header is already present.
46
+ *
47
+ * @param headers - Mutable header map to update on success.
48
+ * @param cookieHeader - Optional Cookie header value from the cookie jar.
49
+ */
50
+ applyCookie(headers, cookieHeader) {
51
+ const hasCookieHeader = Object.keys(headers).some((key) => key.toLowerCase() === 'cookie');
52
+ if (!cookieHeader || hasCookieHeader) {
53
+ return { ok: true };
54
+ }
55
+ const cookieError = validateHeaderField('Cookie', cookieHeader);
56
+ if (cookieError) {
57
+ return { ok: false, error: cookieError };
58
+ }
59
+ headers.Cookie = cookieHeader;
60
+ return { ok: true };
61
+ }
62
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Result of building a multipart request body.
3
+ */
4
+ export type BuildMultipartResult = {
5
+ formData: FormData;
6
+ } | {
7
+ error: string;
8
+ };
9
+ /**
10
+ * Request body encoding for multipart, urlencoded, and preview summaries.
11
+ */
12
+ export interface IBody {
13
+ /**
14
+ * Builds a human-readable summary of multipart form parts for request preview.
15
+ *
16
+ * @param body - Serialized multipart form parts JSON.
17
+ */
18
+ summarizeFormParts(body: string): string;
19
+ /**
20
+ * Builds a FormData body from serialized multipart form parts.
21
+ *
22
+ * @param body - Serialized multipart form parts JSON.
23
+ */
24
+ buildMultipart(body: string): Promise<BuildMultipartResult>;
25
+ /**
26
+ * Builds an application/x-www-form-urlencoded body from serialized key-value rows.
27
+ *
28
+ * @param body - JSON array stored in the request body field.
29
+ */
30
+ buildUrlEncoded(body: string): string;
31
+ }
32
+ //# sourceMappingURL=IBody.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IBody.d.ts","sourceRoot":"","sources":["../src/IBody.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,KAAK;IACpB;;;;OAIG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAEzC;;;;OAIG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAE5D;;;;OAIG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACvC"}
package/dist/IBody.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,40 @@
1
+ import type { BodyType, KeyValue } from './types.js';
2
+ /**
3
+ * Result of building request headers from user input.
4
+ */
5
+ export type BuildHeadersResult = {
6
+ ok: true;
7
+ headers: Record<string, string>;
8
+ } | {
9
+ ok: false;
10
+ error: string;
11
+ };
12
+ /**
13
+ * Result of merging a cookie jar header into a request header map.
14
+ */
15
+ export type ApplyCookieResult = {
16
+ ok: true;
17
+ } | {
18
+ ok: false;
19
+ error: string;
20
+ };
21
+ /**
22
+ * Request header construction and cookie header merging.
23
+ */
24
+ export interface IHeaders {
25
+ /**
26
+ * Builds request headers from enabled key-value pairs and body type defaults.
27
+ *
28
+ * @param headers - User-defined headers.
29
+ * @param bodyType - Body type used to infer Content-Type when absent.
30
+ */
31
+ build(headers: KeyValue[], bodyType: BodyType): BuildHeadersResult;
32
+ /**
33
+ * Validates and merges a cookie jar header when no Cookie header is already present.
34
+ *
35
+ * @param headers - Mutable header map to update on success.
36
+ * @param cookieHeader - Optional Cookie header value from the cookie jar.
37
+ */
38
+ applyCookie(headers: Record<string, string>, cookieHeader: string | undefined): ApplyCookieResult;
39
+ }
40
+ //# sourceMappingURL=IHeaders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IHeaders.d.ts","sourceRoot":"","sources":["../src/IHeaders.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAErD;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5E;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;;;;OAKG;IACH,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,kBAAkB,CAAC;IAEnE;;;;;OAKG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,iBAAiB,CAAC;CACnG"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import type { KeyValue } from './types.js';
2
+ /**
3
+ * URL query string building and validation for outbound HTTP requests.
4
+ */
5
+ export interface IQueryString {
6
+ /**
7
+ * Appends enabled query parameters to a base URL.
8
+ *
9
+ * @param baseUrl - Request URL before query string merging.
10
+ * @param params - Key-value pairs to append as search params.
11
+ */
12
+ buildUrl(baseUrl: string, params: KeyValue[]): string;
13
+ /**
14
+ * Returns whether a URL is safe to send via fetch: absolute http(s) or root-relative path.
15
+ *
16
+ * @param url - Request URL before or after query string merging.
17
+ */
18
+ isValidRequestUrl(url: string): boolean;
19
+ }
20
+ //# sourceMappingURL=IQueryString.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IQueryString.d.ts","sourceRoot":"","sources":["../src/IQueryString.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IAEtD;;;;OAIG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACzC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import type { SendRequestInput, SendResult } from './types.js';
2
+ import type { RequestSettings } from './settings.js';
3
+ /**
4
+ * Executes outbound HTTP requests and returns timing and response metadata.
5
+ */
6
+ export interface IRequester {
7
+ /**
8
+ * Executes an HTTP request via fetch and returns timing and response metadata.
9
+ *
10
+ * @param input - Method, URL, headers, params, body, and body type.
11
+ * @param settings - General request settings for timeout, size limits, SSL verification, and redirect following.
12
+ * @param signal - Optional abort signal to cancel the in-flight request.
13
+ * @param cookieHeader - Optional Cookie header value from the cookie jar.
14
+ */
15
+ executeRequest(input: SendRequestInput, settings?: RequestSettings, signal?: AbortSignal, cookieHeader?: string): Promise<SendResult>;
16
+ }
17
+ //# sourceMappingURL=IRequester.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IRequester.d.ts","sourceRoot":"","sources":["../src/IRequester.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;;;;;;OAOG;IACH,cAAc,CACZ,KAAK,EAAE,gBAAgB,EACvB,QAAQ,CAAC,EAAE,eAAe,EAC1B,MAAM,CAAC,EAAE,WAAW,EACpB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,UAAU,CAAC,CAAC;CACxB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Result of reading a response body with size limits applied.
3
+ */
4
+ export type ReadResponseBodyResult = {
5
+ body: string;
6
+ sizeBytes: number;
7
+ bodyBase64?: string;
8
+ } | {
9
+ error: string;
10
+ };
11
+ /**
12
+ * Response body reading with configurable and hard size limits.
13
+ */
14
+ export interface IResponseReader {
15
+ /**
16
+ * Resolves the effective response size limit in megabytes.
17
+ *
18
+ * @param maxResponseSizeMb - User setting; 0 means no configurable limit.
19
+ */
20
+ resolveMaxResponseSizeMb(maxResponseSizeMb: number): number;
21
+ /**
22
+ * Reads a response body, enforcing a max size in megabytes.
23
+ *
24
+ * @param response - Fetch response to read.
25
+ * @param maxResponseSizeMb - Maximum body size in MB; 0 uses the hard cap only.
26
+ */
27
+ read(response: Response, maxResponseSizeMb: number): Promise<ReadResponseBodyResult>;
28
+ }
29
+ //# sourceMappingURL=IResponseReader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IResponseReader.d.ts","sourceRoot":"","sources":["../src/IResponseReader.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAChC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,wBAAwB,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAAC;IAE5D;;;;;OAKG;IACH,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACtF"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,40 @@
1
+ import type { KeyValue } from './types.js';
2
+ import type { IQueryString } from './IQueryString.js';
3
+ /**
4
+ * Builds and validates URLs and query strings for outbound requests.
5
+ */
6
+ export declare class QueryString implements IQueryString {
7
+ /**
8
+ * HTTP schemes allowed for outbound requests.
9
+ */
10
+ private static readonly ALLOWED_PROTOCOLS;
11
+ /**
12
+ * Returns whether a URL string is a root-relative path (`/api`), not protocol-relative (`//cdn`).
13
+ *
14
+ * @param url - Trimmed URL string.
15
+ */
16
+ private isRootRelativePath;
17
+ /**
18
+ * Appends query parameters via string concatenation for root-relative paths.
19
+ *
20
+ * @param trimmed - Trimmed base URL that failed absolute URL parsing.
21
+ * @param enabledParams - Enabled key-value pairs to append.
22
+ */
23
+ private appendQueryFallback;
24
+ /**
25
+ * Appends enabled query parameters to a base URL.
26
+ *
27
+ * @param baseUrl - Request URL before query string merging.
28
+ * @param params - Key-value pairs to append as search params.
29
+ * @returns URL with merged query parameters.
30
+ */
31
+ buildUrl(baseUrl: string, params: KeyValue[]): string;
32
+ /**
33
+ * Returns whether a URL is safe to send via fetch: absolute http(s) or root-relative path.
34
+ *
35
+ * @param url - Request URL before or after query string merging.
36
+ * @returns True when the URL uses http/https or is a root-relative path.
37
+ */
38
+ isValidRequestUrl(url: string): boolean;
39
+ }
40
+ //# sourceMappingURL=QueryString.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QueryString.d.ts","sourceRoot":"","sources":["../src/QueryString.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;GAEG;AACH,qBAAa,WAAY,YAAW,YAAY;IAC9C;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAgC;IAEzE;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAI1B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAQ3B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM;IAwBrD;;;;;OAKG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;CAUxC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Builds and validates URLs and query strings for outbound requests.
3
+ */
4
+ export class QueryString {
5
+ /**
6
+ * HTTP schemes allowed for outbound requests.
7
+ */
8
+ static ALLOWED_PROTOCOLS = new Set(['http:', 'https:']);
9
+ /**
10
+ * Returns whether a URL string is a root-relative path (`/api`), not protocol-relative (`//cdn`).
11
+ *
12
+ * @param url - Trimmed URL string.
13
+ */
14
+ isRootRelativePath(url) {
15
+ return url.startsWith('/') && !url.startsWith('//');
16
+ }
17
+ /**
18
+ * Appends query parameters via string concatenation for root-relative paths.
19
+ *
20
+ * @param trimmed - Trimmed base URL that failed absolute URL parsing.
21
+ * @param enabledParams - Enabled key-value pairs to append.
22
+ */
23
+ appendQueryFallback(trimmed, enabledParams) {
24
+ const separator = trimmed.includes('?') ? '&' : '?';
25
+ const query = enabledParams
26
+ .map((p) => `${encodeURIComponent(p.key.trim())}=${encodeURIComponent(p.value)}`)
27
+ .join('&');
28
+ return `${trimmed}${separator}${query}`;
29
+ }
30
+ /**
31
+ * Appends enabled query parameters to a base URL.
32
+ *
33
+ * @param baseUrl - Request URL before query string merging.
34
+ * @param params - Key-value pairs to append as search params.
35
+ * @returns URL with merged query parameters.
36
+ */
37
+ buildUrl(baseUrl, params) {
38
+ const trimmed = baseUrl.trim();
39
+ if (!trimmed)
40
+ return trimmed;
41
+ const enabledParams = params.filter((p) => p.enabled && p.key.trim());
42
+ if (enabledParams.length === 0)
43
+ return trimmed;
44
+ try {
45
+ const url = new URL(trimmed);
46
+ if (!QueryString.ALLOWED_PROTOCOLS.has(url.protocol)) {
47
+ return trimmed;
48
+ }
49
+ for (const param of enabledParams) {
50
+ url.searchParams.set(param.key.trim(), param.value);
51
+ }
52
+ return url.toString();
53
+ }
54
+ catch {
55
+ if (!this.isRootRelativePath(trimmed)) {
56
+ return trimmed;
57
+ }
58
+ return this.appendQueryFallback(trimmed, enabledParams);
59
+ }
60
+ }
61
+ /**
62
+ * Returns whether a URL is safe to send via fetch: absolute http(s) or root-relative path.
63
+ *
64
+ * @param url - Request URL before or after query string merging.
65
+ * @returns True when the URL uses http/https or is a root-relative path.
66
+ */
67
+ isValidRequestUrl(url) {
68
+ const trimmed = url.trim();
69
+ if (!trimmed)
70
+ return false;
71
+ try {
72
+ return QueryString.ALLOWED_PROTOCOLS.has(new URL(trimmed).protocol);
73
+ }
74
+ catch {
75
+ return this.isRootRelativePath(trimmed);
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,100 @@
1
+ import type { SendRequestInput, SendResult } from './types.js';
2
+ import { type RequestSettings } from './settings.js';
3
+ import type { IBody } from './IBody.js';
4
+ import type { IHeaders } from './IHeaders.js';
5
+ import type { IQueryString } from './IQueryString.js';
6
+ import type { IRequester } from './IRequester.js';
7
+ import type { IResponseReader } from './IResponseReader.js';
8
+ /** HTTP status codes treated as redirects when following manually. */
9
+ export declare const REDIRECT_STATUSES: Set<number>;
10
+ /** Maximum redirect hops before returning an error. */
11
+ export declare const MAX_REDIRECTS = 20;
12
+ /**
13
+ * Optional collaborators injected into {@link IRequester} implementations.
14
+ */
15
+ export interface RequesterDeps {
16
+ queryString?: IQueryString;
17
+ headers?: IHeaders;
18
+ body?: IBody;
19
+ responseReader?: IResponseReader;
20
+ }
21
+ /**
22
+ * Executes outbound HTTP requests via fetch with configurable collaborators.
23
+ */
24
+ export declare class Requester implements IRequester {
25
+ private readonly queryString;
26
+ private readonly headers;
27
+ private readonly body;
28
+ private readonly responseReader;
29
+ /**
30
+ * Creates a requester with optional collaborators defaulting to the standard implementations.
31
+ *
32
+ * @param deps - Optional query string, header, body, and response reader implementations.
33
+ */
34
+ constructor(deps?: RequesterDeps);
35
+ /**
36
+ * Combines optional cancel and timeout signals for fetch.
37
+ *
38
+ * @param signal - Optional user cancel signal.
39
+ * @param timeoutMs - Request timeout in milliseconds; 0 disables timeout.
40
+ */
41
+ private buildEffectiveSignal;
42
+ /**
43
+ * Maps fetch errors to user-facing messages.
44
+ *
45
+ * @param err - Thrown fetch error.
46
+ * @param timeoutMs - Configured request timeout in milliseconds.
47
+ */
48
+ private mapFetchError;
49
+ /**
50
+ * Returns a shared undici Agent that skips TLS certificate verification.
51
+ */
52
+ private getInsecureDispatcher;
53
+ /**
54
+ * Builds a failed {@link SendResult} with consistent error fields.
55
+ *
56
+ * @param error - User-facing error message.
57
+ * @param request - Sent request metadata captured before or during the attempt.
58
+ * @param timeMs - Elapsed time in milliseconds.
59
+ * @param responseHeaders - Optional response headers when available before failure.
60
+ * @param setCookieHeaders - Optional Set-Cookie headers from the response.
61
+ */
62
+ private errorResult;
63
+ /**
64
+ * Logs the outbound request when very-verbose mode is enabled.
65
+ *
66
+ * Records the HTTP verb, resolved URL, request headers, body type, and request
67
+ * body. Response headers and response bodies are intentionally omitted.
68
+ *
69
+ * @param request - Final request metadata sent to fetch.
70
+ */
71
+ private logOutgoingRequest;
72
+ /**
73
+ * Builds the fetch request body for one hop.
74
+ *
75
+ * @param input - Original send input (body source).
76
+ * @param bodyType - Body type for this hop.
77
+ * @param shouldSendBody - Whether a body should be attached.
78
+ */
79
+ private buildRequestBody;
80
+ /**
81
+ * Resolves the fetch dispatcher from general settings.
82
+ *
83
+ * @param settings - General request settings.
84
+ */
85
+ private resolveDispatcher;
86
+ /**
87
+ * Executes an HTTP request via fetch and returns timing and response metadata.
88
+ *
89
+ * When redirect following is enabled, 3xx responses are followed manually so
90
+ * each hop can be recorded in {@link SendResult.redirects}.
91
+ *
92
+ * @param input - Method, URL, headers, params, body, and body type.
93
+ * @param settings - General request settings for timeout, size limits, SSL verification, and redirect following.
94
+ * @param signal - Optional abort signal to cancel the in-flight request.
95
+ * @param cookieHeader - Optional Cookie header value from the cookie jar.
96
+ * @returns Response status, headers, body, timing, size, and optional redirect chain; error field on failure.
97
+ */
98
+ executeRequest(input: SendRequestInput, settings?: RequestSettings, signal?: AbortSignal, cookieHeader?: string): Promise<SendResult>;
99
+ }
100
+ //# sourceMappingURL=Requester.d.ts.map