@naturalcycles/js-lib 14.144.1 → 14.146.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.
Files changed (40) hide show
  1. package/dist/array/array.util.js +2 -2
  2. package/dist/datetime/localDate.js +0 -1
  3. package/dist/datetime/localTime.js +0 -1
  4. package/dist/decorators/createPromiseDecorator.js +0 -1
  5. package/dist/http/fetcher.d.ts +27 -19
  6. package/dist/http/fetcher.js +60 -13
  7. package/dist/http/fetcher.model.d.ts +21 -8
  8. package/dist/object/object.util.js +0 -1
  9. package/dist/object/sortObjectDeep.js +0 -1
  10. package/dist/promise/abortable.d.ts +2 -2
  11. package/dist/promise/pMap.js +1 -1
  12. package/dist/string/leven.js +0 -1
  13. package/dist/string/string.util.js +0 -1
  14. package/dist/vendor/is.d.ts +1 -1
  15. package/dist/vendor/is.js +1 -7
  16. package/dist-esm/array/array.util.js +2 -2
  17. package/dist-esm/datetime/localDate.js +0 -1
  18. package/dist-esm/datetime/localTime.js +0 -1
  19. package/dist-esm/decorators/createPromiseDecorator.js +0 -1
  20. package/dist-esm/http/fetcher.js +61 -71
  21. package/dist-esm/object/object.util.js +0 -1
  22. package/dist-esm/object/sortObjectDeep.js +0 -1
  23. package/dist-esm/promise/pMap.js +15 -34
  24. package/dist-esm/string/leven.js +0 -1
  25. package/dist-esm/string/string.util.js +0 -1
  26. package/dist-esm/vendor/is.js +1 -7
  27. package/package.json +1 -1
  28. package/src/array/array.util.ts +2 -2
  29. package/src/datetime/localDate.ts +0 -2
  30. package/src/datetime/localTime.ts +0 -2
  31. package/src/decorators/createPromiseDecorator.ts +1 -1
  32. package/src/http/fetcher.model.ts +31 -8
  33. package/src/http/fetcher.ts +98 -46
  34. package/src/json-schema/jsonSchema.model.ts +0 -1
  35. package/src/object/object.util.ts +0 -1
  36. package/src/object/sortObjectDeep.ts +0 -1
  37. package/src/promise/pMap.ts +1 -1
  38. package/src/string/leven.ts +1 -1
  39. package/src/string/string.util.ts +0 -2
  40. package/src/vendor/is.ts +7 -10
@@ -1,5 +1,4 @@
1
1
  /// <reference lib="dom"/>
2
- import { __asyncValues } from "tslib";
3
2
  import { isServerSide } from '../env';
4
3
  import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util';
5
4
  import { HttpRequestError } from '../error/httpRequestError';
@@ -66,6 +65,16 @@ export class Fetcher {
66
65
  static create(cfg = {}) {
67
66
  return new Fetcher(cfg);
68
67
  }
68
+ // mode=readableStream
69
+ /**
70
+ * Returns raw fetchResponse.body, which is a ReadableStream<Uint8Array>
71
+ *
72
+ * More on streams and Node interop:
73
+ * https://css-tricks.com/web-streams-everywhere-and-fetch-for-node-js/
74
+ */
75
+ async getReadableStream(url, opt) {
76
+ return await this.fetch(url, Object.assign({ mode: 'readableStream' }, opt));
77
+ }
69
78
  async fetch(url, opt) {
70
79
  const res = await this.doFetch(url, opt);
71
80
  if (res.err) {
@@ -80,11 +89,13 @@ export class Fetcher {
80
89
  * Never throws, returns `err` property in the response instead.
81
90
  * Use this method instead of `throwHttpErrors: false` or try-catching.
82
91
  */
83
- async doFetch(url, rawOpt = {}) {
84
- var _a, e_1, _b, _c, _d, e_2, _e, _f;
85
- var _g;
92
+ async doFetch(url, opt = {}) {
93
+ const req = this.normalizeOptions(url, opt);
94
+ return await this.doFetchRequest(req);
95
+ }
96
+ async doFetchRequest(req) {
97
+ var _a;
86
98
  const { logger } = this.cfg;
87
- const req = this.normalizeOptions(url, rawOpt);
88
99
  const { timeoutSeconds, init: { method }, } = req;
89
100
  // setup timeout
90
101
  let timeout;
@@ -95,25 +106,8 @@ export class Fetcher {
95
106
  abortController.abort(`timeout of ${timeoutSeconds} sec`);
96
107
  }, timeoutSeconds * 1000);
97
108
  }
98
- try {
99
- for (var _h = true, _j = __asyncValues(this.cfg.hooks.beforeRequest || []), _k; _k = await _j.next(), _a = _k.done, !_a;) {
100
- _c = _k.value;
101
- _h = false;
102
- try {
103
- const hook = _c;
104
- await hook(req);
105
- }
106
- finally {
107
- _h = true;
108
- }
109
- }
110
- }
111
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
112
- finally {
113
- try {
114
- if (!_h && !_a && (_b = _j.return)) await _b.call(_j);
115
- }
116
- finally { if (e_1) throw e_1.error; }
109
+ for (const hook of this.cfg.hooks.beforeRequest || []) {
110
+ await hook(req);
117
111
  }
118
112
  const isFullUrl = req.url.includes('://');
119
113
  const fullUrl = isFullUrl ? new URL(req.url) : undefined;
@@ -129,7 +123,7 @@ export class Fetcher {
129
123
  signature,
130
124
  };
131
125
  while (!res.retryStatus.retryStopped) {
132
- const started = Date.now();
126
+ req.started = Date.now();
133
127
  if (this.cfg.logRequest) {
134
128
  const { retryAttempt } = res.retryStatus;
135
129
  logger.log([' >>', signature, retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`]
@@ -151,37 +145,26 @@ export class Fetcher {
151
145
  res.fetchResponse = undefined;
152
146
  }
153
147
  res.statusFamily = this.getStatusFamily(res);
154
- if ((_g = res.fetchResponse) === null || _g === void 0 ? void 0 : _g.ok) {
155
- await this.onOkResponse(res, started, timeout);
148
+ if ((_a = res.fetchResponse) === null || _a === void 0 ? void 0 : _a.ok) {
149
+ await this.onOkResponse(res, timeout);
156
150
  }
157
151
  else {
158
152
  // !res.ok
159
- await this.onNotOkResponse(res, started, timeout);
153
+ await this.onNotOkResponse(res, timeout);
160
154
  }
161
155
  }
162
- try {
163
- for (var _l = true, _m = __asyncValues(this.cfg.hooks.afterResponse || []), _o; _o = await _m.next(), _d = _o.done, !_d;) {
164
- _f = _o.value;
165
- _l = false;
166
- try {
167
- const hook = _f;
168
- await hook(res);
169
- }
170
- finally {
171
- _l = true;
172
- }
173
- }
156
+ for (const hook of this.cfg.hooks.afterResponse || []) {
157
+ await hook(res);
174
158
  }
175
- catch (e_2_1) { e_2 = { error: e_2_1 }; }
176
- finally {
177
- try {
178
- if (!_l && !_d && (_e = _m.return)) await _e.call(_m);
159
+ if (req.paginate && res.ok) {
160
+ const proceed = await req.paginate(req, res);
161
+ if (proceed) {
162
+ return await this.doFetchRequest(req);
179
163
  }
180
- finally { if (e_2) throw e_2.error; }
181
164
  }
182
165
  return res;
183
166
  }
184
- async onOkResponse(res, started, timeout) {
167
+ async onOkResponse(res, timeout) {
185
168
  const { req } = res;
186
169
  const { mode } = res.req;
187
170
  if (mode === 'json') {
@@ -204,7 +187,7 @@ export class Fetcher {
204
187
  // } satisfies HttpRequestErrorData)
205
188
  res.err = _anyToError(err);
206
189
  res.ok = false;
207
- return await this.onNotOkResponse(res, started, timeout);
190
+ return await this.onNotOkResponse(res, timeout);
208
191
  }
209
192
  }
210
193
  else {
@@ -227,6 +210,14 @@ export class Fetcher {
227
210
  else if (mode === 'blob') {
228
211
  res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
229
212
  }
213
+ else if (mode === 'readableStream') {
214
+ res.body = res.fetchResponse.body;
215
+ if (res.body === null) {
216
+ res.err = new Error(`fetchResponse.body is null`);
217
+ res.ok = false;
218
+ return await this.onNotOkResponse(res, timeout);
219
+ }
220
+ }
230
221
  clearTimeout(timeout);
231
222
  res.retryStatus.retryStopped = true;
232
223
  // res.err can happen on JSON.parse error
@@ -238,7 +229,7 @@ export class Fetcher {
238
229
  res.fetchResponse.status,
239
230
  res.signature,
240
231
  retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
241
- _since(started),
232
+ _since(res.req.started),
242
233
  ]
243
234
  .filter(Boolean)
244
235
  .join(' '));
@@ -253,7 +244,7 @@ export class Fetcher {
253
244
  async callNativeFetch(url, init) {
254
245
  return await globalThis.fetch(url, init);
255
246
  }
256
- async onNotOkResponse(res, started, timeout) {
247
+ async onNotOkResponse(res, timeout) {
257
248
  var _a, _b;
258
249
  clearTimeout(timeout);
259
250
  let cause;
@@ -280,35 +271,18 @@ export class Fetcher {
280
271
  requestBaseUrl: this.cfg.baseUrl || null,
281
272
  requestMethod: res.req.init.method,
282
273
  requestSignature: res.signature,
283
- requestDuration: Date.now() - started,
274
+ requestDuration: Date.now() - res.req.started,
284
275
  }), cause);
285
276
  await this.processRetry(res);
286
277
  }
287
278
  async processRetry(res) {
288
- var _a, e_3, _b, _c;
279
+ var _a;
289
280
  const { retryStatus } = res;
290
281
  if (!this.shouldRetry(res)) {
291
282
  retryStatus.retryStopped = true;
292
283
  }
293
- try {
294
- for (var _d = true, _e = __asyncValues(this.cfg.hooks.beforeRetry || []), _f; _f = await _e.next(), _a = _f.done, !_a;) {
295
- _c = _f.value;
296
- _d = false;
297
- try {
298
- const hook = _c;
299
- await hook(res);
300
- }
301
- finally {
302
- _d = true;
303
- }
304
- }
305
- }
306
- catch (e_3_1) { e_3 = { error: e_3_1 }; }
307
- finally {
308
- try {
309
- if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
310
- }
311
- finally { if (e_3) throw e_3.error; }
284
+ for (const hook of this.cfg.hooks.beforeRetry || []) {
285
+ await hook(res);
312
286
  }
313
287
  const { count, timeoutMultiplier, timeoutMax } = res.req.retry;
314
288
  if (retryStatus.retryAttempt >= count) {
@@ -316,6 +290,22 @@ export class Fetcher {
316
290
  }
317
291
  if (retryStatus.retryStopped)
318
292
  return;
293
+ // Here we know that more retries will be attempted
294
+ // We don't log "last error", because it will be thrown and logged by consumer,
295
+ // but we should log all previous errors, otherwise they are lost.
296
+ // Here is the right place where we know it's not the "last error"
297
+ if (res.err) {
298
+ const { retryAttempt } = retryStatus;
299
+ this.cfg.logger.error([
300
+ ' <<',
301
+ ((_a = res.fetchResponse) === null || _a === void 0 ? void 0 : _a.status) || 0,
302
+ res.signature,
303
+ `try#${retryAttempt + 1}/${count + 1}`,
304
+ _since(res.req.started),
305
+ ]
306
+ .filter(Boolean)
307
+ .join(' '), res.err.cause || res.err);
308
+ }
319
309
  retryStatus.retryAttempt++;
320
310
  retryStatus.retryTimeout = _clamp(retryStatus.retryTimeout * timeoutMultiplier, 0, timeoutMax);
321
311
  const noise = Math.random() * 500;
@@ -419,7 +409,7 @@ export class Fetcher {
419
409
  normalizeOptions(url, opt) {
420
410
  var _a, _b;
421
411
  const { timeoutSeconds, throwHttpErrors, retryPost, retry4xx, retry5xx, retry, mode, jsonReviver, } = this.cfg;
422
- const req = Object.assign(Object.assign({ mode,
412
+ const req = Object.assign(Object.assign({ started: Date.now(), mode,
423
413
  url,
424
414
  timeoutSeconds,
425
415
  throwHttpErrors,
@@ -102,7 +102,6 @@ export function _mapValues(obj, mapper, mutate = false) {
102
102
  * Does not support `mutate` flag.
103
103
  */
104
104
  export function _mapKeys(obj, mapper) {
105
- // eslint-disable-next-line unicorn/prefer-object-from-entries
106
105
  return Object.entries(obj).reduce((map, [k, v]) => {
107
106
  map[mapper(k, v, obj)] = v;
108
107
  return map;
@@ -5,7 +5,6 @@ import { _isObject } from '..';
5
5
  export function _sortObjectDeep(o) {
6
6
  // array
7
7
  if (Array.isArray(o)) {
8
- // eslint-disable-next-line unicorn/no-array-callback-reference
9
8
  return o.map(_sortObjectDeep);
10
9
  }
11
10
  if (_isObject(o)) {
@@ -6,7 +6,6 @@ Improvements:
6
6
  - Included Typescript typings (no need for @types/p-map)
7
7
  - Compatible with pProps (that had typings issues)
8
8
  */
9
- import { __asyncValues } from "tslib";
10
9
  import { END, ErrorMode, SKIP } from '..';
11
10
  /**
12
11
  * Returns a `Promise` that is fulfilled when all promises in `input` and ones returned from `mapper` are fulfilled,
@@ -35,7 +34,6 @@ import { END, ErrorMode, SKIP } from '..';
35
34
  * })();
36
35
  */
37
36
  export async function pMap(iterable, mapper, opt = {}) {
38
- var _a, e_1, _b, _c;
39
37
  const ret = [];
40
38
  // const iterator = iterable[Symbol.iterator]()
41
39
  const items = [...iterable];
@@ -49,40 +47,23 @@ export async function pMap(iterable, mapper, opt = {}) {
49
47
  let currentIndex = 0;
50
48
  // Special cases that are able to preserve async stack traces
51
49
  if (concurrency === 1) {
52
- try {
53
- // Special case for concurrency == 1
54
- for (var _d = true, items_1 = __asyncValues(items), items_1_1; items_1_1 = await items_1.next(), _a = items_1_1.done, !_a;) {
55
- _c = items_1_1.value;
56
- _d = false;
57
- try {
58
- const item = _c;
59
- try {
60
- const r = await mapper(item, currentIndex++);
61
- if (r === END)
62
- break;
63
- if (r !== SKIP)
64
- ret.push(r);
65
- }
66
- catch (err) {
67
- if (errorMode === ErrorMode.THROW_IMMEDIATELY)
68
- throw err;
69
- if (errorMode === ErrorMode.THROW_AGGREGATED) {
70
- errors.push(err);
71
- }
72
- // otherwise, suppress completely
73
- }
74
- }
75
- finally {
76
- _d = true;
77
- }
78
- }
79
- }
80
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
81
- finally {
50
+ // Special case for concurrency == 1
51
+ for (const item of items) {
82
52
  try {
83
- if (!_d && !_a && (_b = items_1.return)) await _b.call(items_1);
53
+ const r = await mapper(item, currentIndex++);
54
+ if (r === END)
55
+ break;
56
+ if (r !== SKIP)
57
+ ret.push(r);
58
+ }
59
+ catch (err) {
60
+ if (errorMode === ErrorMode.THROW_IMMEDIATELY)
61
+ throw err;
62
+ if (errorMode === ErrorMode.THROW_AGGREGATED) {
63
+ errors.push(err);
64
+ }
65
+ // otherwise, suppress completely
84
66
  }
85
- finally { if (e_1) throw e_1.error; }
86
67
  }
87
68
  if (errors.length) {
88
69
  throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`);
@@ -56,7 +56,6 @@ export function _leven(first, second) {
56
56
  for (index = 0; index < firstLength; index++) {
57
57
  temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1;
58
58
  temporary = array[index];
59
- // eslint-disable-next-line no-multi-assign
60
59
  result = array[index] =
61
60
  temporary > result
62
61
  ? temporary2 > result
@@ -1,4 +1,3 @@
1
- /* eslint-disable unicorn/prefer-string-slice */
2
1
  /**
3
2
  * Converts the first character of string to upper case and the remaining to lower case.
4
3
  */
@@ -60,7 +60,6 @@ const primitiveTypeNames = [
60
60
  function isPrimitiveTypeName(name) {
61
61
  return primitiveTypeNames.includes(name);
62
62
  }
63
- // eslint-disable-next-line @typescript-eslint/ban-types
64
63
  function isOfType(type) {
65
64
  return (value) => typeof value === type;
66
65
  }
@@ -122,7 +121,6 @@ is.string = isOfType('string');
122
121
  const isNumberType = isOfType('number');
123
122
  is.number = (value) => isNumberType(value) && !is.nan(value);
124
123
  is.bigint = isOfType('bigint');
125
- // eslint-disable-next-line @typescript-eslint/ban-types
126
124
  is.function_ = isOfType('function');
127
125
  is.null_ = (value) => value === null;
128
126
  is.class_ = (value) => is.function_(value) && value.toString().startsWith('class ');
@@ -151,7 +149,6 @@ is.promise = (value) => is.nativePromise(value) || hasPromiseAPI(value);
151
149
  is.generatorFunction = isObjectOfType('GeneratorFunction');
152
150
  is.asyncGeneratorFunction = (value) => getObjectType(value) === 'AsyncGeneratorFunction';
153
151
  is.asyncFunction = (value) => getObjectType(value) === 'AsyncFunction';
154
- // eslint-disable-next-line no-prototype-builtins, @typescript-eslint/ban-types
155
152
  is.boundFunction = (value) => is.function_(value) && !value.hasOwnProperty('prototype');
156
153
  is.regExp = isObjectOfType('RegExp');
157
154
  is.date = isObjectOfType('Date');
@@ -181,7 +178,7 @@ is.urlString = (value) => {
181
178
  return false;
182
179
  }
183
180
  try {
184
- new URL(value); // eslint-disable-line no-new
181
+ new URL(value);
185
182
  return true;
186
183
  }
187
184
  catch (_a) {
@@ -341,7 +338,6 @@ export const assert = {
341
338
  string: (value) => assertType(is.string(value), 'string', value),
342
339
  number: (value) => assertType(is.number(value), 'number', value),
343
340
  bigint: (value) => assertType(is.bigint(value), 'bigint', value),
344
- // eslint-disable-next-line @typescript-eslint/ban-types
345
341
  function_: (value) => assertType(is.function_(value), 'Function', value),
346
342
  null_: (value) => assertType(is.null_(value), 'null', value),
347
343
  class_: (value) => assertType(is.class_(value), AssertionTypeDescription.class_, value),
@@ -366,9 +362,7 @@ export const assert = {
366
362
  promise: (value) => assertType(is.promise(value), 'Promise', value),
367
363
  generatorFunction: (value) => assertType(is.generatorFunction(value), 'GeneratorFunction', value),
368
364
  asyncGeneratorFunction: (value) => assertType(is.asyncGeneratorFunction(value), 'AsyncGeneratorFunction', value),
369
- // eslint-disable-next-line @typescript-eslint/ban-types
370
365
  asyncFunction: (value) => assertType(is.asyncFunction(value), 'AsyncFunction', value),
371
- // eslint-disable-next-line @typescript-eslint/ban-types
372
366
  boundFunction: (value) => assertType(is.boundFunction(value), 'Function', value),
373
367
  regExp: (value) => assertType(is.regExp(value), 'RegExp', value),
374
368
  date: (value) => assertType(is.date(value), 'Date', value),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.144.1",
3
+ "version": "14.146.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -149,7 +149,7 @@ export function _sortBy<T>(
149
149
  ): T[] {
150
150
  const mod = descending ? -1 : 1
151
151
  return (mutate ? items : [...items]).sort((_a, _b) => {
152
- const [a, b] = [_a, _b].map(mapper) // eslint-disable-line unicorn/no-array-callback-reference
152
+ const [a, b] = [_a, _b].map(mapper)
153
153
  if (typeof a === 'number' && typeof b === 'number') return (a - b) * mod
154
154
  return String(a).localeCompare(String(b)) * mod
155
155
  })
@@ -159,7 +159,7 @@ export function _sortBy<T>(
159
159
  * Like items.find(), but it tries to find from the END of the array.
160
160
  */
161
161
  export function _findLast<T>(items: T[], predicate: Predicate<T>): T | undefined {
162
- return [...items].reverse().find(predicate) // eslint-disable-line unicorn/no-array-callback-reference
162
+ return [...items].reverse().find(predicate)
163
163
  }
164
164
 
165
165
  export function _takeWhile<T>(items: T[], predicate: Predicate<T>): T[] {
@@ -17,8 +17,6 @@ const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
17
17
  export type LocalDateConfig = LocalDate | Date | IsoDateString
18
18
  export type LocalDateFormatter = (ld: LocalDate) => string
19
19
 
20
- /* eslint-disable no-dupe-class-members */
21
-
22
20
  /**
23
21
  * @experimental
24
22
  */
@@ -40,8 +40,6 @@ const SECONDS_IN_DAY = 86400
40
40
  // const MILLISECONDS_IN_MINUTE = 60000
41
41
  const VALID_DAYS_OF_WEEK = new Set([1, 2, 3, 4, 5, 6, 7])
42
42
 
43
- /* eslint-disable no-dupe-class-members */
44
-
45
43
  /**
46
44
  * @experimental
47
45
  */
@@ -52,7 +52,7 @@ export interface PromiseDecoratorResp<PARAMS> {
52
52
  *
53
53
  * @experimental
54
54
  */
55
- // eslint-disable-next-line @typescript-eslint/naming-convention
55
+
56
56
  export function _createPromiseDecorator<RES = any, PARAMS = any>(
57
57
  cfg: PromiseDecoratorCfg<RES, PARAMS>,
58
58
  decoratorParams: PARAMS = {} as any,
@@ -1,16 +1,24 @@
1
1
  import type { CommonLogger } from '../log/commonLogger'
2
2
  import type { Promisable } from '../typeFest'
3
- import type { Reviver } from '../types'
3
+ import type { Reviver, UnixTimestampMillisNumber } from '../types'
4
4
  import type { HttpMethod, HttpStatusFamily } from './http.model'
5
5
 
6
- export interface FetcherNormalizedCfg extends Required<FetcherCfg>, FetcherRequest {
6
+ export interface FetcherNormalizedCfg
7
+ extends Required<FetcherCfg>,
8
+ Omit<FetcherRequest, 'started'> {
7
9
  logger: CommonLogger
8
10
  searchParams: Record<string, any>
9
11
  }
10
12
 
11
- export type FetcherBeforeRequestHook = (req: FetcherRequest) => Promisable<void>
12
- export type FetcherAfterResponseHook = (res: FetcherResponse) => Promisable<void>
13
- export type FetcherBeforeRetryHook = (res: FetcherResponse) => Promisable<void>
13
+ export type FetcherBeforeRequestHook = <BODY = unknown>(
14
+ req: FetcherRequest<BODY>,
15
+ ) => Promisable<void>
16
+ export type FetcherAfterResponseHook = <BODY = unknown>(
17
+ res: FetcherResponse<BODY>,
18
+ ) => Promisable<void>
19
+ export type FetcherBeforeRetryHook = <BODY = unknown>(
20
+ res: FetcherResponse<BODY>,
21
+ ) => Promisable<void>
14
22
 
15
23
  export interface FetcherCfg {
16
24
  /**
@@ -88,7 +96,8 @@ export interface FetcherRetryOptions {
88
96
  timeoutMultiplier: number
89
97
  }
90
98
 
91
- export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers' | 'baseUrl'> {
99
+ export interface FetcherRequest<BODY = unknown>
100
+ extends Omit<FetcherOptions<BODY>, 'method' | 'headers' | 'baseUrl'> {
92
101
  url: string
93
102
  init: RequestInitNormalized
94
103
  mode: FetcherMode
@@ -98,9 +107,10 @@ export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers
98
107
  retryPost: boolean
99
108
  retry4xx: boolean
100
109
  retry5xx: boolean
110
+ started: UnixTimestampMillisNumber
101
111
  }
102
112
 
103
- export interface FetcherOptions {
113
+ export interface FetcherOptions<BODY = unknown> {
104
114
  method?: HttpMethod
105
115
 
106
116
  baseUrl?: string
@@ -159,6 +169,19 @@ export interface FetcherOptions {
159
169
  retry5xx?: boolean
160
170
 
161
171
  jsonReviver?: Reviver
172
+
173
+ /**
174
+ * Allows to walk over multiple pages of results.
175
+ * Paginate take a function.
176
+ * Function has access to FetcherRequest and FetcherResponse
177
+ * 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.
181
+ *
182
+ * @experimental
183
+ */
184
+ paginate?: (req: FetcherRequest<BODY>, res: FetcherSuccessResponse<BODY>) => Promisable<boolean>
162
185
  }
163
186
 
164
187
  export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
@@ -192,4 +215,4 @@ export type FetcherResponse<BODY = unknown> =
192
215
  | FetcherSuccessResponse<BODY>
193
216
  | FetcherErrorResponse<BODY>
194
217
 
195
- export type FetcherMode = 'json' | 'text' | 'void' | 'arrayBuffer' | 'blob'
218
+ export type FetcherMode = 'json' | 'text' | 'void' | 'arrayBuffer' | 'blob' | 'readableStream'