@afoures/http-client 0.1.1 → 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/README.md CHANGED
@@ -29,7 +29,7 @@ import { Endpoint, http_client } from '@afoures/http-client'
29
29
  import { z } from 'zod'
30
30
 
31
31
  const api = http_client({
32
- origin: 'https://api.example.com',
32
+ base_url: 'https://api.example.com',
33
33
  endpoints: {
34
34
  users: {
35
35
  list: new Endpoint({
@@ -43,6 +43,7 @@ const api = http_client({
43
43
  },
44
44
  data: {
45
45
  schema: z.array(z.object({ id: z.string(), name: z.string() })),
46
+ parse: 'json',
46
47
  },
47
48
  }),
48
49
  get: new Endpoint({
@@ -50,6 +51,7 @@ const api = http_client({
50
51
  pathname: '/users/(:id)',
51
52
  data: {
52
53
  schema: z.object({ id: z.string(), name: z.string() }),
54
+ parse: 'json',
53
55
  },
54
56
  }),
55
57
  create: new Endpoint({
@@ -57,9 +59,11 @@ const api = http_client({
57
59
  pathname: '/users',
58
60
  body: {
59
61
  schema: z.object({ name: z.string(), email: z.string().email() }),
62
+ serialize: 'json',
60
63
  },
61
64
  data: {
62
65
  schema: z.object({ id: z.string(), name: z.string() }),
66
+ parse: 'json',
63
67
  },
64
68
  }),
65
69
  },
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { AbortedError, DeserializationError, HttpClientError, NetworkError, SerializationError, TimeoutError, UnexpectedError } from "./lib/errors.mjs";
1
+ import { AbortedError, HttpClientError, NetworkError, ParseError, SerializationError, TimeoutError, UnexpectedError } from "./lib/errors.mjs";
2
2
  import { HTTPFetch, HTTPMethod, HTTPStatus, Parser, Pathname, Schema, Serializer } from "./lib/types.mjs";
3
3
  import { AnyEndpoint, Endpoint, EndpointDefinition } from "./lib/endpoint.mjs";
4
4
  import { EndpointMap, HttpClientOptions, http_client } from "./lib/http-client.mjs";
5
- export { AbortedError, type AnyEndpoint, DeserializationError, Endpoint, type EndpointDefinition, type EndpointMap, type HTTPFetch, type HTTPMethod, type HTTPStatus, HttpClientError, type HttpClientOptions, NetworkError, type Parser, type Pathname, type Schema, SerializationError, type Serializer, TimeoutError, UnexpectedError, http_client };
5
+ export { AbortedError, type AnyEndpoint, Endpoint, type EndpointDefinition, type EndpointMap, type HTTPFetch, type HTTPMethod, type HTTPStatus, HttpClientError, type HttpClientOptions, NetworkError, ParseError, type Parser, type Pathname, type Schema, SerializationError, type Serializer, TimeoutError, UnexpectedError, http_client };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { AbortedError, DeserializationError, HttpClientError, NetworkError, SerializationError, TimeoutError, UnexpectedError } from "./lib/errors.mjs";
1
+ import { AbortedError, HttpClientError, NetworkError, ParseError, SerializationError, TimeoutError, UnexpectedError } from "./lib/errors.mjs";
2
2
  import { Endpoint } from "./lib/endpoint.mjs";
3
3
  import { http_client } from "./lib/http-client.mjs";
4
4
 
5
- export { AbortedError, DeserializationError, Endpoint, HttpClientError, NetworkError, SerializationError, TimeoutError, UnexpectedError, http_client };
5
+ export { AbortedError, Endpoint, HttpClientError, NetworkError, ParseError, SerializationError, TimeoutError, UnexpectedError, http_client };
@@ -1,4 +1,4 @@
1
- import { DeserializationError, SerializationError } from "./errors.mjs";
1
+ import { ParseError, SerializationError } from "./errors.mjs";
2
2
  import { ErrorMessage, HTTPFetch, HTTPMethod, Parser, Pathname, Pretty, Schema, Serializer } from "./types.mjs";
3
3
 
4
4
  //#region src/lib/endpoint.d.ts
@@ -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(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 | DeserializationError>;
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
@@ -1,8 +1,79 @@
1
- import { DeserializationError, SerializationError } from "./errors.mjs";
1
+ import { ParseError, SerializationError } from "./errors.mjs";
2
2
  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,10 +106,11 @@ 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
- if (this.#serializers.params.serialization) pathname_params = this.#serializers.params.serialization(transformed_params);
113
+ if (this.#serializers.params.serialize) pathname_params = this.#serializers.params.serialize(transformed_params);
42
114
  else pathname_params = Object.fromEntries(Object.entries(transformed_params).map(([key, value]) => [key, String(value)]));
43
115
  } else pathname_params = Object.fromEntries(Object.entries(init.params).map(([key, value]) => [key, String(value)]));
44
116
  const pathname = this.#pattern.href(pathname_params);
@@ -47,11 +119,12 @@ 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
- if (typeof this.#serializers.query.serialization === "function") search_params = this.#serializers.query.serialization(transformed_query);
54
- else if (this.#serializers.query.serialization === "urlencoded") {
126
+ if (typeof this.#serializers.query.serialize === "function") search_params = this.#serializers.query.serialize(transformed_query);
127
+ else if (this.#serializers.query.serialize === "urlencoded") {
55
128
  if (Array.isArray(transformed_query)) transformed_query.forEach((tuple, index) => {
56
129
  if (Array.isArray(tuple)) tuple.forEach((value, tupleIndex) => {
57
130
  search_params.append(`${index}[${tupleIndex}]`, String(value));
@@ -80,83 +153,61 @@ 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
- if (typeof this.#serializers.body.serialization === "function") return this.#serializers.body.serialization(transformed_content);
160
+ if (typeof this.#serializers.body.serialize === "function") return this.#serializers.body.serialize(transformed_content);
87
161
  else return {
88
162
  body: JSON.stringify(transformed_content),
89
163
  content_type: "application/json"
90
164
  };
91
165
  }
92
- async parse_response(response) {
93
- const raw_response = response;
94
- const cloned_response = response.clone();
95
- const status = cloned_response.status;
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.deserialization === "function") parsed = await parser.deserialization(cloned_response.body);
110
- else if (parser.deserialization === "json") parsed = await parse_as_json(cloned_response);
111
- else if (parser.deserialization === "text") parsed = await cloned_response.text();
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
- if (result.issues !== void 0) return new DeserializationError("Error deserialization failed", {
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 cloned_response.text();
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.deserialization === "function") parsed = await parser.deserialization(cloned_response.body);
139
- else if (parser.deserialization === "json") parsed = await parse_as_json(cloned_response);
140
- else if (parser.deserialization === "text") parsed = await cloned_response.text();
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
- if (result.issues !== void 0) return new DeserializationError("Response deserialization failed", {
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
- ok: true,
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
  }
@@ -170,19 +221,19 @@ async function parse_as_json(response) {
170
221
  throw new Error(`Failed to parse response as JSON: ${e instanceof Error ? e.message : String(e)}`);
171
222
  }
172
223
  }
173
- function as_serializer(serializer, default_serialization) {
224
+ function as_serializer(serializer, default_serialize) {
174
225
  if (!serializer || typeof serializer !== "object" || !("schema" in serializer)) return null;
175
- if (default_serialization === void 0 || "serialization" in serializer && typeof serializer.serialization !== "undefined") return serializer;
226
+ if (default_serialize === void 0 || "serialize" in serializer && typeof serializer.serialize !== "undefined") return serializer;
176
227
  return {
177
- serialization: default_serialization,
228
+ serialize: default_serialize,
178
229
  ...serializer
179
230
  };
180
231
  }
181
- function as_parser(parser, default_deserialization) {
232
+ function as_parser(parser, default_parse) {
182
233
  if (!parser || typeof parser !== "object" || !("schema" in parser)) return null;
183
- if (default_deserialization === void 0 || "deserialization" in parser && typeof parser.deserialization !== "undefined") return parser;
234
+ if (default_parse === void 0 || "parse" in parser && typeof parser.parse !== "undefined") return parser;
184
235
  return {
185
- deserialization: default_deserialization,
236
+ parse: default_parse,
186
237
  ...parser
187
238
  };
188
239
  }
@@ -1,54 +1,78 @@
1
1
  //#region src/lib/errors.d.ts
2
- type BaseContext = {
3
- operation: string;
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 ErrorCause = {
6
- cause?: unknown;
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
- constructor(message: string, options: ErrorCause);
10
- }
11
- declare class TimeoutError extends HttpClientError {
12
- readonly context: BaseContext;
35
+ readonly context: ErrorContext;
13
36
  constructor(message: string, {
14
37
  cause,
15
- ...context
16
- }: ErrorCause & BaseContext);
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
- readonly context: BaseContext;
20
- constructor(message: string, {
21
- cause,
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
- readonly context: BaseContext;
27
- constructor(message: string, {
28
- cause,
29
- ...context
30
- }: ErrorCause & BaseContext);
54
+ constructor(message: string, options: {
55
+ cause?: unknown;
56
+ } & Partial<ErrorContext>);
31
57
  }
32
- declare class DeserializationError extends HttpClientError {
33
- readonly context: BaseContext;
34
- constructor(message: string, {
35
- cause,
36
- ...context
37
- }: ErrorCause & BaseContext);
58
+ declare class ParseError extends HttpClientError {
59
+ constructor(message: string, options: {
60
+ cause?: unknown;
61
+ } & Partial<ErrorContext>);
38
62
  }
39
63
  declare class NetworkError extends HttpClientError {
40
- readonly context: BaseContext;
41
- constructor(message: string, {
42
- cause,
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: BaseContext;
69
+ readonly context: ErrorContext;
48
70
  constructor(message: string, {
49
71
  cause,
50
- ...context
51
- }: ErrorCause & BaseContext);
72
+ ...options
73
+ }: {
74
+ cause?: unknown;
75
+ } & Partial<ErrorContext>);
52
76
  }
53
77
  //#endregion
54
- export { AbortedError, DeserializationError, HttpClientError, NetworkError, SerializationError, TimeoutError, UnexpectedError };
78
+ export { AbortedError, HttpClientError, NetworkError, ParseError, SerializationError, TimeoutError, UnexpectedError };
@@ -1,58 +1,56 @@
1
1
  //#region src/lib/errors.ts
2
2
  var HttpClientError = class extends Error {
3
- constructor(message, options) {
4
- super(message, options);
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
- context;
10
- constructor(message, { cause, ...context }) {
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
- context;
18
- constructor(message, { cause, ...context }) {
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
- context;
26
- constructor(message, { cause, ...context }) {
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
- var DeserializationError = class extends HttpClientError {
33
- context;
34
- constructor(message, { cause, ...context }) {
35
- super(message, { cause });
36
- this.name = "DeserializationError";
37
- this.context = context;
31
+ var ParseError = class extends HttpClientError {
32
+ constructor(message, options) {
33
+ super(message, options);
34
+ this.name = "ParseError";
38
35
  }
39
36
  };
40
37
  var NetworkError = class extends HttpClientError {
41
- context;
42
- constructor(message, { cause, ...context }) {
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, ...context }) {
45
+ constructor(message, { cause, ...options }) {
51
46
  super(message, { cause });
52
47
  this.name = "UnexpectedError";
53
- this.context = context;
48
+ this.context = {
49
+ ...options,
50
+ operation: options.operation ?? "unknown"
51
+ };
54
52
  }
55
53
  };
56
54
 
57
55
  //#endregion
58
- export { AbortedError, DeserializationError, HttpClientError, NetworkError, SerializationError, TimeoutError, UnexpectedError };
56
+ export { AbortedError, HttpClientError, NetworkError, ParseError, SerializationError, TimeoutError, UnexpectedError };