@naturalcycles/js-lib 14.146.0 → 14.147.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,5 +1,5 @@
1
1
  /// <reference lib="dom" />
2
- import type { FetcherAfterResponseHook, FetcherBeforeRequestHook, FetcherBeforeRetryHook, FetcherCfg, FetcherNormalizedCfg, FetcherOptions, FetcherRequest, FetcherResponse } from './fetcher.model';
2
+ import type { FetcherAfterResponseHook, FetcherBeforeRequestHook, FetcherBeforeRetryHook, FetcherCfg, FetcherNormalizedCfg, FetcherOptions, FetcherResponse } from './fetcher.model';
3
3
  /**
4
4
  * Experimental wrapper around Fetch.
5
5
  * Works in both Browser and Node, using `globalThis.fetch`.
@@ -37,14 +37,13 @@ export declare class Fetcher {
37
37
  * https://css-tricks.com/web-streams-everywhere-and-fetch-for-node-js/
38
38
  */
39
39
  getReadableStream(url: string, opt?: FetcherOptions<ReadableStream<Uint8Array>>): Promise<ReadableStream<Uint8Array>>;
40
- fetch<T = unknown>(url: string, opt?: FetcherOptions<T>): Promise<T>;
40
+ fetch<T = unknown>(opt: FetcherOptions<T>): Promise<T>;
41
41
  /**
42
42
  * Returns FetcherResponse.
43
43
  * Never throws, returns `err` property in the response instead.
44
44
  * Use this method instead of `throwHttpErrors: false` or try-catching.
45
45
  */
46
- doFetch<T = unknown>(url: string, opt?: FetcherOptions<T>): Promise<FetcherResponse<T>>;
47
- doFetchRequest<T = unknown>(req: FetcherRequest<T>): Promise<FetcherResponse<T>>;
46
+ doFetch<T = unknown>(opt: FetcherOptions<T>): Promise<FetcherResponse<T>>;
48
47
  private onOkResponse;
49
48
  /**
50
49
  * This method exists to be able to easily mock it.
@@ -31,7 +31,8 @@ class Fetcher {
31
31
  http_model_1.HTTP_METHODS.forEach(method => {
32
32
  const m = method.toLowerCase();
33
33
  this[`${m}Void`] = async (url, opt) => {
34
- return await this.fetch(url, {
34
+ return await this.fetch({
35
+ url,
35
36
  method,
36
37
  mode: 'void',
37
38
  ...opt,
@@ -41,14 +42,16 @@ class Fetcher {
41
42
  return // mode=text
42
43
  ;
43
44
  this[`${m}Text`] = async (url, opt) => {
44
- return await this.fetch(url, {
45
+ return await this.fetch({
46
+ url,
45
47
  method,
46
48
  mode: 'text',
47
49
  ...opt,
48
50
  });
49
51
  };
50
52
  this[m] = async (url, opt) => {
51
- return await this.fetch(url, {
53
+ return await this.fetch({
54
+ url,
52
55
  method,
53
56
  mode: 'json',
54
57
  ...opt,
@@ -85,13 +88,14 @@ class Fetcher {
85
88
  * https://css-tricks.com/web-streams-everywhere-and-fetch-for-node-js/
86
89
  */
87
90
  async getReadableStream(url, opt) {
88
- return await this.fetch(url, {
91
+ return await this.fetch({
92
+ url,
89
93
  mode: 'readableStream',
90
94
  ...opt,
91
95
  });
92
96
  }
93
- async fetch(url, opt) {
94
- const res = await this.doFetch(url, opt);
97
+ async fetch(opt) {
98
+ const res = await this.doFetch(opt);
95
99
  if (res.err) {
96
100
  if (res.req.throwHttpErrors)
97
101
  throw res.err;
@@ -104,11 +108,8 @@ class Fetcher {
104
108
  * Never throws, returns `err` property in the response instead.
105
109
  * Use this method instead of `throwHttpErrors: false` or try-catching.
106
110
  */
107
- async doFetch(url, opt = {}) {
108
- const req = this.normalizeOptions(url, opt);
109
- return await this.doFetchRequest(req);
110
- }
111
- async doFetchRequest(req) {
111
+ async doFetch(opt) {
112
+ const req = this.normalizeOptions(opt);
112
113
  const { logger } = this.cfg;
113
114
  const { timeoutSeconds, init: { method }, } = req;
114
115
  // setup timeout
@@ -123,9 +124,9 @@ class Fetcher {
123
124
  for (const hook of this.cfg.hooks.beforeRequest || []) {
124
125
  await hook(req);
125
126
  }
126
- const isFullUrl = req.url.includes('://');
127
- const fullUrl = isFullUrl ? new URL(req.url) : undefined;
128
- const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.url;
127
+ const isFullUrl = req.fullUrl.includes('://');
128
+ const fullUrl = isFullUrl ? new URL(req.fullUrl) : undefined;
129
+ const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.fullUrl;
129
130
  const signature = [method, shortUrl].join(' ');
130
131
  const res = {
131
132
  req,
@@ -148,7 +149,7 @@ class Fetcher {
148
149
  }
149
150
  }
150
151
  try {
151
- res.fetchResponse = await this.callNativeFetch(req.url, req.init);
152
+ res.fetchResponse = await this.callNativeFetch(req.fullUrl, req.init);
152
153
  res.ok = res.fetchResponse.ok;
153
154
  }
154
155
  catch (err) {
@@ -171,9 +172,9 @@ class Fetcher {
171
172
  await hook(res);
172
173
  }
173
174
  if (req.paginate && res.ok) {
174
- const proceed = await req.paginate(req, res);
175
- if (proceed) {
176
- return await this.doFetchRequest(req);
175
+ const proceeed = await req.paginate(res, opt);
176
+ if (proceeed) {
177
+ return await this.doFetch(opt);
177
178
  }
178
179
  }
179
180
  return res;
@@ -280,7 +281,7 @@ class Fetcher {
280
281
  // Enabled, cause `data` is not printed by default when error is HttpError
281
282
  // method: req.method,
282
283
  // tryCount: req.tryCount,
283
- requestUrl: res.req.url,
284
+ requestUrl: res.req.fullUrl,
284
285
  requestBaseUrl: this.cfg.baseUrl || null,
285
286
  requestMethod: res.req.init.method,
286
287
  requestSignature: res.signature,
@@ -387,7 +388,7 @@ class Fetcher {
387
388
  const { debug = false } = cfg;
388
389
  const norm = (0, object_util_1._merge)({
389
390
  baseUrl: '',
390
- url: '',
391
+ inputUrl: '',
391
392
  mode: 'void',
392
393
  searchParams: {},
393
394
  timeoutSeconds: 30,
@@ -415,12 +416,11 @@ class Fetcher {
415
416
  norm.init.headers = (0, object_util_1._mapKeys)(norm.init.headers, k => k.toLowerCase());
416
417
  return norm;
417
418
  }
418
- normalizeOptions(url, opt) {
419
+ normalizeOptions(opt) {
419
420
  const { timeoutSeconds, throwHttpErrors, retryPost, retry4xx, retry5xx, retry, mode, jsonReviver, } = this.cfg;
420
421
  const req = {
421
422
  started: Date.now(),
422
423
  mode,
423
- url,
424
424
  timeoutSeconds,
425
425
  throwHttpErrors,
426
426
  retryPost,
@@ -428,6 +428,8 @@ class Fetcher {
428
428
  retry5xx,
429
429
  jsonReviver,
430
430
  ...(0, object_util_1._omit)(opt, ['method', 'headers', 'credentials']),
431
+ inputUrl: opt.url || '',
432
+ fullUrl: opt.url || '',
431
433
  retry: {
432
434
  ...retry,
433
435
  ...(0, object_util_1._filterUndefinedValues)(opt.retry || {}),
@@ -444,11 +446,11 @@ class Fetcher {
444
446
  // setup url
445
447
  const baseUrl = opt.baseUrl || this.cfg.baseUrl;
446
448
  if (baseUrl) {
447
- if (url.startsWith('/')) {
449
+ if (req.fullUrl.startsWith('/')) {
448
450
  console.warn(`Fetcher: url should not start with / when baseUrl is specified`);
449
- url = url.slice(1);
451
+ req.fullUrl = req.fullUrl.slice(1);
450
452
  }
451
- req.url = `${baseUrl}/${url}`;
453
+ req.fullUrl = `${baseUrl}/${req.inputUrl}`;
452
454
  }
453
455
  const searchParams = (0, object_util_1._filterUndefinedValues)({
454
456
  ...this.cfg.searchParams,
@@ -456,7 +458,7 @@ class Fetcher {
456
458
  });
457
459
  if (Object.keys(searchParams).length) {
458
460
  const qs = new URLSearchParams(searchParams).toString();
459
- req.url += req.url.includes('?') ? '&' : '?' + qs;
461
+ req.fullUrl += req.fullUrl.includes('?') ? '&' : '?' + qs;
460
462
  }
461
463
  // setup request body
462
464
  if (opt.json !== undefined) {
@@ -2,7 +2,7 @@ import type { CommonLogger } from '../log/commonLogger';
2
2
  import type { Promisable } from '../typeFest';
3
3
  import type { Reviver, UnixTimestampMillisNumber } from '../types';
4
4
  import type { HttpMethod, HttpStatusFamily } from './http.model';
5
- export interface FetcherNormalizedCfg extends Required<FetcherCfg>, Omit<FetcherRequest, 'started'> {
5
+ export interface FetcherNormalizedCfg extends Required<FetcherCfg>, Omit<FetcherRequest, 'started' | 'fullUrl'> {
6
6
  logger: CommonLogger;
7
7
  searchParams: Record<string, any>;
8
8
  }
@@ -77,8 +77,16 @@ export interface FetcherRetryOptions {
77
77
  timeoutMax: number;
78
78
  timeoutMultiplier: number;
79
79
  }
80
- export interface FetcherRequest<BODY = unknown> extends Omit<FetcherOptions<BODY>, 'method' | 'headers' | 'baseUrl'> {
81
- url: string;
80
+ export interface FetcherRequest<BODY = unknown> extends Omit<FetcherOptions<BODY>, 'method' | 'headers' | 'baseUrl' | 'url'> {
81
+ /**
82
+ * inputUrl is only the part that was passed in the request,
83
+ * without baseUrl or searchParams.
84
+ */
85
+ inputUrl: string;
86
+ /**
87
+ * fullUrl includes baseUrl and searchParams.
88
+ */
89
+ fullUrl: string;
82
90
  init: RequestInitNormalized;
83
91
  mode: FetcherMode;
84
92
  throwHttpErrors: boolean;
@@ -91,6 +99,11 @@ export interface FetcherRequest<BODY = unknown> extends Omit<FetcherOptions<BODY
91
99
  }
92
100
  export interface FetcherOptions<BODY = unknown> {
93
101
  method?: HttpMethod;
102
+ /**
103
+ * If defined - this `url` will override the original given `url`.
104
+ * baseUrl (and searchParams) will still modify it.
105
+ */
106
+ url?: string;
94
107
  baseUrl?: string;
95
108
  throwHttpErrors?: boolean;
96
109
  /**
@@ -138,15 +151,18 @@ export interface FetcherOptions<BODY = unknown> {
138
151
  /**
139
152
  * Allows to walk over multiple pages of results.
140
153
  * Paginate take a function.
141
- * Function has access to FetcherRequest and FetcherResponse
154
+ * Function has access to FetcherResponse and FetcherOptions
142
155
  * and has to make a decision to continue pagination or not.
143
- * Return true to continue, false otherwise.
144
- * If continue - it is expected to modify/mutate the FetcherRequest, as it will be used
145
- * to request the next page.
156
+ *
157
+ * Return false to stop pagination.
158
+ * Return true to continue pagination.
159
+ * Feel free to mutate/modify opt (FetcherOptions), for example:
160
+ *
161
+ * opt.searchParams!['page']++
146
162
  *
147
163
  * @experimental
148
164
  */
149
- paginate?: (req: FetcherRequest<BODY>, res: FetcherSuccessResponse<BODY>) => Promisable<boolean>;
165
+ paginate?: (res: FetcherSuccessResponse<BODY>, opt: FetcherOptions<BODY>) => Promisable<boolean>;
150
166
  }
151
167
  export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
152
168
  method: HttpMethod;
@@ -28,16 +28,19 @@ export class Fetcher {
28
28
  HTTP_METHODS.forEach(method => {
29
29
  const m = method.toLowerCase();
30
30
  this[`${m}Void`] = async (url, opt) => {
31
- return await this.fetch(url, Object.assign({ method, mode: 'void' }, opt));
31
+ return await this.fetch(Object.assign({ url,
32
+ method, mode: 'void' }, opt));
32
33
  };
33
34
  if (method === 'HEAD')
34
35
  return // mode=text
35
36
  ;
36
37
  this[`${m}Text`] = async (url, opt) => {
37
- return await this.fetch(url, Object.assign({ method, mode: 'text' }, opt));
38
+ return await this.fetch(Object.assign({ url,
39
+ method, mode: 'text' }, opt));
38
40
  };
39
41
  this[m] = async (url, opt) => {
40
- return await this.fetch(url, Object.assign({ method, mode: 'json' }, opt));
42
+ return await this.fetch(Object.assign({ url,
43
+ method, mode: 'json' }, opt));
41
44
  };
42
45
  });
43
46
  }
@@ -73,10 +76,10 @@ export class Fetcher {
73
76
  * https://css-tricks.com/web-streams-everywhere-and-fetch-for-node-js/
74
77
  */
75
78
  async getReadableStream(url, opt) {
76
- return await this.fetch(url, Object.assign({ mode: 'readableStream' }, opt));
79
+ return await this.fetch(Object.assign({ url, mode: 'readableStream' }, opt));
77
80
  }
78
- async fetch(url, opt) {
79
- const res = await this.doFetch(url, opt);
81
+ async fetch(opt) {
82
+ const res = await this.doFetch(opt);
80
83
  if (res.err) {
81
84
  if (res.req.throwHttpErrors)
82
85
  throw res.err;
@@ -89,12 +92,9 @@ export class Fetcher {
89
92
  * Never throws, returns `err` property in the response instead.
90
93
  * Use this method instead of `throwHttpErrors: false` or try-catching.
91
94
  */
92
- async doFetch(url, opt = {}) {
93
- const req = this.normalizeOptions(url, opt);
94
- return await this.doFetchRequest(req);
95
- }
96
- async doFetchRequest(req) {
95
+ async doFetch(opt) {
97
96
  var _a;
97
+ const req = this.normalizeOptions(opt);
98
98
  const { logger } = this.cfg;
99
99
  const { timeoutSeconds, init: { method }, } = req;
100
100
  // setup timeout
@@ -109,9 +109,9 @@ export class Fetcher {
109
109
  for (const hook of this.cfg.hooks.beforeRequest || []) {
110
110
  await hook(req);
111
111
  }
112
- const isFullUrl = req.url.includes('://');
113
- const fullUrl = isFullUrl ? new URL(req.url) : undefined;
114
- const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.url;
112
+ const isFullUrl = req.fullUrl.includes('://');
113
+ const fullUrl = isFullUrl ? new URL(req.fullUrl) : undefined;
114
+ const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.fullUrl;
115
115
  const signature = [method, shortUrl].join(' ');
116
116
  const res = {
117
117
  req,
@@ -134,7 +134,7 @@ export class Fetcher {
134
134
  }
135
135
  }
136
136
  try {
137
- res.fetchResponse = await this.callNativeFetch(req.url, req.init);
137
+ res.fetchResponse = await this.callNativeFetch(req.fullUrl, req.init);
138
138
  res.ok = res.fetchResponse.ok;
139
139
  }
140
140
  catch (err) {
@@ -157,9 +157,9 @@ export class Fetcher {
157
157
  await hook(res);
158
158
  }
159
159
  if (req.paginate && res.ok) {
160
- const proceed = await req.paginate(req, res);
161
- if (proceed) {
162
- return await this.doFetchRequest(req);
160
+ const proceeed = await req.paginate(res, opt);
161
+ if (proceeed) {
162
+ return await this.doFetch(opt);
163
163
  }
164
164
  }
165
165
  return res;
@@ -267,7 +267,7 @@ export class Fetcher {
267
267
  // Enabled, cause `data` is not printed by default when error is HttpError
268
268
  // method: req.method,
269
269
  // tryCount: req.tryCount,
270
- requestUrl: res.req.url,
270
+ requestUrl: res.req.fullUrl,
271
271
  requestBaseUrl: this.cfg.baseUrl || null,
272
272
  requestMethod: res.req.init.method,
273
273
  requestSignature: res.signature,
@@ -378,7 +378,7 @@ export class Fetcher {
378
378
  const { debug = false } = cfg;
379
379
  const norm = _merge({
380
380
  baseUrl: '',
381
- url: '',
381
+ inputUrl: '',
382
382
  mode: 'void',
383
383
  searchParams: {},
384
384
  timeoutSeconds: 30,
@@ -406,32 +406,31 @@ export class Fetcher {
406
406
  norm.init.headers = _mapKeys(norm.init.headers, k => k.toLowerCase());
407
407
  return norm;
408
408
  }
409
- normalizeOptions(url, opt) {
409
+ normalizeOptions(opt) {
410
410
  var _a, _b;
411
411
  const { timeoutSeconds, throwHttpErrors, retryPost, retry4xx, retry5xx, retry, mode, jsonReviver, } = this.cfg;
412
412
  const req = Object.assign(Object.assign({ started: Date.now(), mode,
413
- url,
414
413
  timeoutSeconds,
415
414
  throwHttpErrors,
416
415
  retryPost,
417
416
  retry4xx,
418
417
  retry5xx,
419
- jsonReviver }, _omit(opt, ['method', 'headers', 'credentials'])), { retry: Object.assign(Object.assign({}, retry), _filterUndefinedValues(opt.retry || {})), init: _merge(Object.assign(Object.assign({}, this.cfg.init), { method: opt.method || this.cfg.init.method, credentials: opt.credentials || this.cfg.init.credentials, redirect: ((_b = (_a = opt.followRedirects) !== null && _a !== void 0 ? _a : this.cfg.followRedirects) !== null && _b !== void 0 ? _b : true) ? 'follow' : 'error' }), {
418
+ jsonReviver }, _omit(opt, ['method', 'headers', 'credentials'])), { inputUrl: opt.url || '', fullUrl: opt.url || '', retry: Object.assign(Object.assign({}, retry), _filterUndefinedValues(opt.retry || {})), init: _merge(Object.assign(Object.assign({}, this.cfg.init), { method: opt.method || this.cfg.init.method, credentials: opt.credentials || this.cfg.init.credentials, redirect: ((_b = (_a = opt.followRedirects) !== null && _a !== void 0 ? _a : this.cfg.followRedirects) !== null && _b !== void 0 ? _b : true) ? 'follow' : 'error' }), {
420
419
  headers: _mapKeys(opt.headers || {}, k => k.toLowerCase()),
421
420
  }) });
422
421
  // setup url
423
422
  const baseUrl = opt.baseUrl || this.cfg.baseUrl;
424
423
  if (baseUrl) {
425
- if (url.startsWith('/')) {
424
+ if (req.fullUrl.startsWith('/')) {
426
425
  console.warn(`Fetcher: url should not start with / when baseUrl is specified`);
427
- url = url.slice(1);
426
+ req.fullUrl = req.fullUrl.slice(1);
428
427
  }
429
- req.url = `${baseUrl}/${url}`;
428
+ req.fullUrl = `${baseUrl}/${req.inputUrl}`;
430
429
  }
431
430
  const searchParams = _filterUndefinedValues(Object.assign(Object.assign({}, this.cfg.searchParams), opt.searchParams));
432
431
  if (Object.keys(searchParams).length) {
433
432
  const qs = new URLSearchParams(searchParams).toString();
434
- req.url += req.url.includes('?') ? '&' : '?' + qs;
433
+ req.fullUrl += req.fullUrl.includes('?') ? '&' : '?' + qs;
435
434
  }
436
435
  // setup request body
437
436
  if (opt.json !== undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.146.0",
3
+ "version": "14.147.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -5,7 +5,7 @@ import type { HttpMethod, HttpStatusFamily } from './http.model'
5
5
 
6
6
  export interface FetcherNormalizedCfg
7
7
  extends Required<FetcherCfg>,
8
- Omit<FetcherRequest, 'started'> {
8
+ Omit<FetcherRequest, 'started' | 'fullUrl'> {
9
9
  logger: CommonLogger
10
10
  searchParams: Record<string, any>
11
11
  }
@@ -97,8 +97,16 @@ export interface FetcherRetryOptions {
97
97
  }
98
98
 
99
99
  export interface FetcherRequest<BODY = unknown>
100
- extends Omit<FetcherOptions<BODY>, 'method' | 'headers' | 'baseUrl'> {
101
- url: string
100
+ extends Omit<FetcherOptions<BODY>, 'method' | 'headers' | 'baseUrl' | 'url'> {
101
+ /**
102
+ * inputUrl is only the part that was passed in the request,
103
+ * without baseUrl or searchParams.
104
+ */
105
+ inputUrl: string
106
+ /**
107
+ * fullUrl includes baseUrl and searchParams.
108
+ */
109
+ fullUrl: string
102
110
  init: RequestInitNormalized
103
111
  mode: FetcherMode
104
112
  throwHttpErrors: boolean
@@ -113,6 +121,12 @@ export interface FetcherRequest<BODY = unknown>
113
121
  export interface FetcherOptions<BODY = unknown> {
114
122
  method?: HttpMethod
115
123
 
124
+ /**
125
+ * If defined - this `url` will override the original given `url`.
126
+ * baseUrl (and searchParams) will still modify it.
127
+ */
128
+ url?: string
129
+
116
130
  baseUrl?: string
117
131
 
118
132
  throwHttpErrors?: boolean
@@ -173,15 +187,18 @@ export interface FetcherOptions<BODY = unknown> {
173
187
  /**
174
188
  * Allows to walk over multiple pages of results.
175
189
  * Paginate take a function.
176
- * Function has access to FetcherRequest and FetcherResponse
190
+ * Function has access to FetcherResponse and FetcherOptions
177
191
  * and has to make a decision to continue pagination or not.
178
- * Return true to continue, false otherwise.
179
- * If continue - it is expected to modify/mutate the FetcherRequest, as it will be used
180
- * to request the next page.
192
+ *
193
+ * Return false to stop pagination.
194
+ * Return true to continue pagination.
195
+ * Feel free to mutate/modify opt (FetcherOptions), for example:
196
+ *
197
+ * opt.searchParams!['page']++
181
198
  *
182
199
  * @experimental
183
200
  */
184
- paginate?: (req: FetcherRequest<BODY>, res: FetcherSuccessResponse<BODY>) => Promisable<boolean>
201
+ paginate?: (res: FetcherSuccessResponse<BODY>, opt: FetcherOptions<BODY>) => Promisable<boolean>
185
202
  }
186
203
 
187
204
  export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
@@ -57,7 +57,8 @@ export class Fetcher {
57
57
  url: string,
58
58
  opt?: FetcherOptions<void>,
59
59
  ): Promise<void> => {
60
- return await this.fetch<void>(url, {
60
+ return await this.fetch<void>({
61
+ url,
61
62
  method,
62
63
  mode: 'void',
63
64
  ...opt,
@@ -69,7 +70,8 @@ export class Fetcher {
69
70
  url: string,
70
71
  opt?: FetcherOptions<string>,
71
72
  ): Promise<string> => {
72
- return await this.fetch<string>(url, {
73
+ return await this.fetch<string>({
74
+ url,
73
75
  method,
74
76
  mode: 'text',
75
77
  ...opt,
@@ -78,7 +80,8 @@ export class Fetcher {
78
80
 
79
81
  // Default mode=json, but overridable
80
82
  ;(this as any)[m] = async <T = unknown>(url: string, opt?: FetcherOptions<T>): Promise<T> => {
81
- return await this.fetch<T>(url, {
83
+ return await this.fetch<T>({
84
+ url,
82
85
  method,
83
86
  mode: 'json',
84
87
  ...opt,
@@ -145,14 +148,15 @@ export class Fetcher {
145
148
  url: string,
146
149
  opt?: FetcherOptions<ReadableStream<Uint8Array>>,
147
150
  ): Promise<ReadableStream<Uint8Array>> {
148
- return await this.fetch(url, {
151
+ return await this.fetch({
152
+ url,
149
153
  mode: 'readableStream',
150
154
  ...opt,
151
155
  })
152
156
  }
153
157
 
154
- async fetch<T = unknown>(url: string, opt?: FetcherOptions<T>): Promise<T> {
155
- const res = await this.doFetch<T>(url, opt)
158
+ async fetch<T = unknown>(opt: FetcherOptions<T>): Promise<T> {
159
+ const res = await this.doFetch<T>(opt)
156
160
  if (res.err) {
157
161
  if (res.req.throwHttpErrors) throw res.err
158
162
  return res as any
@@ -165,15 +169,8 @@ export class Fetcher {
165
169
  * Never throws, returns `err` property in the response instead.
166
170
  * Use this method instead of `throwHttpErrors: false` or try-catching.
167
171
  */
168
- async doFetch<T = unknown>(
169
- url: string,
170
- opt: FetcherOptions<T> = {},
171
- ): Promise<FetcherResponse<T>> {
172
- const req = this.normalizeOptions(url, opt)
173
- return await this.doFetchRequest<T>(req)
174
- }
175
-
176
- async doFetchRequest<T = unknown>(req: FetcherRequest<T>): Promise<FetcherResponse<T>> {
172
+ async doFetch<T = unknown>(opt: FetcherOptions<T>): Promise<FetcherResponse<T>> {
173
+ const req = this.normalizeOptions(opt)
177
174
  const { logger } = this.cfg
178
175
  const {
179
176
  timeoutSeconds,
@@ -194,9 +191,9 @@ export class Fetcher {
194
191
  await hook(req)
195
192
  }
196
193
 
197
- const isFullUrl = req.url.includes('://')
198
- const fullUrl = isFullUrl ? new URL(req.url) : undefined
199
- const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.url
194
+ const isFullUrl = req.fullUrl.includes('://')
195
+ const fullUrl = isFullUrl ? new URL(req.fullUrl) : undefined
196
+ const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.fullUrl
200
197
  const signature = [method, shortUrl].join(' ')
201
198
 
202
199
  const res = {
@@ -225,7 +222,7 @@ export class Fetcher {
225
222
  }
226
223
 
227
224
  try {
228
- res.fetchResponse = await this.callNativeFetch(req.url, req.init)
225
+ res.fetchResponse = await this.callNativeFetch(req.fullUrl, req.init)
229
226
  res.ok = res.fetchResponse.ok
230
227
  } catch (err) {
231
228
  // For example, CORS error would result in "TypeError: failed to fetch" here
@@ -249,9 +246,9 @@ export class Fetcher {
249
246
  }
250
247
 
251
248
  if (req.paginate && res.ok) {
252
- const proceed = await req.paginate(req, res)
253
- if (proceed) {
254
- return await this.doFetchRequest(req)
249
+ const proceeed = await req.paginate(res, opt)
250
+ if (proceeed) {
251
+ return await this.doFetch(opt)
255
252
  }
256
253
  }
257
254
 
@@ -372,7 +369,7 @@ export class Fetcher {
372
369
  // Enabled, cause `data` is not printed by default when error is HttpError
373
370
  // method: req.method,
374
371
  // tryCount: req.tryCount,
375
- requestUrl: res.req.url,
372
+ requestUrl: res.req.fullUrl,
376
373
  requestBaseUrl: this.cfg.baseUrl || (null as any),
377
374
  requestMethod: res.req.init.method,
378
375
  requestSignature: res.signature,
@@ -495,7 +492,7 @@ export class Fetcher {
495
492
  const norm: FetcherNormalizedCfg = _merge(
496
493
  {
497
494
  baseUrl: '',
498
- url: '',
495
+ inputUrl: '',
499
496
  mode: 'void',
500
497
  searchParams: {},
501
498
  timeoutSeconds: 30,
@@ -528,7 +525,7 @@ export class Fetcher {
528
525
  return norm
529
526
  }
530
527
 
531
- private normalizeOptions<BODY>(url: string, opt: FetcherOptions<BODY>): FetcherRequest<BODY> {
528
+ private normalizeOptions<BODY>(opt: FetcherOptions<BODY>): FetcherRequest<BODY> {
532
529
  const {
533
530
  timeoutSeconds,
534
531
  throwHttpErrors,
@@ -543,7 +540,6 @@ export class Fetcher {
543
540
  const req: FetcherRequest<BODY> = {
544
541
  started: Date.now(),
545
542
  mode,
546
- url,
547
543
  timeoutSeconds,
548
544
  throwHttpErrors,
549
545
  retryPost,
@@ -551,6 +547,8 @@ export class Fetcher {
551
547
  retry5xx,
552
548
  jsonReviver,
553
549
  ..._omit(opt, ['method', 'headers', 'credentials']),
550
+ inputUrl: opt.url || '',
551
+ fullUrl: opt.url || '',
554
552
  retry: {
555
553
  ...retry,
556
554
  ..._filterUndefinedValues(opt.retry || {}),
@@ -571,11 +569,11 @@ export class Fetcher {
571
569
  // setup url
572
570
  const baseUrl = opt.baseUrl || this.cfg.baseUrl
573
571
  if (baseUrl) {
574
- if (url.startsWith('/')) {
572
+ if (req.fullUrl.startsWith('/')) {
575
573
  console.warn(`Fetcher: url should not start with / when baseUrl is specified`)
576
- url = url.slice(1)
574
+ req.fullUrl = req.fullUrl.slice(1)
577
575
  }
578
- req.url = `${baseUrl}/${url}`
576
+ req.fullUrl = `${baseUrl}/${req.inputUrl}`
579
577
  }
580
578
 
581
579
  const searchParams = _filterUndefinedValues({
@@ -585,7 +583,7 @@ export class Fetcher {
585
583
 
586
584
  if (Object.keys(searchParams).length) {
587
585
  const qs = new URLSearchParams(searchParams).toString()
588
- req.url += req.url.includes('?') ? '&' : '?' + qs
586
+ req.fullUrl += req.fullUrl.includes('?') ? '&' : '?' + qs
589
587
  }
590
588
 
591
589
  // setup request body