@naturalcycles/js-lib 14.125.0 → 14.127.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.
@@ -1,142 +1,5 @@
1
1
  /// <reference lib="dom" />
2
- import { CommonLogger } from '../log/commonLogger';
3
- import type { Promisable } from '../typeFest';
4
- import { Reviver } from '../types';
5
- import type { HttpMethod, HttpStatusFamily } from './http.model';
6
- export interface FetcherNormalizedCfg extends Required<FetcherCfg>, FetcherRequest {
7
- logger: CommonLogger;
8
- searchParams: Record<string, any>;
9
- }
10
- export type FetcherBeforeRequestHook = (req: FetcherRequest) => Promisable<void>;
11
- export type FetcherAfterResponseHook = (res: FetcherResponse) => Promisable<void>;
12
- export type FetcherBeforeRetryHook = (res: FetcherResponse) => Promisable<void>;
13
- export interface FetcherCfg {
14
- /**
15
- * Should **not** contain trailing slash.
16
- */
17
- baseUrl?: string;
18
- /**
19
- * Default rule is that you **are allowed** to mutate req, res, res.retryStatus
20
- * properties of hook function arguments.
21
- * If you throw an error from the hook - it will be re-thrown as-is.
22
- */
23
- hooks?: {
24
- /**
25
- * Allows to mutate req.
26
- */
27
- beforeRequest?: FetcherBeforeRequestHook[];
28
- /**
29
- * Allows to mutate res.
30
- * If you set `res.err` - it will be thrown.
31
- */
32
- afterResponse?: FetcherAfterResponseHook[];
33
- /**
34
- * Allows to mutate res.retryStatus to override retry behavior.
35
- */
36
- beforeRetry?: FetcherBeforeRetryHook[];
37
- };
38
- /**
39
- * If true - enables all possible logging.
40
- */
41
- debug?: boolean;
42
- logRequest?: boolean;
43
- logRequestBody?: boolean;
44
- logResponse?: boolean;
45
- logResponseBody?: boolean;
46
- /**
47
- * Defaults to `console`.
48
- */
49
- logger?: CommonLogger;
50
- }
51
- export interface FetcherRetryStatus {
52
- retryAttempt: number;
53
- retryTimeout: number;
54
- retryStopped: boolean;
55
- }
56
- export interface FetcherRetryOptions {
57
- count: number;
58
- timeout: number;
59
- timeoutMax: number;
60
- timeoutMultiplier: number;
61
- }
62
- export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers'> {
63
- url: string;
64
- init: RequestInitNormalized;
65
- mode: FetcherMode;
66
- throwHttpErrors: boolean;
67
- timeoutSeconds: number;
68
- retry: FetcherRetryOptions;
69
- retryPost: boolean;
70
- retry4xx: boolean;
71
- retry5xx: boolean;
72
- }
73
- export interface FetcherOptions {
74
- method?: HttpMethod;
75
- throwHttpErrors?: boolean;
76
- /**
77
- * Default: 30.
78
- *
79
- * Timeout applies to both get the response and retrieve the body (e.g `await res.json()`),
80
- * so both should finish within this single timeout (not each).
81
- */
82
- timeoutSeconds?: number;
83
- json?: any;
84
- text?: string;
85
- /**
86
- * Supports all the types that RequestInit.body supports.
87
- *
88
- * Useful when you want to e.g pass FormData.
89
- */
90
- body?: Blob | BufferSource | FormData | URLSearchParams | string;
91
- credentials?: RequestCredentials;
92
- /**
93
- * Default to true.
94
- */
95
- followRedirects?: boolean;
96
- headers?: Record<string, any>;
97
- mode?: FetcherMode;
98
- searchParams?: Record<string, any>;
99
- /**
100
- * Default is 2 retries (3 tries in total).
101
- * Pass `retry: { count: 0 }` to disable retries.
102
- */
103
- retry?: Partial<FetcherRetryOptions>;
104
- /**
105
- * Defaults to false.
106
- * Set to true to allow retrying `post` requests.
107
- */
108
- retryPost?: boolean;
109
- /**
110
- * Defaults to false.
111
- */
112
- retry4xx?: boolean;
113
- /**
114
- * Defaults to true.
115
- */
116
- retry5xx?: boolean;
117
- jsonReviver?: Reviver;
118
- }
119
- export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
120
- method: HttpMethod;
121
- headers: Record<string, any>;
122
- };
123
- export interface FetcherSuccessResponse<BODY = unknown> extends FetcherResponse<BODY> {
124
- err?: undefined;
125
- fetchResponse: Response;
126
- body: BODY;
127
- }
128
- export interface FetcherErrorResponse<BODY = unknown> extends FetcherResponse<BODY> {
129
- err: Error;
130
- }
131
- export interface FetcherResponse<BODY = unknown> {
132
- err?: Error;
133
- req: FetcherRequest;
134
- fetchResponse?: Response;
135
- statusFamily?: HttpStatusFamily;
136
- body?: BODY;
137
- retryStatus: FetcherRetryStatus;
138
- }
139
- export type FetcherMode = 'json' | 'text' | 'void';
2
+ import type { FetcherAfterResponseHook, FetcherBeforeRequestHook, FetcherBeforeRetryHook, FetcherCfg, FetcherNormalizedCfg, FetcherOptions, FetcherResponse } from './fetcher.model';
140
3
  /**
141
4
  * Experimental wrapper around Fetch.
142
5
  * Works in both Browser and Node, using `globalThis.fetch`.
@@ -116,7 +116,8 @@ class Fetcher {
116
116
  retryTimeout: req.retry.timeout,
117
117
  },
118
118
  };
119
- const shortUrl = this.getShortUrl(req.url);
119
+ const fullUrl = new URL(req.url);
120
+ const shortUrl = this.getShortUrl(fullUrl);
120
121
  const signature = [method.toUpperCase(), shortUrl].join(' ');
121
122
  /* eslint-disable no-await-in-loop */
122
123
  while (!res.retryStatus.retryStopped) {
@@ -132,10 +133,12 @@ class Fetcher {
132
133
  }
133
134
  try {
134
135
  res.fetchResponse = await globalThis.fetch(req.url, req.init);
136
+ res.ok = res.fetchResponse.ok;
135
137
  }
136
138
  catch (err) {
137
139
  // For example, CORS error would result in "TypeError: failed to fetch" here
138
140
  res.err = err;
141
+ res.ok = false;
139
142
  }
140
143
  res.statusFamily = this.getStatusFamily(res);
141
144
  if (res.fetchResponse?.ok) {
@@ -147,6 +150,7 @@ class Fetcher {
147
150
  res.body = JSON.parse(text, req.jsonReviver);
148
151
  }
149
152
  catch (err) {
153
+ res.ok = false;
150
154
  res.err = (0, error_util_1._anyToError)(err, http_error_1.HttpError, (0, object_util_1._filterNullishValues)({
151
155
  httpStatusCode: 0,
152
156
  url: req.url,
@@ -280,9 +284,18 @@ class Fetcher {
280
284
  */
281
285
  getShortUrl(url) {
282
286
  const { baseUrl } = this.cfg;
283
- if (!baseUrl)
284
- return url;
285
- return url.split('?')[0].slice(baseUrl.length);
287
+ if (url.password) {
288
+ url = new URL(url.toString()); // prevent original url mutation
289
+ url.password = '[redacted]';
290
+ }
291
+ let shortUrl = url.toString();
292
+ if (!this.cfg.logWithSearchParams) {
293
+ shortUrl = shortUrl.split('?')[0];
294
+ }
295
+ if (!this.cfg.logWithPrefixUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
296
+ shortUrl = shortUrl.slice(baseUrl.length);
297
+ }
298
+ return shortUrl;
286
299
  }
287
300
  normalizeCfg(cfg) {
288
301
  if (cfg.baseUrl?.endsWith('/')) {
@@ -306,6 +319,8 @@ class Fetcher {
306
319
  logRequestBody: debug,
307
320
  logResponse: debug,
308
321
  logResponseBody: debug,
322
+ logWithPrefixUrl: true,
323
+ logWithSearchParams: true,
309
324
  retry: { ...defRetryOptions },
310
325
  init: {
311
326
  method: cfg.method || 'get',
@@ -0,0 +1,151 @@
1
+ import type { CommonLogger } from '../log/commonLogger';
2
+ import type { Promisable } from '../typeFest';
3
+ import type { Reviver } from '../types';
4
+ import type { HttpMethod, HttpStatusFamily } from './http.model';
5
+ export interface FetcherNormalizedCfg extends Required<FetcherCfg>, FetcherRequest {
6
+ logger: CommonLogger;
7
+ searchParams: Record<string, any>;
8
+ }
9
+ export type FetcherBeforeRequestHook = (req: FetcherRequest) => Promisable<void>;
10
+ export type FetcherAfterResponseHook = (res: FetcherResponse) => Promisable<void>;
11
+ export type FetcherBeforeRetryHook = (res: FetcherResponse) => Promisable<void>;
12
+ export interface FetcherCfg {
13
+ /**
14
+ * Should **not** contain trailing slash.
15
+ */
16
+ baseUrl?: string;
17
+ /**
18
+ * Default rule is that you **are allowed** to mutate req, res, res.retryStatus
19
+ * properties of hook function arguments.
20
+ * If you throw an error from the hook - it will be re-thrown as-is.
21
+ */
22
+ hooks?: {
23
+ /**
24
+ * Allows to mutate req.
25
+ */
26
+ beforeRequest?: FetcherBeforeRequestHook[];
27
+ /**
28
+ * Allows to mutate res.
29
+ * If you set `res.err` - it will be thrown.
30
+ */
31
+ afterResponse?: FetcherAfterResponseHook[];
32
+ /**
33
+ * Allows to mutate res.retryStatus to override retry behavior.
34
+ */
35
+ beforeRetry?: FetcherBeforeRetryHook[];
36
+ };
37
+ /**
38
+ * If true - enables all possible logging.
39
+ */
40
+ debug?: boolean;
41
+ logRequest?: boolean;
42
+ logRequestBody?: boolean;
43
+ logResponse?: boolean;
44
+ logResponseBody?: boolean;
45
+ /**
46
+ * Default to true.
47
+ * Set to false to exclude `prefixUrl` from logs (both success and error)
48
+ */
49
+ logWithPrefixUrl?: boolean;
50
+ /**
51
+ * Default to true.
52
+ * Set to false to strip searchParams from url when logging (both success and error)
53
+ */
54
+ logWithSearchParams?: boolean;
55
+ /**
56
+ * Defaults to `console`.
57
+ */
58
+ logger?: CommonLogger;
59
+ }
60
+ export interface FetcherRetryStatus {
61
+ retryAttempt: number;
62
+ retryTimeout: number;
63
+ retryStopped: boolean;
64
+ }
65
+ export interface FetcherRetryOptions {
66
+ count: number;
67
+ timeout: number;
68
+ timeoutMax: number;
69
+ timeoutMultiplier: number;
70
+ }
71
+ export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers'> {
72
+ url: string;
73
+ init: RequestInitNormalized;
74
+ mode: FetcherMode;
75
+ throwHttpErrors: boolean;
76
+ timeoutSeconds: number;
77
+ retry: FetcherRetryOptions;
78
+ retryPost: boolean;
79
+ retry4xx: boolean;
80
+ retry5xx: boolean;
81
+ }
82
+ export interface FetcherOptions {
83
+ method?: HttpMethod;
84
+ throwHttpErrors?: boolean;
85
+ /**
86
+ * Default: 30.
87
+ *
88
+ * Timeout applies to both get the response and retrieve the body (e.g `await res.json()`),
89
+ * so both should finish within this single timeout (not each).
90
+ */
91
+ timeoutSeconds?: number;
92
+ json?: any;
93
+ text?: string;
94
+ /**
95
+ * Supports all the types that RequestInit.body supports.
96
+ *
97
+ * Useful when you want to e.g pass FormData.
98
+ */
99
+ body?: Blob | BufferSource | FormData | URLSearchParams | string;
100
+ credentials?: RequestCredentials;
101
+ /**
102
+ * Default to true.
103
+ */
104
+ followRedirects?: boolean;
105
+ headers?: Record<string, any>;
106
+ mode?: FetcherMode;
107
+ searchParams?: Record<string, any>;
108
+ /**
109
+ * Default is 2 retries (3 tries in total).
110
+ * Pass `retry: { count: 0 }` to disable retries.
111
+ */
112
+ retry?: Partial<FetcherRetryOptions>;
113
+ /**
114
+ * Defaults to false.
115
+ * Set to true to allow retrying `post` requests.
116
+ */
117
+ retryPost?: boolean;
118
+ /**
119
+ * Defaults to false.
120
+ */
121
+ retry4xx?: boolean;
122
+ /**
123
+ * Defaults to true.
124
+ */
125
+ retry5xx?: boolean;
126
+ jsonReviver?: Reviver;
127
+ }
128
+ export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
129
+ method: HttpMethod;
130
+ headers: Record<string, any>;
131
+ };
132
+ export interface FetcherSuccessResponse<BODY = unknown> {
133
+ ok: true;
134
+ err: undefined;
135
+ fetchResponse: Response;
136
+ body: BODY;
137
+ req: FetcherRequest;
138
+ statusFamily?: HttpStatusFamily;
139
+ retryStatus: FetcherRetryStatus;
140
+ }
141
+ export interface FetcherErrorResponse<BODY = unknown> {
142
+ ok: false;
143
+ err: Error;
144
+ fetchResponse?: Response;
145
+ body?: BODY;
146
+ req: FetcherRequest;
147
+ statusFamily?: HttpStatusFamily;
148
+ retryStatus: FetcherRetryStatus;
149
+ }
150
+ export type FetcherResponse<BODY = unknown> = FetcherSuccessResponse<BODY> | FetcherErrorResponse<BODY>;
151
+ export type FetcherMode = 'json' | 'text' | 'void';
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/index.d.ts CHANGED
@@ -76,4 +76,5 @@ export * from './datetime/dateInterval';
76
76
  export * from './datetime/timeInterval';
77
77
  export * from './http/http.model';
78
78
  export * from './http/fetcher';
79
+ export * from './http/fetcher.model';
79
80
  export { is };
package/dist/index.js CHANGED
@@ -81,3 +81,4 @@ tslib_1.__exportStar(require("./datetime/dateInterval"), exports);
81
81
  tslib_1.__exportStar(require("./datetime/timeInterval"), exports);
82
82
  tslib_1.__exportStar(require("./http/http.model"), exports);
83
83
  tslib_1.__exportStar(require("./http/fetcher"), exports);
84
+ tslib_1.__exportStar(require("./http/fetcher.model"), exports);
@@ -124,7 +124,8 @@ export class Fetcher {
124
124
  retryTimeout: req.retry.timeout,
125
125
  },
126
126
  };
127
- const shortUrl = this.getShortUrl(req.url);
127
+ const fullUrl = new URL(req.url);
128
+ const shortUrl = this.getShortUrl(fullUrl);
128
129
  const signature = [method.toUpperCase(), shortUrl].join(' ');
129
130
  /* eslint-disable no-await-in-loop */
130
131
  while (!res.retryStatus.retryStopped) {
@@ -140,10 +141,12 @@ export class Fetcher {
140
141
  }
141
142
  try {
142
143
  res.fetchResponse = await globalThis.fetch(req.url, req.init);
144
+ res.ok = res.fetchResponse.ok;
143
145
  }
144
146
  catch (err) {
145
147
  // For example, CORS error would result in "TypeError: failed to fetch" here
146
148
  res.err = err;
149
+ res.ok = false;
147
150
  }
148
151
  res.statusFamily = this.getStatusFamily(res);
149
152
  if ((_g = res.fetchResponse) === null || _g === void 0 ? void 0 : _g.ok) {
@@ -155,6 +158,7 @@ export class Fetcher {
155
158
  res.body = JSON.parse(text, req.jsonReviver);
156
159
  }
157
160
  catch (err) {
161
+ res.ok = false;
158
162
  res.err = _anyToError(err, HttpError, _filterNullishValues({
159
163
  httpStatusCode: 0,
160
164
  url: req.url,
@@ -320,9 +324,18 @@ export class Fetcher {
320
324
  */
321
325
  getShortUrl(url) {
322
326
  const { baseUrl } = this.cfg;
323
- if (!baseUrl)
324
- return url;
325
- return url.split('?')[0].slice(baseUrl.length);
327
+ if (url.password) {
328
+ url = new URL(url.toString()); // prevent original url mutation
329
+ url.password = '[redacted]';
330
+ }
331
+ let shortUrl = url.toString();
332
+ if (!this.cfg.logWithSearchParams) {
333
+ shortUrl = shortUrl.split('?')[0];
334
+ }
335
+ if (!this.cfg.logWithPrefixUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
336
+ shortUrl = shortUrl.slice(baseUrl.length);
337
+ }
338
+ return shortUrl;
326
339
  }
327
340
  normalizeCfg(cfg) {
328
341
  var _a;
@@ -347,6 +360,8 @@ export class Fetcher {
347
360
  logRequestBody: debug,
348
361
  logResponse: debug,
349
362
  logResponseBody: debug,
363
+ logWithPrefixUrl: true,
364
+ logWithSearchParams: true,
350
365
  retry: Object.assign({}, defRetryOptions),
351
366
  init: {
352
367
  method: cfg.method || 'get',
@@ -0,0 +1 @@
1
+ export {};
package/dist-esm/index.js CHANGED
@@ -76,4 +76,5 @@ export * from './datetime/dateInterval';
76
76
  export * from './datetime/timeInterval';
77
77
  export * from './http/http.model';
78
78
  export * from './http/fetcher';
79
+ export * from './http/fetcher.model';
79
80
  export { is };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.125.0",
3
+ "version": "14.127.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -0,0 +1,181 @@
1
+ import type { CommonLogger } from '../log/commonLogger'
2
+ import type { Promisable } from '../typeFest'
3
+ import type { Reviver } from '../types'
4
+ import type { HttpMethod, HttpStatusFamily } from './http.model'
5
+
6
+ export interface FetcherNormalizedCfg extends Required<FetcherCfg>, FetcherRequest {
7
+ logger: CommonLogger
8
+ searchParams: Record<string, any>
9
+ }
10
+
11
+ export type FetcherBeforeRequestHook = (req: FetcherRequest) => Promisable<void>
12
+ export type FetcherAfterResponseHook = (res: FetcherResponse) => Promisable<void>
13
+ export type FetcherBeforeRetryHook = (res: FetcherResponse) => Promisable<void>
14
+
15
+ export interface FetcherCfg {
16
+ /**
17
+ * Should **not** contain trailing slash.
18
+ */
19
+ baseUrl?: string
20
+
21
+ /**
22
+ * Default rule is that you **are allowed** to mutate req, res, res.retryStatus
23
+ * properties of hook function arguments.
24
+ * If you throw an error from the hook - it will be re-thrown as-is.
25
+ */
26
+ hooks?: {
27
+ /**
28
+ * Allows to mutate req.
29
+ */
30
+ beforeRequest?: FetcherBeforeRequestHook[]
31
+ /**
32
+ * Allows to mutate res.
33
+ * If you set `res.err` - it will be thrown.
34
+ */
35
+ afterResponse?: FetcherAfterResponseHook[]
36
+ /**
37
+ * Allows to mutate res.retryStatus to override retry behavior.
38
+ */
39
+ beforeRetry?: FetcherBeforeRetryHook[]
40
+ }
41
+
42
+ /**
43
+ * If true - enables all possible logging.
44
+ */
45
+ debug?: boolean
46
+ logRequest?: boolean
47
+ logRequestBody?: boolean
48
+ logResponse?: boolean
49
+ logResponseBody?: boolean
50
+
51
+ /**
52
+ * Default to true.
53
+ * Set to false to exclude `prefixUrl` from logs (both success and error)
54
+ */
55
+ logWithPrefixUrl?: boolean
56
+
57
+ /**
58
+ * Default to true.
59
+ * Set to false to strip searchParams from url when logging (both success and error)
60
+ */
61
+ logWithSearchParams?: boolean
62
+
63
+ /**
64
+ * Defaults to `console`.
65
+ */
66
+ logger?: CommonLogger
67
+ }
68
+
69
+ export interface FetcherRetryStatus {
70
+ retryAttempt: number
71
+ retryTimeout: number
72
+ retryStopped: boolean
73
+ }
74
+
75
+ export interface FetcherRetryOptions {
76
+ count: number
77
+ timeout: number
78
+ timeoutMax: number
79
+ timeoutMultiplier: number
80
+ }
81
+
82
+ export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers'> {
83
+ url: string
84
+ init: RequestInitNormalized
85
+ mode: FetcherMode
86
+ throwHttpErrors: boolean
87
+ timeoutSeconds: number
88
+ retry: FetcherRetryOptions
89
+ retryPost: boolean
90
+ retry4xx: boolean
91
+ retry5xx: boolean
92
+ }
93
+
94
+ export interface FetcherOptions {
95
+ method?: HttpMethod
96
+ throwHttpErrors?: boolean
97
+ /**
98
+ * Default: 30.
99
+ *
100
+ * Timeout applies to both get the response and retrieve the body (e.g `await res.json()`),
101
+ * so both should finish within this single timeout (not each).
102
+ */
103
+ timeoutSeconds?: number
104
+
105
+ json?: any
106
+ text?: string
107
+ /**
108
+ * Supports all the types that RequestInit.body supports.
109
+ *
110
+ * Useful when you want to e.g pass FormData.
111
+ */
112
+ body?: Blob | BufferSource | FormData | URLSearchParams | string
113
+
114
+ credentials?: RequestCredentials
115
+ /**
116
+ * Default to true.
117
+ */
118
+ followRedirects?: boolean
119
+
120
+ // Removing RequestInit from options to simplify FetcherOptions interface.
121
+ // Will instead only add hand-picked useful options, such as `credentials`.
122
+ // init?: Partial<RequestInitNormalized>
123
+
124
+ headers?: Record<string, any>
125
+ mode?: FetcherMode // default to 'void'
126
+
127
+ searchParams?: Record<string, any>
128
+
129
+ /**
130
+ * Default is 2 retries (3 tries in total).
131
+ * Pass `retry: { count: 0 }` to disable retries.
132
+ */
133
+ retry?: Partial<FetcherRetryOptions>
134
+
135
+ /**
136
+ * Defaults to false.
137
+ * Set to true to allow retrying `post` requests.
138
+ */
139
+ retryPost?: boolean
140
+ /**
141
+ * Defaults to false.
142
+ */
143
+ retry4xx?: boolean
144
+ /**
145
+ * Defaults to true.
146
+ */
147
+ retry5xx?: boolean
148
+
149
+ jsonReviver?: Reviver
150
+ }
151
+
152
+ export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
153
+ method: HttpMethod
154
+ headers: Record<string, any>
155
+ }
156
+
157
+ export interface FetcherSuccessResponse<BODY = unknown> {
158
+ ok: true
159
+ err: undefined
160
+ fetchResponse: Response
161
+ body: BODY
162
+ req: FetcherRequest
163
+ statusFamily?: HttpStatusFamily
164
+ retryStatus: FetcherRetryStatus
165
+ }
166
+
167
+ export interface FetcherErrorResponse<BODY = unknown> {
168
+ ok: false
169
+ err: Error
170
+ fetchResponse?: Response
171
+ body?: BODY
172
+ req: FetcherRequest
173
+ statusFamily?: HttpStatusFamily
174
+ retryStatus: FetcherRetryStatus
175
+ }
176
+
177
+ export type FetcherResponse<BODY = unknown> =
178
+ | FetcherSuccessResponse<BODY>
179
+ | FetcherErrorResponse<BODY>
180
+
181
+ export type FetcherMode = 'json' | 'text' | 'void'
@@ -3,7 +3,6 @@
3
3
  import { ErrorObject } from '../error/error.model'
4
4
  import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util'
5
5
  import { HttpError } from '../error/http.error'
6
- import { CommonLogger } from '../log/commonLogger'
7
6
  import { _clamp } from '../number/number.util'
8
7
  import {
9
8
  _filterNullishValues,
@@ -15,170 +14,19 @@ import {
15
14
  import { pDelay } from '../promise/pDelay'
16
15
  import { _jsonParseIfPossible } from '../string/json.util'
17
16
  import { _since } from '../time/time.util'
18
- import type { Promisable } from '../typeFest'
19
- import { Reviver } from '../types'
17
+ import type {
18
+ FetcherAfterResponseHook,
19
+ FetcherBeforeRequestHook,
20
+ FetcherBeforeRetryHook,
21
+ FetcherCfg,
22
+ FetcherNormalizedCfg,
23
+ FetcherOptions,
24
+ FetcherRequest,
25
+ FetcherResponse,
26
+ FetcherRetryOptions,
27
+ } from './fetcher.model'
20
28
  import { HTTP_METHODS } from './http.model'
21
- import type { HttpMethod, HttpStatusFamily } from './http.model'
22
-
23
- export interface FetcherNormalizedCfg extends Required<FetcherCfg>, FetcherRequest {
24
- logger: CommonLogger
25
- searchParams: Record<string, any>
26
- }
27
-
28
- export type FetcherBeforeRequestHook = (req: FetcherRequest) => Promisable<void>
29
- export type FetcherAfterResponseHook = (res: FetcherResponse) => Promisable<void>
30
- export type FetcherBeforeRetryHook = (res: FetcherResponse) => Promisable<void>
31
-
32
- export interface FetcherCfg {
33
- /**
34
- * Should **not** contain trailing slash.
35
- */
36
- baseUrl?: string
37
-
38
- /**
39
- * Default rule is that you **are allowed** to mutate req, res, res.retryStatus
40
- * properties of hook function arguments.
41
- * If you throw an error from the hook - it will be re-thrown as-is.
42
- */
43
- hooks?: {
44
- /**
45
- * Allows to mutate req.
46
- */
47
- beforeRequest?: FetcherBeforeRequestHook[]
48
- /**
49
- * Allows to mutate res.
50
- * If you set `res.err` - it will be thrown.
51
- */
52
- afterResponse?: FetcherAfterResponseHook[]
53
- /**
54
- * Allows to mutate res.retryStatus to override retry behavior.
55
- */
56
- beforeRetry?: FetcherBeforeRetryHook[]
57
- }
58
-
59
- /**
60
- * If true - enables all possible logging.
61
- */
62
- debug?: boolean
63
- logRequest?: boolean
64
- logRequestBody?: boolean
65
- logResponse?: boolean
66
- logResponseBody?: boolean
67
-
68
- /**
69
- * Defaults to `console`.
70
- */
71
- logger?: CommonLogger
72
- }
73
-
74
- export interface FetcherRetryStatus {
75
- retryAttempt: number
76
- retryTimeout: number
77
- retryStopped: boolean
78
- }
79
-
80
- export interface FetcherRetryOptions {
81
- count: number
82
- timeout: number
83
- timeoutMax: number
84
- timeoutMultiplier: number
85
- }
86
-
87
- export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers'> {
88
- url: string
89
- init: RequestInitNormalized
90
- mode: FetcherMode
91
- throwHttpErrors: boolean
92
- timeoutSeconds: number
93
- retry: FetcherRetryOptions
94
- retryPost: boolean
95
- retry4xx: boolean
96
- retry5xx: boolean
97
- }
98
-
99
- export interface FetcherOptions {
100
- method?: HttpMethod
101
- throwHttpErrors?: boolean
102
- /**
103
- * Default: 30.
104
- *
105
- * Timeout applies to both get the response and retrieve the body (e.g `await res.json()`),
106
- * so both should finish within this single timeout (not each).
107
- */
108
- timeoutSeconds?: number
109
-
110
- json?: any
111
- text?: string
112
- /**
113
- * Supports all the types that RequestInit.body supports.
114
- *
115
- * Useful when you want to e.g pass FormData.
116
- */
117
- body?: Blob | BufferSource | FormData | URLSearchParams | string
118
-
119
- credentials?: RequestCredentials
120
- /**
121
- * Default to true.
122
- */
123
- followRedirects?: boolean
124
-
125
- // Removing RequestInit from options to simplify FetcherOptions interface.
126
- // Will instead only add hand-picked useful options, such as `credentials`.
127
- // init?: Partial<RequestInitNormalized>
128
-
129
- headers?: Record<string, any>
130
- mode?: FetcherMode // default to 'void'
131
-
132
- searchParams?: Record<string, any>
133
-
134
- /**
135
- * Default is 2 retries (3 tries in total).
136
- * Pass `retry: { count: 0 }` to disable retries.
137
- */
138
- retry?: Partial<FetcherRetryOptions>
139
-
140
- /**
141
- * Defaults to false.
142
- * Set to true to allow retrying `post` requests.
143
- */
144
- retryPost?: boolean
145
- /**
146
- * Defaults to false.
147
- */
148
- retry4xx?: boolean
149
- /**
150
- * Defaults to true.
151
- */
152
- retry5xx?: boolean
153
-
154
- jsonReviver?: Reviver
155
- }
156
-
157
- export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
158
- method: HttpMethod
159
- headers: Record<string, any>
160
- }
161
-
162
- export interface FetcherSuccessResponse<BODY = unknown> extends FetcherResponse<BODY> {
163
- err?: undefined
164
- fetchResponse: Response
165
- body: BODY
166
- }
167
-
168
- export interface FetcherErrorResponse<BODY = unknown> extends FetcherResponse<BODY> {
169
- err: Error
170
- }
171
-
172
- export interface FetcherResponse<BODY = unknown> {
173
- err?: Error
174
- req: FetcherRequest
175
- fetchResponse?: Response
176
- statusFamily?: HttpStatusFamily
177
- body?: BODY
178
- retryStatus: FetcherRetryStatus
179
- }
180
-
181
- export type FetcherMode = 'json' | 'text' | 'void'
29
+ import type { HttpStatusFamily } from './http.model'
182
30
 
183
31
  const defRetryOptions: FetcherRetryOptions = {
184
32
  count: 2,
@@ -288,7 +136,7 @@ export class Fetcher {
288
136
  if (res.req.throwHttpErrors) throw res.err
289
137
  return res as any
290
138
  }
291
- return res.body!
139
+ return res.body
292
140
  }
293
141
 
294
142
  /**
@@ -322,16 +170,17 @@ export class Fetcher {
322
170
  await hook(req)
323
171
  }
324
172
 
325
- const res: FetcherResponse<any> = {
173
+ const res = {
326
174
  req,
327
175
  retryStatus: {
328
176
  retryAttempt: 0,
329
177
  retryStopped: false,
330
178
  retryTimeout: req.retry.timeout,
331
179
  },
332
- }
180
+ } as FetcherResponse<any>
333
181
 
334
- const shortUrl = this.getShortUrl(req.url)
182
+ const fullUrl = new URL(req.url)
183
+ const shortUrl = this.getShortUrl(fullUrl)
335
184
  const signature = [method.toUpperCase(), shortUrl].join(' ')
336
185
 
337
186
  /* eslint-disable no-await-in-loop */
@@ -352,9 +201,11 @@ export class Fetcher {
352
201
 
353
202
  try {
354
203
  res.fetchResponse = await globalThis.fetch(req.url, req.init)
204
+ res.ok = res.fetchResponse.ok
355
205
  } catch (err) {
356
206
  // For example, CORS error would result in "TypeError: failed to fetch" here
357
207
  res.err = err as Error
208
+ res.ok = false
358
209
  }
359
210
  res.statusFamily = this.getStatusFamily(res)
360
211
 
@@ -367,6 +218,7 @@ export class Fetcher {
367
218
  try {
368
219
  res.body = JSON.parse(text, req.jsonReviver)
369
220
  } catch (err) {
221
+ res.ok = false
370
222
  res.err = _anyToError(
371
223
  err,
372
224
  HttpError,
@@ -514,11 +366,25 @@ export class Fetcher {
514
366
  /**
515
367
  * Returns url without baseUrl and before ?queryString
516
368
  */
517
- private getShortUrl(url: string): string {
369
+ private getShortUrl(url: URL): string {
518
370
  const { baseUrl } = this.cfg
519
- if (!baseUrl) return url
520
371
 
521
- return url.split('?')[0]!.slice(baseUrl.length)
372
+ if (url.password) {
373
+ url = new URL(url.toString()) // prevent original url mutation
374
+ url.password = '[redacted]'
375
+ }
376
+
377
+ let shortUrl = url.toString()
378
+
379
+ if (!this.cfg.logWithSearchParams) {
380
+ shortUrl = shortUrl.split('?')[0]!
381
+ }
382
+
383
+ if (!this.cfg.logWithPrefixUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
384
+ shortUrl = shortUrl.slice(baseUrl.length)
385
+ }
386
+
387
+ return shortUrl
522
388
  }
523
389
 
524
390
  private normalizeCfg(cfg: FetcherCfg & FetcherOptions): FetcherNormalizedCfg {
@@ -545,6 +411,8 @@ export class Fetcher {
545
411
  logRequestBody: debug,
546
412
  logResponse: debug,
547
413
  logResponseBody: debug,
414
+ logWithPrefixUrl: true,
415
+ logWithSearchParams: true,
548
416
  retry: { ...defRetryOptions },
549
417
  init: {
550
418
  method: cfg.method || 'get',
package/src/index.ts CHANGED
@@ -76,5 +76,6 @@ export * from './datetime/dateInterval'
76
76
  export * from './datetime/timeInterval'
77
77
  export * from './http/http.model'
78
78
  export * from './http/fetcher'
79
+ export * from './http/fetcher.model'
79
80
 
80
81
  export { is }