@beyonk/http 12.4.2 → 12.6.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
@@ -192,10 +192,14 @@ Handlers have a signature with two items:
192
192
  })
193
193
  ```
194
194
 
195
+ Every error carries the request that failed as `e.request` (`{ method, url }`), and HTTP failures also carry `e.status` and the parsed response as `e.body`. Errors thrown by `fetch` itself, such as network failures or aborts, have `e.request` but no `e.status`.
196
+
195
197
  ctx can be whatever you want really - it is whatever you pass in as `context(...)`.
196
198
 
197
199
  However, if the `context` object you pass in has a `fetch` function, this is used as the `fetch` for XHR requests.
198
200
 
201
+ If it has a `logger` with an `error(details, message)` method, such as a pino logger, every failure that reaches a `default` handler (request-level or global) is logged through it before the handler runs, with `err`, `status`, `method`, `url` and `body`. Without one the console is used. Failures caught by a status-specific handler such as `.notFound()` are not logged.
202
+
199
203
  ```js
200
204
  export async function preload () {
201
205
  await Api
package/dist/index.cjs CHANGED
@@ -29,10 +29,13 @@ var HttpError = class extends Error {
29
29
  * Create a new HTTP error
30
30
  * @param {string} message - Error message
31
31
  * @param {any} body - Error response body
32
+ * @param {number} [status] - HTTP status code, absent when the request never got a response
32
33
  */
33
- constructor(message, body) {
34
+ constructor(message, body, status) {
34
35
  super(message);
35
36
  this.body = body;
37
+ this.status = status;
38
+ this.request = void 0;
36
39
  }
37
40
  };
38
41
  var AccessDeniedError = class extends HttpError {
@@ -141,11 +144,26 @@ var Api = class {
141
144
  handle(e, ctx) {
142
145
  const constructorName = Object.getPrototypeOf(e).constructor.name;
143
146
  const globalHandlerName = `${constructorName[0].toLowerCase()}${constructorName.slice(1, -5)}`;
144
- const handler = this.handlers[constructorName] || this.options.handlers && this.options.handlers[globalHandlerName] || this.defaultHandler || this.options.handlers && this.options.handlers.default || ((e2) => {
147
+ const specificHandler = this.handlers[constructorName] || this.options.handlers && this.options.handlers[globalHandlerName];
148
+ const handler = specificHandler || this.defaultHandler || this.options.handlers && this.options.handlers.default || ((e2) => {
145
149
  console.error(constructorName, e2.message, e2);
146
150
  });
151
+ if (!specificHandler) {
152
+ this.log(e, ctx);
153
+ }
147
154
  return handler(e, ctx);
148
155
  }
156
+ /**
157
+ * Log a failed request through the context logger, or the console
158
+ * @param {HttpError} e - Error instance
159
+ * @param {ApiContext} [ctx] - API context
160
+ */
161
+ log(e, ctx) {
162
+ const logger = ctx && ctx.logger || console;
163
+ const { method, url } = e.request || {};
164
+ const reason = e.status ? `with ${e.status}` : `(${e.message})`;
165
+ logger.error({ err: e, status: e.status, method, url, body: e.body }, `${method} ${url} failed ${reason}`);
166
+ }
149
167
  /**
150
168
  * Send the HTTP request
151
169
  * @template T
@@ -175,7 +193,7 @@ var Api = class {
175
193
  try {
176
194
  result = await this.#doQuery(1, client, ep, options);
177
195
  } catch (e) {
178
- console.log(e);
196
+ e.request = { method: options.method, url: ep };
179
197
  return this.handle(e, this.ctx);
180
198
  } finally {
181
199
  this.resetRequest();
@@ -228,7 +246,7 @@ var Api = class {
228
246
  console.log("Failed to parse error body when asked.");
229
247
  }
230
248
  const ClientError = getErrorByCode(r.status);
231
- throw new ClientError(r.statusText, content);
249
+ throw new ClientError(r.statusText, content, r.status);
232
250
  } catch (e) {
233
251
  if (retry.attempts && retry.errors && attempt < retry.attempts && retry.errors.includes(e.code)) {
234
252
  console.warn(`Got ${e.code} when calling ${endpoint}. Retrying request (${attempt}/${retry.attempts})`);
package/dist/index.d.cts CHANGED
@@ -30,6 +30,10 @@ type RetryOptions = {
30
30
  type ApiContext = {
31
31
  /** Fetch client */
32
32
  fetch?: FetchClient;
33
+ /** Logger for failed requests, defaults to console */
34
+ logger?: {
35
+ error: (details: Record<string, any>, message: string) => void;
36
+ };
33
37
  /** Additional context properties */
34
38
  [key: string]: any;
35
39
  };
@@ -98,9 +102,16 @@ declare class HttpError extends Error {
98
102
  * Create a new HTTP error
99
103
  * @param {string} message - Error message
100
104
  * @param {any} body - Error response body
105
+ * @param {number} [status] - HTTP status code, absent when the request never got a response
101
106
  */
102
- constructor(message: string, body: any);
107
+ constructor(message: string, body: any, status?: number);
103
108
  body: any;
109
+ status: number;
110
+ /** @type {{ method: string, url: string } | undefined} The request that failed */
111
+ request: {
112
+ method: string;
113
+ url: string;
114
+ } | undefined;
104
115
  }
105
116
  declare namespace _default {
106
117
  export { create };
@@ -145,6 +156,12 @@ declare class Api {
145
156
  * @returns {any} Result of error handler
146
157
  */
147
158
  handle(e: HttpError, ctx?: ApiContext): any;
159
+ /**
160
+ * Log a failed request through the context logger, or the console
161
+ * @param {HttpError} e - Error instance
162
+ * @param {ApiContext} [ctx] - API context
163
+ */
164
+ log(e: HttpError, ctx?: ApiContext): void;
148
165
  /**
149
166
  * Send the HTTP request
150
167
  * @template T
package/dist/index.d.ts CHANGED
@@ -30,6 +30,10 @@ type RetryOptions = {
30
30
  type ApiContext = {
31
31
  /** Fetch client */
32
32
  fetch?: FetchClient;
33
+ /** Logger for failed requests, defaults to console */
34
+ logger?: {
35
+ error: (details: Record<string, any>, message: string) => void;
36
+ };
33
37
  /** Additional context properties */
34
38
  [key: string]: any;
35
39
  };
@@ -98,9 +102,16 @@ declare class HttpError extends Error {
98
102
  * Create a new HTTP error
99
103
  * @param {string} message - Error message
100
104
  * @param {any} body - Error response body
105
+ * @param {number} [status] - HTTP status code, absent when the request never got a response
101
106
  */
102
- constructor(message: string, body: any);
107
+ constructor(message: string, body: any, status?: number);
103
108
  body: any;
109
+ status: number;
110
+ /** @type {{ method: string, url: string } | undefined} The request that failed */
111
+ request: {
112
+ method: string;
113
+ url: string;
114
+ } | undefined;
104
115
  }
105
116
  declare namespace _default {
106
117
  export { create };
@@ -145,6 +156,12 @@ declare class Api {
145
156
  * @returns {any} Result of error handler
146
157
  */
147
158
  handle(e: HttpError, ctx?: ApiContext): any;
159
+ /**
160
+ * Log a failed request through the context logger, or the console
161
+ * @param {HttpError} e - Error instance
162
+ * @param {ApiContext} [ctx] - API context
163
+ */
164
+ log(e: HttpError, ctx?: ApiContext): void;
148
165
  /**
149
166
  * Send the HTTP request
150
167
  * @template T
package/dist/index.js CHANGED
@@ -4,10 +4,13 @@ var HttpError = class extends Error {
4
4
  * Create a new HTTP error
5
5
  * @param {string} message - Error message
6
6
  * @param {any} body - Error response body
7
+ * @param {number} [status] - HTTP status code, absent when the request never got a response
7
8
  */
8
- constructor(message, body) {
9
+ constructor(message, body, status) {
9
10
  super(message);
10
11
  this.body = body;
12
+ this.status = status;
13
+ this.request = void 0;
11
14
  }
12
15
  };
13
16
  var AccessDeniedError = class extends HttpError {
@@ -116,11 +119,26 @@ var Api = class {
116
119
  handle(e, ctx) {
117
120
  const constructorName = Object.getPrototypeOf(e).constructor.name;
118
121
  const globalHandlerName = `${constructorName[0].toLowerCase()}${constructorName.slice(1, -5)}`;
119
- const handler = this.handlers[constructorName] || this.options.handlers && this.options.handlers[globalHandlerName] || this.defaultHandler || this.options.handlers && this.options.handlers.default || ((e2) => {
122
+ const specificHandler = this.handlers[constructorName] || this.options.handlers && this.options.handlers[globalHandlerName];
123
+ const handler = specificHandler || this.defaultHandler || this.options.handlers && this.options.handlers.default || ((e2) => {
120
124
  console.error(constructorName, e2.message, e2);
121
125
  });
126
+ if (!specificHandler) {
127
+ this.log(e, ctx);
128
+ }
122
129
  return handler(e, ctx);
123
130
  }
131
+ /**
132
+ * Log a failed request through the context logger, or the console
133
+ * @param {HttpError} e - Error instance
134
+ * @param {ApiContext} [ctx] - API context
135
+ */
136
+ log(e, ctx) {
137
+ const logger = ctx && ctx.logger || console;
138
+ const { method, url } = e.request || {};
139
+ const reason = e.status ? `with ${e.status}` : `(${e.message})`;
140
+ logger.error({ err: e, status: e.status, method, url, body: e.body }, `${method} ${url} failed ${reason}`);
141
+ }
124
142
  /**
125
143
  * Send the HTTP request
126
144
  * @template T
@@ -150,7 +168,7 @@ var Api = class {
150
168
  try {
151
169
  result = await this.#doQuery(1, client, ep, options);
152
170
  } catch (e) {
153
- console.log(e);
171
+ e.request = { method: options.method, url: ep };
154
172
  return this.handle(e, this.ctx);
155
173
  } finally {
156
174
  this.resetRequest();
@@ -203,7 +221,7 @@ var Api = class {
203
221
  console.log("Failed to parse error body when asked.");
204
222
  }
205
223
  const ClientError = getErrorByCode(r.status);
206
- throw new ClientError(r.statusText, content);
224
+ throw new ClientError(r.statusText, content, r.status);
207
225
  } catch (e) {
208
226
  if (retry.attempts && retry.errors && attempt < retry.attempts && retry.errors.includes(e.code)) {
209
227
  console.warn(`Got ${e.code} when calling ${endpoint}. Retrying request (${attempt}/${retry.attempts})`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beyonk/http",
3
- "version": "12.4.2",
3
+ "version": "12.6.0",
4
4
  "description": "An isomorphic http client for Svelte apps",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/beyonk/http.git",