@afoures/http-client 0.2.0 → 0.3.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/dist/lib/endpoint.d.mts +1 -1
- package/dist/lib/endpoint.mjs +105 -54
- package/dist/lib/errors.d.mts +57 -33
- package/dist/lib/errors.mjs +22 -24
- package/dist/lib/http-client.mjs +133 -13
- package/dist/lib/types.d.mts +2 -0
- package/package.json +5 -5
package/dist/lib/endpoint.d.mts
CHANGED
|
@@ -33,7 +33,7 @@ declare class Endpoint<http_method extends HTTPMethod.Any, pathname extends Path
|
|
|
33
33
|
body: BodyInit | null;
|
|
34
34
|
content_type?: string;
|
|
35
35
|
} | SerializationError>;
|
|
36
|
-
parse_response(
|
|
36
|
+
parse_response(raw_response: Response): Promise<HTTPFetch.ClientErrorResponse<Schema.infer_output<error_schema, string>> | HTTPFetch.ServerErrorResponse<Schema.infer_output<error_schema, string>> | HTTPFetch.SuccessfulResponse<Schema.infer_output<data_schema, void>> | HTTPFetch.RedirectMessage | ParseError>;
|
|
37
37
|
}
|
|
38
38
|
type AnyEndpoint = Endpoint<any, any, any, any, any, any, any>;
|
|
39
39
|
//#endregion
|
package/dist/lib/endpoint.mjs
CHANGED
|
@@ -3,6 +3,77 @@ import "./types.mjs";
|
|
|
3
3
|
import { RoutePattern } from "@remix-run/route-pattern";
|
|
4
4
|
|
|
5
5
|
//#region src/lib/endpoint.ts
|
|
6
|
+
const RESPONSE = {
|
|
7
|
+
success(method, data, raw_response) {
|
|
8
|
+
const response = {
|
|
9
|
+
ok: true,
|
|
10
|
+
method,
|
|
11
|
+
url: raw_response.url,
|
|
12
|
+
status: raw_response.status,
|
|
13
|
+
data,
|
|
14
|
+
headers: raw_response.headers,
|
|
15
|
+
raw_response
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(response, "raw_response", {
|
|
18
|
+
enumerable: false,
|
|
19
|
+
writable: false,
|
|
20
|
+
configurable: false
|
|
21
|
+
});
|
|
22
|
+
return response;
|
|
23
|
+
},
|
|
24
|
+
redirect(method, raw_response) {
|
|
25
|
+
const redirect_to = raw_response.headers.get("Location") || null;
|
|
26
|
+
const response = {
|
|
27
|
+
ok: false,
|
|
28
|
+
method,
|
|
29
|
+
url: raw_response.url,
|
|
30
|
+
status: raw_response.status,
|
|
31
|
+
redirect_to,
|
|
32
|
+
headers: raw_response.headers,
|
|
33
|
+
raw_response
|
|
34
|
+
};
|
|
35
|
+
Object.defineProperty(response, "raw_response", {
|
|
36
|
+
enumerable: false,
|
|
37
|
+
writable: false,
|
|
38
|
+
configurable: false
|
|
39
|
+
});
|
|
40
|
+
return response;
|
|
41
|
+
},
|
|
42
|
+
client_error(method, error, raw_response) {
|
|
43
|
+
const response = {
|
|
44
|
+
ok: false,
|
|
45
|
+
method,
|
|
46
|
+
url: raw_response.url,
|
|
47
|
+
status: raw_response.status,
|
|
48
|
+
error,
|
|
49
|
+
headers: raw_response.headers,
|
|
50
|
+
raw_response
|
|
51
|
+
};
|
|
52
|
+
Object.defineProperty(response, "raw_response", {
|
|
53
|
+
enumerable: false,
|
|
54
|
+
writable: false,
|
|
55
|
+
configurable: false
|
|
56
|
+
});
|
|
57
|
+
return response;
|
|
58
|
+
},
|
|
59
|
+
server_error(method, error, raw_response) {
|
|
60
|
+
const response = {
|
|
61
|
+
ok: false,
|
|
62
|
+
method,
|
|
63
|
+
url: raw_response.url,
|
|
64
|
+
status: raw_response.status,
|
|
65
|
+
error,
|
|
66
|
+
headers: raw_response.headers,
|
|
67
|
+
raw_response
|
|
68
|
+
};
|
|
69
|
+
Object.defineProperty(response, "raw_response", {
|
|
70
|
+
enumerable: false,
|
|
71
|
+
writable: false,
|
|
72
|
+
configurable: false
|
|
73
|
+
});
|
|
74
|
+
return response;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
6
77
|
var Endpoint = class {
|
|
7
78
|
#method;
|
|
8
79
|
#pattern;
|
|
@@ -35,7 +106,8 @@ var Endpoint = class {
|
|
|
35
106
|
const result = await this.#serializers.params.schema["~standard"].validate(init.params);
|
|
36
107
|
if (result.issues !== void 0) return new SerializationError("Params serialization failed", {
|
|
37
108
|
operation: "generate_url",
|
|
38
|
-
cause: result.issues
|
|
109
|
+
cause: result.issues,
|
|
110
|
+
input: { params: init.params }
|
|
39
111
|
});
|
|
40
112
|
const transformed_params = result.value;
|
|
41
113
|
if (this.#serializers.params.serialize) pathname_params = this.#serializers.params.serialize(transformed_params);
|
|
@@ -47,7 +119,8 @@ var Endpoint = class {
|
|
|
47
119
|
const result = await this.#serializers.query.schema["~standard"].validate(init.query);
|
|
48
120
|
if (result.issues !== void 0) return new SerializationError("Query serialization failed", {
|
|
49
121
|
cause: result.issues,
|
|
50
|
-
operation: "generate_url"
|
|
122
|
+
operation: "generate_url",
|
|
123
|
+
input: { query: init.query }
|
|
51
124
|
});
|
|
52
125
|
const transformed_query = result.value;
|
|
53
126
|
if (typeof this.#serializers.query.serialize === "function") search_params = this.#serializers.query.serialize(transformed_query);
|
|
@@ -80,7 +153,8 @@ var Endpoint = class {
|
|
|
80
153
|
const result = await this.#serializers.body.schema["~standard"].validate(init.body);
|
|
81
154
|
if (result.issues !== void 0) return new SerializationError("Body serialization failed", {
|
|
82
155
|
operation: "serialize_body",
|
|
83
|
-
cause: result.issues
|
|
156
|
+
cause: result.issues,
|
|
157
|
+
input: { body: init.body }
|
|
84
158
|
});
|
|
85
159
|
const transformed_content = result.value;
|
|
86
160
|
if (typeof this.#serializers.body.serialize === "function") return this.#serializers.body.serialize(transformed_content);
|
|
@@ -89,74 +163,51 @@ var Endpoint = class {
|
|
|
89
163
|
content_type: "application/json"
|
|
90
164
|
};
|
|
91
165
|
}
|
|
92
|
-
async parse_response(
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const headers = cloned_response.headers;
|
|
97
|
-
if (status >= 300 && status < 400) return {
|
|
98
|
-
ok: false,
|
|
99
|
-
status,
|
|
100
|
-
redirect_to: headers.get("Location") || null,
|
|
101
|
-
headers,
|
|
102
|
-
raw_response
|
|
103
|
-
};
|
|
104
|
-
if (status >= 400 && status < 600) {
|
|
166
|
+
async parse_response(raw_response) {
|
|
167
|
+
const response = raw_response.clone();
|
|
168
|
+
if (raw_response.status >= 300 && raw_response.status < 400) return RESPONSE.redirect(this.#method, raw_response);
|
|
169
|
+
if (raw_response.status >= 400 && raw_response.status < 600) {
|
|
105
170
|
let error;
|
|
106
171
|
if (this.#parsers.error) {
|
|
107
172
|
const parser = this.#parsers.error;
|
|
108
173
|
let parsed;
|
|
109
|
-
if (typeof parser.parse === "function") parsed = await parser.parse(
|
|
110
|
-
else if (parser.parse === "json") parsed = await parse_as_json(
|
|
111
|
-
else if (parser.parse === "text") parsed = await
|
|
174
|
+
if (typeof parser.parse === "function") parsed = await parser.parse(response.body);
|
|
175
|
+
else if (parser.parse === "json") parsed = await parse_as_json(response);
|
|
176
|
+
else if (parser.parse === "text") parsed = await response.text();
|
|
112
177
|
const result = await parser.schema["~standard"].validate(parsed);
|
|
113
178
|
if (result.issues !== void 0) return new ParseError("Error parsing failed", {
|
|
114
179
|
cause: result.issues,
|
|
115
|
-
operation: "parse_response"
|
|
180
|
+
operation: "parse_response",
|
|
181
|
+
response: {
|
|
182
|
+
status: raw_response.status,
|
|
183
|
+
headers: raw_response.headers,
|
|
184
|
+
body: parsed
|
|
185
|
+
}
|
|
116
186
|
});
|
|
117
187
|
error = result.value;
|
|
118
|
-
} else error = await
|
|
119
|
-
return
|
|
120
|
-
ok: false,
|
|
121
|
-
status,
|
|
122
|
-
error,
|
|
123
|
-
headers,
|
|
124
|
-
raw_response
|
|
125
|
-
};
|
|
188
|
+
} else error = await response.text();
|
|
189
|
+
return raw_response.status >= 400 && raw_response.status < 500 ? RESPONSE.client_error(this.#method, error, raw_response) : RESPONSE.server_error(this.#method, error, raw_response);
|
|
126
190
|
}
|
|
127
|
-
if (status >= 200 && status < 300) {
|
|
128
|
-
if (status === 204) return
|
|
129
|
-
ok: true,
|
|
130
|
-
status: 204,
|
|
131
|
-
data: null,
|
|
132
|
-
headers,
|
|
133
|
-
raw_response
|
|
134
|
-
};
|
|
191
|
+
if (raw_response.status >= 200 && raw_response.status < 300) {
|
|
192
|
+
if (raw_response.status === 204) return RESPONSE.success(this.#method, null, raw_response);
|
|
135
193
|
if (this.#parsers.data) {
|
|
136
194
|
const parser = this.#parsers.data;
|
|
137
195
|
let parsed;
|
|
138
|
-
if (typeof parser.parse === "function") parsed = await parser.parse(
|
|
139
|
-
else if (parser.parse === "json") parsed = await parse_as_json(
|
|
140
|
-
else if (parser.parse === "text") parsed = await
|
|
196
|
+
if (typeof parser.parse === "function") parsed = await parser.parse(response.body);
|
|
197
|
+
else if (parser.parse === "json") parsed = await parse_as_json(response);
|
|
198
|
+
else if (parser.parse === "text") parsed = await response.text();
|
|
141
199
|
const result = await parser.schema["~standard"].validate(parsed);
|
|
142
200
|
if (result.issues !== void 0) return new ParseError("Response parsing failed", {
|
|
143
201
|
cause: result.issues,
|
|
144
|
-
operation: "parse_response"
|
|
202
|
+
operation: "parse_response",
|
|
203
|
+
response: {
|
|
204
|
+
status: raw_response.status,
|
|
205
|
+
headers: raw_response.headers,
|
|
206
|
+
body: parsed
|
|
207
|
+
}
|
|
145
208
|
});
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
status,
|
|
149
|
-
data: result.value,
|
|
150
|
-
headers,
|
|
151
|
-
raw_response
|
|
152
|
-
};
|
|
153
|
-
} else return {
|
|
154
|
-
ok: true,
|
|
155
|
-
status,
|
|
156
|
-
data: null,
|
|
157
|
-
headers,
|
|
158
|
-
raw_response
|
|
159
|
-
};
|
|
209
|
+
return RESPONSE.success(this.#method, result.value, raw_response);
|
|
210
|
+
} else return RESPONSE.success(this.#method, null, raw_response);
|
|
160
211
|
}
|
|
161
212
|
throw new Error(`Unhandled status code: ${status}`);
|
|
162
213
|
}
|
package/dist/lib/errors.d.mts
CHANGED
|
@@ -1,54 +1,78 @@
|
|
|
1
1
|
//#region src/lib/errors.d.ts
|
|
2
|
-
type
|
|
3
|
-
|
|
2
|
+
type RequestContext = {
|
|
3
|
+
url: string;
|
|
4
|
+
method: string;
|
|
5
|
+
pathname?: string;
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
headers?: Headers;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
};
|
|
10
|
+
type ResponseContext = {
|
|
11
|
+
status: number;
|
|
12
|
+
statusText?: string;
|
|
13
|
+
headers?: Headers;
|
|
14
|
+
body?: unknown;
|
|
15
|
+
};
|
|
16
|
+
type TimingContext = {
|
|
17
|
+
startTime?: number;
|
|
18
|
+
duration?: number;
|
|
19
|
+
attempt?: number;
|
|
20
|
+
maxAttempts?: number;
|
|
4
21
|
};
|
|
5
|
-
type
|
|
6
|
-
|
|
22
|
+
type InputContext = {
|
|
23
|
+
params?: unknown;
|
|
24
|
+
query?: unknown;
|
|
25
|
+
body?: unknown;
|
|
26
|
+
};
|
|
27
|
+
type ErrorContext = {
|
|
28
|
+
operation: string;
|
|
29
|
+
request?: RequestContext;
|
|
30
|
+
response?: ResponseContext;
|
|
31
|
+
timing?: TimingContext;
|
|
32
|
+
input?: InputContext;
|
|
7
33
|
};
|
|
8
34
|
declare class HttpClientError extends Error {
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
declare class TimeoutError extends HttpClientError {
|
|
12
|
-
readonly context: BaseContext;
|
|
35
|
+
readonly context: ErrorContext;
|
|
13
36
|
constructor(message: string, {
|
|
14
37
|
cause,
|
|
15
|
-
...
|
|
16
|
-
}:
|
|
38
|
+
...options
|
|
39
|
+
}: {
|
|
40
|
+
cause?: unknown;
|
|
41
|
+
} & Partial<ErrorContext>);
|
|
42
|
+
}
|
|
43
|
+
declare class TimeoutError extends HttpClientError {
|
|
44
|
+
constructor(message: string, options: {
|
|
45
|
+
cause?: unknown;
|
|
46
|
+
} & Partial<ErrorContext>);
|
|
17
47
|
}
|
|
18
48
|
declare class AbortedError extends HttpClientError {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
...context
|
|
23
|
-
}: ErrorCause & BaseContext);
|
|
49
|
+
constructor(message: string, options: {
|
|
50
|
+
cause?: unknown;
|
|
51
|
+
} & Partial<ErrorContext>);
|
|
24
52
|
}
|
|
25
53
|
declare class SerializationError extends HttpClientError {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
...context
|
|
30
|
-
}: ErrorCause & BaseContext);
|
|
54
|
+
constructor(message: string, options: {
|
|
55
|
+
cause?: unknown;
|
|
56
|
+
} & Partial<ErrorContext>);
|
|
31
57
|
}
|
|
32
58
|
declare class ParseError extends HttpClientError {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
...context
|
|
37
|
-
}: ErrorCause & BaseContext);
|
|
59
|
+
constructor(message: string, options: {
|
|
60
|
+
cause?: unknown;
|
|
61
|
+
} & Partial<ErrorContext>);
|
|
38
62
|
}
|
|
39
63
|
declare class NetworkError extends HttpClientError {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
...context
|
|
44
|
-
}: ErrorCause & BaseContext);
|
|
64
|
+
constructor(message: string, options: {
|
|
65
|
+
cause?: unknown;
|
|
66
|
+
} & Partial<ErrorContext>);
|
|
45
67
|
}
|
|
46
68
|
declare class UnexpectedError extends Error {
|
|
47
|
-
readonly context:
|
|
69
|
+
readonly context: ErrorContext;
|
|
48
70
|
constructor(message: string, {
|
|
49
71
|
cause,
|
|
50
|
-
...
|
|
51
|
-
}:
|
|
72
|
+
...options
|
|
73
|
+
}: {
|
|
74
|
+
cause?: unknown;
|
|
75
|
+
} & Partial<ErrorContext>);
|
|
52
76
|
}
|
|
53
77
|
//#endregion
|
|
54
78
|
export { AbortedError, HttpClientError, NetworkError, ParseError, SerializationError, TimeoutError, UnexpectedError };
|
package/dist/lib/errors.mjs
CHANGED
|
@@ -1,56 +1,54 @@
|
|
|
1
1
|
//#region src/lib/errors.ts
|
|
2
2
|
var HttpClientError = class extends Error {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
context;
|
|
4
|
+
constructor(message, { cause, ...options }) {
|
|
5
|
+
super(message, { cause });
|
|
5
6
|
this.name = "HttpClientError";
|
|
7
|
+
this.context = {
|
|
8
|
+
...options,
|
|
9
|
+
operation: options.operation ?? "unknown"
|
|
10
|
+
};
|
|
6
11
|
}
|
|
7
12
|
};
|
|
8
13
|
var TimeoutError = class extends HttpClientError {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
super(message, { cause });
|
|
14
|
+
constructor(message, options) {
|
|
15
|
+
super(message, options);
|
|
12
16
|
this.name = "TimeoutError";
|
|
13
|
-
this.context = context;
|
|
14
17
|
}
|
|
15
18
|
};
|
|
16
19
|
var AbortedError = class extends HttpClientError {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
super(message, { cause });
|
|
20
|
+
constructor(message, options) {
|
|
21
|
+
super(message, options);
|
|
20
22
|
this.name = "AbortedError";
|
|
21
|
-
this.context = context;
|
|
22
23
|
}
|
|
23
24
|
};
|
|
24
25
|
var SerializationError = class extends HttpClientError {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
super(message, { cause });
|
|
26
|
+
constructor(message, options) {
|
|
27
|
+
super(message, options);
|
|
28
28
|
this.name = "SerializationError";
|
|
29
|
-
this.context = context;
|
|
30
29
|
}
|
|
31
30
|
};
|
|
32
31
|
var ParseError = class extends HttpClientError {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
super(message, { cause });
|
|
32
|
+
constructor(message, options) {
|
|
33
|
+
super(message, options);
|
|
36
34
|
this.name = "ParseError";
|
|
37
|
-
this.context = context;
|
|
38
35
|
}
|
|
39
36
|
};
|
|
40
37
|
var NetworkError = class extends HttpClientError {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
super(message, { cause });
|
|
38
|
+
constructor(message, options) {
|
|
39
|
+
super(message, options);
|
|
44
40
|
this.name = "NetworkError";
|
|
45
|
-
this.context = context;
|
|
46
41
|
}
|
|
47
42
|
};
|
|
48
43
|
var UnexpectedError = class extends Error {
|
|
49
44
|
context;
|
|
50
|
-
constructor(message, { cause, ...
|
|
45
|
+
constructor(message, { cause, ...options }) {
|
|
51
46
|
super(message, { cause });
|
|
52
47
|
this.name = "UnexpectedError";
|
|
53
|
-
this.context =
|
|
48
|
+
this.context = {
|
|
49
|
+
...options,
|
|
50
|
+
operation: options.operation ?? "unknown"
|
|
51
|
+
};
|
|
54
52
|
}
|
|
55
53
|
};
|
|
56
54
|
|
package/dist/lib/http-client.mjs
CHANGED
|
@@ -6,7 +6,16 @@ import { extract_args, merge_options, remove_custom_options, sleep } from "./uti
|
|
|
6
6
|
//#region src/lib/http-client.ts
|
|
7
7
|
function fetch_endpoint_factory({ base_url, endpoint, custom_fetch, get_default_options = () => ({}), hooks = {} }) {
|
|
8
8
|
async function fetch_endpoint(input) {
|
|
9
|
-
|
|
9
|
+
let start_time = Date.now();
|
|
10
|
+
if (!URL.canParse(base_url)) return new UnexpectedError(`Invalid base_url: ${base_url}`, {
|
|
11
|
+
operation: "base_url_validation",
|
|
12
|
+
request: {
|
|
13
|
+
url: base_url,
|
|
14
|
+
method: endpoint.method,
|
|
15
|
+
baseUrl: base_url
|
|
16
|
+
},
|
|
17
|
+
timing: { startTime: start_time }
|
|
18
|
+
});
|
|
10
19
|
const { args, options } = extract_args(input);
|
|
11
20
|
const { headers, ...merged_options } = merge_options(await get_default_options(), endpoint.options, options);
|
|
12
21
|
const url = await endpoint.generate_url({
|
|
@@ -15,12 +24,29 @@ function fetch_endpoint_factory({ base_url, endpoint, custom_fetch, get_default_
|
|
|
15
24
|
query: args.query
|
|
16
25
|
}).catch((error) => new UnexpectedError("Failed to generate URL", {
|
|
17
26
|
cause: error,
|
|
18
|
-
operation: "generate_url"
|
|
27
|
+
operation: "generate_url",
|
|
28
|
+
request: {
|
|
29
|
+
url: base_url,
|
|
30
|
+
method: endpoint.method,
|
|
31
|
+
baseUrl: base_url
|
|
32
|
+
},
|
|
33
|
+
input: {
|
|
34
|
+
params: args.params,
|
|
35
|
+
query: args.query
|
|
36
|
+
},
|
|
37
|
+
timing: { startTime: start_time }
|
|
19
38
|
}));
|
|
20
39
|
if (url instanceof Error) return url;
|
|
21
40
|
const serialized = await endpoint.serialize_body({ body: args.body }).catch((error) => new UnexpectedError("Failed to serialize body", {
|
|
22
41
|
cause: error,
|
|
23
|
-
operation: "serialize_body"
|
|
42
|
+
operation: "serialize_body",
|
|
43
|
+
request: {
|
|
44
|
+
url: url instanceof URL ? url.toString() : base_url,
|
|
45
|
+
method: endpoint.method,
|
|
46
|
+
baseUrl: base_url
|
|
47
|
+
},
|
|
48
|
+
input: { body: args.body },
|
|
49
|
+
timing: { startTime: start_time }
|
|
24
50
|
}));
|
|
25
51
|
if (serialized instanceof Error) return serialized;
|
|
26
52
|
headers.delete("Content-Type");
|
|
@@ -50,7 +76,18 @@ function fetch_endpoint_factory({ base_url, endpoint, custom_fetch, get_default_
|
|
|
50
76
|
} catch (local_error) {
|
|
51
77
|
error = new UnexpectedError("Failed to create request", {
|
|
52
78
|
cause: local_error,
|
|
53
|
-
operation: "create_request"
|
|
79
|
+
operation: "create_request",
|
|
80
|
+
request: {
|
|
81
|
+
url: url instanceof URL ? url.toString() : base_url,
|
|
82
|
+
method: endpoint.method,
|
|
83
|
+
headers,
|
|
84
|
+
timeout: options.timeout,
|
|
85
|
+
baseUrl: base_url
|
|
86
|
+
},
|
|
87
|
+
timing: {
|
|
88
|
+
startTime: start_time,
|
|
89
|
+
attempt: 1
|
|
90
|
+
}
|
|
54
91
|
});
|
|
55
92
|
break;
|
|
56
93
|
}
|
|
@@ -60,17 +97,48 @@ function fetch_endpoint_factory({ base_url, endpoint, custom_fetch, get_default_
|
|
|
60
97
|
response = await custom_fetch(request);
|
|
61
98
|
error = void 0;
|
|
62
99
|
} catch (local_error) {
|
|
100
|
+
const duration = Date.now() - start_time;
|
|
63
101
|
if (local_error instanceof Error && local_error.name === "TimeoutError") error = new TimeoutError(local_error.message, {
|
|
64
102
|
cause: local_error,
|
|
65
|
-
operation: "fetch"
|
|
103
|
+
operation: "fetch",
|
|
104
|
+
request: {
|
|
105
|
+
url: request.url,
|
|
106
|
+
method: request.method,
|
|
107
|
+
timeout: options.timeout
|
|
108
|
+
},
|
|
109
|
+
timing: {
|
|
110
|
+
startTime: start_time,
|
|
111
|
+
duration,
|
|
112
|
+
attempt
|
|
113
|
+
}
|
|
66
114
|
});
|
|
67
115
|
else if (local_error instanceof Error && local_error.name === "AbortError") error = new AbortedError(local_error.message, {
|
|
68
116
|
cause: local_error,
|
|
69
|
-
operation: "fetch"
|
|
117
|
+
operation: "fetch",
|
|
118
|
+
request: {
|
|
119
|
+
url: request.url,
|
|
120
|
+
method: request.method,
|
|
121
|
+
timeout: options.timeout
|
|
122
|
+
},
|
|
123
|
+
timing: {
|
|
124
|
+
startTime: start_time,
|
|
125
|
+
duration,
|
|
126
|
+
attempt
|
|
127
|
+
}
|
|
70
128
|
});
|
|
71
129
|
else error = new NetworkError("Network error", {
|
|
72
130
|
cause: local_error,
|
|
73
|
-
operation: "fetch"
|
|
131
|
+
operation: "fetch",
|
|
132
|
+
request: {
|
|
133
|
+
url: request.url,
|
|
134
|
+
method: request.method,
|
|
135
|
+
timeout: options.timeout
|
|
136
|
+
},
|
|
137
|
+
timing: {
|
|
138
|
+
startTime: start_time,
|
|
139
|
+
duration,
|
|
140
|
+
attempt
|
|
141
|
+
}
|
|
74
142
|
});
|
|
75
143
|
}
|
|
76
144
|
try {
|
|
@@ -91,25 +159,77 @@ function fetch_endpoint_factory({ base_url, endpoint, custom_fetch, get_default_
|
|
|
91
159
|
} catch (local_error) {
|
|
92
160
|
error = new UnexpectedError("Failed to check retry policy", {
|
|
93
161
|
cause: local_error,
|
|
94
|
-
operation: "retry_policy"
|
|
162
|
+
operation: "retry_policy",
|
|
163
|
+
request: {
|
|
164
|
+
url: url instanceof URL ? url.toString() : base_url,
|
|
165
|
+
method: endpoint.method,
|
|
166
|
+
timeout: options.timeout,
|
|
167
|
+
baseUrl: base_url
|
|
168
|
+
},
|
|
169
|
+
timing: {
|
|
170
|
+
startTime: start_time,
|
|
171
|
+
attempt,
|
|
172
|
+
maxAttempts: typeof retry_policy.attempts === "function" ? void 0 : retry_policy.attempts
|
|
173
|
+
}
|
|
95
174
|
});
|
|
96
175
|
break;
|
|
97
176
|
}
|
|
98
177
|
} while (true);
|
|
99
178
|
if (error) return error;
|
|
100
|
-
if (!response) return new UnexpectedError("", {
|
|
179
|
+
if (!response) return new UnexpectedError("No response received", {
|
|
101
180
|
cause: "No response received",
|
|
102
|
-
operation: "parse_response"
|
|
181
|
+
operation: "parse_response",
|
|
182
|
+
request: {
|
|
183
|
+
url: url instanceof URL ? url.toString() : base_url,
|
|
184
|
+
method: endpoint.method,
|
|
185
|
+
timeout: options.timeout,
|
|
186
|
+
baseUrl: base_url
|
|
187
|
+
},
|
|
188
|
+
timing: {
|
|
189
|
+
startTime: start_time,
|
|
190
|
+
attempt
|
|
191
|
+
}
|
|
103
192
|
});
|
|
104
193
|
hooks.on_response?.(response);
|
|
105
|
-
return await endpoint.parse_response(response).catch((error) => {
|
|
194
|
+
return await endpoint.parse_response(response).catch(async (error) => {
|
|
195
|
+
const response_body = await response.clone().text().catch(() => void 0);
|
|
106
196
|
if (error instanceof Error && error.name === "AbortError") return new AbortedError(error.message, {
|
|
107
197
|
cause: error,
|
|
108
|
-
operation: "parse_response"
|
|
198
|
+
operation: "parse_response",
|
|
199
|
+
request: {
|
|
200
|
+
url: response.url,
|
|
201
|
+
method: request?.method
|
|
202
|
+
},
|
|
203
|
+
response: {
|
|
204
|
+
status: response.status,
|
|
205
|
+
statusText: response.statusText,
|
|
206
|
+
headers: response.headers,
|
|
207
|
+
body: response_body
|
|
208
|
+
},
|
|
209
|
+
timing: {
|
|
210
|
+
startTime: start_time,
|
|
211
|
+
attempt
|
|
212
|
+
}
|
|
109
213
|
});
|
|
110
214
|
return new UnexpectedError("Failed to parse response", {
|
|
111
215
|
cause: error,
|
|
112
|
-
operation: "parse_response"
|
|
216
|
+
operation: "parse_response",
|
|
217
|
+
request: {
|
|
218
|
+
url: response.url,
|
|
219
|
+
method: request?.method,
|
|
220
|
+
timeout: options.timeout,
|
|
221
|
+
baseUrl: base_url
|
|
222
|
+
},
|
|
223
|
+
response: {
|
|
224
|
+
status: response.status,
|
|
225
|
+
statusText: response.statusText,
|
|
226
|
+
headers: response.headers,
|
|
227
|
+
body: response_body
|
|
228
|
+
},
|
|
229
|
+
timing: {
|
|
230
|
+
startTime: start_time,
|
|
231
|
+
attempt
|
|
232
|
+
}
|
|
113
233
|
});
|
|
114
234
|
});
|
|
115
235
|
}
|
package/dist/lib/types.d.mts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@afoures/http-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A typesafe and robust HTTP client",
|
|
5
5
|
"homepage": "https://github.com/afoures/http-client#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"@standard-schema/spec": "^1.1.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@afoures/auto-release": "^0.
|
|
38
|
+
"@afoures/auto-release": "^0.5.0",
|
|
39
39
|
"@arktype/attest": "^0.56.0",
|
|
40
|
-
"@types/node": "^24.12.
|
|
41
|
-
"msw": "^2.
|
|
40
|
+
"@types/node": "^24.12.2",
|
|
41
|
+
"msw": "^2.13.2",
|
|
42
42
|
"oxfmt": "^0.36.0",
|
|
43
|
-
"oxlint": "^1.
|
|
43
|
+
"oxlint": "^1.59.0",
|
|
44
44
|
"tsdown": "^0.21.0",
|
|
45
45
|
"typescript": "^5.9.3",
|
|
46
46
|
"zod": "^4.3.6"
|