@naturalcycles/js-lib 14.156.0 → 14.157.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.
@@ -0,0 +1,9 @@
1
+ import type { AnyObject } from './types';
2
+ /**
3
+ * Convert any object to FormData.
4
+ * Please note that every key and value of FormData is `string`.
5
+ * Even if you pass a number - it'll be converted to string.
6
+ * Think URLSearchParams.
7
+ */
8
+ export declare function objectToFormData(obj?: AnyObject): FormData;
9
+ export declare function formDataToObject<T extends AnyObject>(formData: FormData): T;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formDataToObject = exports.objectToFormData = void 0;
4
+ /**
5
+ * Convert any object to FormData.
6
+ * Please note that every key and value of FormData is `string`.
7
+ * Even if you pass a number - it'll be converted to string.
8
+ * Think URLSearchParams.
9
+ */
10
+ function objectToFormData(obj = {}) {
11
+ const fd = new FormData();
12
+ Object.entries(obj).forEach(([k, v]) => fd.append(k, v));
13
+ return fd;
14
+ }
15
+ exports.objectToFormData = objectToFormData;
16
+ function formDataToObject(formData) {
17
+ return Object.fromEntries(formData);
18
+ }
19
+ exports.formDataToObject = formDataToObject;
@@ -1,4 +1,5 @@
1
1
  /// <reference lib="dom" />
2
+ /// <reference lib="dom.iterable" />
2
3
  import type { FetcherAfterResponseHook, FetcherBeforeRequestHook, FetcherBeforeRetryHook, FetcherCfg, FetcherNormalizedCfg, FetcherOptions, FetcherResponse } from './fetcher.model';
3
4
  /**
4
5
  * Experimental wrapper around Fetch.
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  /// <reference lib="dom"/>
3
+ /// <reference lib="dom.iterable"/>
3
4
  Object.defineProperty(exports, "__esModule", { value: true });
4
5
  exports.getFetcher = exports.Fetcher = void 0;
5
6
  const env_1 = require("../env");
@@ -13,6 +14,14 @@ const json_util_1 = require("../string/json.util");
13
14
  const stringifyAny_1 = require("../string/stringifyAny");
14
15
  const time_util_1 = require("../time/time.util");
15
16
  const http_model_1 = require("./http.model");
17
+ const acceptByResponseType = {
18
+ text: 'text/plain',
19
+ json: 'application/json',
20
+ void: '*/*',
21
+ readableStream: 'application/octet-stream',
22
+ arrayBuffer: 'application/octet-stream',
23
+ blob: 'application/octet-stream',
24
+ };
16
25
  const defRetryOptions = {
17
26
  count: 2,
18
27
  timeout: 1000,
@@ -36,18 +45,18 @@ class Fetcher {
36
45
  return await this.fetch({
37
46
  url,
38
47
  method,
39
- mode: 'void',
48
+ responseType: 'void',
40
49
  ...opt,
41
50
  });
42
51
  };
43
52
  if (method === 'HEAD')
44
- return // mode=text
53
+ return // responseType=text
45
54
  ;
46
55
  this[`${m}Text`] = async (url, opt) => {
47
56
  return await this.fetch({
48
57
  url,
49
58
  method,
50
- mode: 'text',
59
+ responseType: 'text',
51
60
  ...opt,
52
61
  });
53
62
  };
@@ -55,7 +64,7 @@ class Fetcher {
55
64
  return await this.fetch({
56
65
  url,
57
66
  method,
58
- mode: 'json',
67
+ responseType: 'json',
59
68
  ...opt,
60
69
  });
61
70
  };
@@ -82,7 +91,7 @@ class Fetcher {
82
91
  static create(cfg = {}) {
83
92
  return new Fetcher(cfg);
84
93
  }
85
- // mode=readableStream
94
+ // responseType=readableStream
86
95
  /**
87
96
  * Returns raw fetchResponse.body, which is a ReadableStream<Uint8Array>
88
97
  *
@@ -92,7 +101,7 @@ class Fetcher {
92
101
  async getReadableStream(url, opt) {
93
102
  return await this.fetch({
94
103
  url,
95
- mode: 'readableStream',
104
+ responseType: 'readableStream',
96
105
  ...opt,
97
106
  });
98
107
  }
@@ -183,8 +192,8 @@ class Fetcher {
183
192
  }
184
193
  async onOkResponse(res, timeout) {
185
194
  const { req } = res;
186
- const { mode } = res.req;
187
- if (mode === 'json') {
195
+ const { responseType } = res.req;
196
+ if (responseType === 'json') {
188
197
  if (res.fetchResponse.body) {
189
198
  const text = await res.fetchResponse.text();
190
199
  if (text) {
@@ -218,16 +227,16 @@ class Fetcher {
218
227
  res.body = {};
219
228
  }
220
229
  }
221
- else if (mode === 'text') {
230
+ else if (responseType === 'text') {
222
231
  res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
223
232
  }
224
- else if (mode === 'arrayBuffer') {
233
+ else if (responseType === 'arrayBuffer') {
225
234
  res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
226
235
  }
227
- else if (mode === 'blob') {
236
+ else if (responseType === 'blob') {
228
237
  res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
229
238
  }
230
- else if (mode === 'readableStream') {
239
+ else if (responseType === 'readableStream') {
231
240
  res.body = res.fetchResponse.body;
232
241
  if (res.body === null) {
233
242
  res.err = new Error(`fetchResponse.body is null`);
@@ -429,7 +438,7 @@ class Fetcher {
429
438
  const norm = (0, object_util_1._merge)({
430
439
  baseUrl: '',
431
440
  inputUrl: '',
432
- mode: 'void',
441
+ responseType: 'void',
433
442
  searchParams: {},
434
443
  timeoutSeconds: 30,
435
444
  retryPost: false,
@@ -448,7 +457,10 @@ class Fetcher {
448
457
  retry: { ...defRetryOptions },
449
458
  init: {
450
459
  method: cfg.method || 'GET',
451
- headers: cfg.headers || {},
460
+ headers: {
461
+ 'user-agent': 'fetcher',
462
+ ...cfg.headers,
463
+ },
452
464
  credentials: cfg.credentials,
453
465
  redirect: cfg.redirect,
454
466
  },
@@ -464,7 +476,7 @@ class Fetcher {
464
476
  'retryPost',
465
477
  'retry4xx',
466
478
  'retry5xx',
467
- 'mode',
479
+ 'responseType',
468
480
  'jsonReviver',
469
481
  'logRequest',
470
482
  'logRequestBody',
@@ -514,9 +526,20 @@ class Fetcher {
514
526
  req.init.body = opt.text;
515
527
  req.init.headers['content-type'] = 'text/plain';
516
528
  }
529
+ else if (opt.form) {
530
+ if (opt.form instanceof URLSearchParams || opt.form instanceof FormData) {
531
+ req.init.body = opt.form;
532
+ }
533
+ else {
534
+ req.init.body = new URLSearchParams(opt.form);
535
+ }
536
+ req.init.headers['content-type'] = 'application/x-www-form-urlencoded';
537
+ }
517
538
  else if (opt.body !== undefined) {
518
539
  req.init.body = opt.body;
519
540
  }
541
+ // Unless `accept` header was already set - set it based on responseType
542
+ req.init.headers['accept'] ||= acceptByResponseType[req.responseType];
520
543
  return req;
521
544
  }
522
545
  }
@@ -1,6 +1,6 @@
1
1
  import type { CommonLogger } from '../log/commonLogger';
2
2
  import type { Promisable } from '../typeFest';
3
- import type { Reviver, UnixTimestampMillisNumber } from '../types';
3
+ import type { AnyObject, Reviver, UnixTimestampMillisNumber } from '../types';
4
4
  import type { HttpMethod, HttpStatusFamily } from './http.model';
5
5
  export interface FetcherNormalizedCfg extends Required<FetcherCfg>, Omit<FetcherRequest, 'started' | 'fullUrl' | 'logRequest' | 'logRequestBody' | 'logResponse' | 'logResponseBody' | 'redirect' | 'credentials'> {
6
6
  logger: CommonLogger;
@@ -88,7 +88,7 @@ export interface FetcherRequest extends Omit<FetcherOptions, 'method' | 'headers
88
88
  */
89
89
  fullUrl: string;
90
90
  init: RequestInitNormalized;
91
- mode: FetcherMode;
91
+ responseType: FetcherResponseType;
92
92
  timeoutSeconds: number;
93
93
  retry: FetcherRetryOptions;
94
94
  retryPost: boolean;
@@ -112,14 +112,29 @@ export interface FetcherOptions {
112
112
  * so both should finish within this single timeout (not each).
113
113
  */
114
114
  timeoutSeconds?: number;
115
- json?: any;
116
- text?: string;
117
115
  /**
118
116
  * Supports all the types that RequestInit.body supports.
119
117
  *
120
118
  * Useful when you want to e.g pass FormData.
121
119
  */
122
120
  body?: Blob | BufferSource | FormData | URLSearchParams | string;
121
+ /**
122
+ * Same as `body`, but also conveniently sets the
123
+ * Content-Type header to `text/plain`
124
+ */
125
+ text?: string;
126
+ /**
127
+ * Same as `body`, but:
128
+ * 1. JSON.stringifies the passed variable
129
+ * 2. Conveniently sets the Content-Type header to `application/json`
130
+ */
131
+ json?: any;
132
+ /**
133
+ * Same as `body`, but:
134
+ * 1. Transforms the passed plain js object into URLSearchParams and passes it to `body`
135
+ * 2. Conveniently sets the Content-Type header to `application/x-www-form-urlencoded`
136
+ */
137
+ form?: FormData | URLSearchParams | AnyObject;
123
138
  credentials?: RequestCredentials;
124
139
  /**
125
140
  * Default to 'follow'.
@@ -128,7 +143,7 @@ export interface FetcherOptions {
128
143
  */
129
144
  redirect?: RequestRedirect;
130
145
  headers?: Record<string, any>;
131
- mode?: FetcherMode;
146
+ responseType?: FetcherResponseType;
132
147
  searchParams?: Record<string, any>;
133
148
  /**
134
149
  * Default is 2 retries (3 tries in total).
@@ -185,4 +200,4 @@ export interface FetcherErrorResponse<BODY = unknown> {
185
200
  signature: string;
186
201
  }
187
202
  export type FetcherResponse<BODY = unknown> = FetcherSuccessResponse<BODY> | FetcherErrorResponse<BODY>;
188
- export type FetcherMode = 'json' | 'text' | 'void' | 'arrayBuffer' | 'blob' | 'readableStream';
203
+ export type FetcherResponseType = 'json' | 'text' | 'void' | 'arrayBuffer' | 'blob' | 'readableStream';
package/dist/index.d.ts CHANGED
@@ -80,6 +80,7 @@ export * from './http/fetcher';
80
80
  export * from './http/fetcher.model';
81
81
  export * from './string/hash.util';
82
82
  export * from './env/buildInfo';
83
+ export * from './form.util';
83
84
  export * from './zod/zod.util';
84
85
  export * from './zod/zod.shared.schemas';
85
86
  import { z, ZodSchema, ZodError, ZodIssue } from 'zod';
package/dist/index.js CHANGED
@@ -84,6 +84,7 @@ tslib_1.__exportStar(require("./http/fetcher"), exports);
84
84
  tslib_1.__exportStar(require("./http/fetcher.model"), exports);
85
85
  tslib_1.__exportStar(require("./string/hash.util"), exports);
86
86
  tslib_1.__exportStar(require("./env/buildInfo"), exports);
87
+ tslib_1.__exportStar(require("./form.util"), exports);
87
88
  tslib_1.__exportStar(require("./zod/zod.util"), exports);
88
89
  tslib_1.__exportStar(require("./zod/zod.shared.schemas"), exports);
89
90
  const zod_1 = require("zod");
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Convert any object to FormData.
3
+ * Please note that every key and value of FormData is `string`.
4
+ * Even if you pass a number - it'll be converted to string.
5
+ * Think URLSearchParams.
6
+ */
7
+ export function objectToFormData(obj = {}) {
8
+ const fd = new FormData();
9
+ Object.entries(obj).forEach(([k, v]) => fd.append(k, v));
10
+ return fd;
11
+ }
12
+ export function formDataToObject(formData) {
13
+ return Object.fromEntries(formData);
14
+ }
@@ -1,4 +1,5 @@
1
1
  /// <reference lib="dom"/>
2
+ /// <reference lib="dom.iterable"/>
2
3
  import { isServerSide } from '../env';
3
4
  import { _anyToError, _anyToErrorObject, _errorLikeToErrorObject } from '../error/error.util';
4
5
  import { HttpRequestError } from '../error/httpRequestError';
@@ -10,6 +11,14 @@ import { _jsonParse, _jsonParseIfPossible } from '../string/json.util';
10
11
  import { _stringifyAny } from '../string/stringifyAny';
11
12
  import { _since } from '../time/time.util';
12
13
  import { HTTP_METHODS } from './http.model';
14
+ const acceptByResponseType = {
15
+ text: 'text/plain',
16
+ json: 'application/json',
17
+ void: '*/*',
18
+ readableStream: 'application/octet-stream',
19
+ arrayBuffer: 'application/octet-stream',
20
+ blob: 'application/octet-stream',
21
+ };
13
22
  const defRetryOptions = {
14
23
  count: 2,
15
24
  timeout: 1000,
@@ -31,18 +40,18 @@ export class Fetcher {
31
40
  const m = method.toLowerCase();
32
41
  this[`${m}Void`] = async (url, opt) => {
33
42
  return await this.fetch(Object.assign({ url,
34
- method, mode: 'void' }, opt));
43
+ method, responseType: 'void' }, opt));
35
44
  };
36
45
  if (method === 'HEAD')
37
- return // mode=text
46
+ return // responseType=text
38
47
  ;
39
48
  this[`${m}Text`] = async (url, opt) => {
40
49
  return await this.fetch(Object.assign({ url,
41
- method, mode: 'text' }, opt));
50
+ method, responseType: 'text' }, opt));
42
51
  };
43
52
  this[m] = async (url, opt) => {
44
53
  return await this.fetch(Object.assign({ url,
45
- method, mode: 'json' }, opt));
54
+ method, responseType: 'json' }, opt));
46
55
  };
47
56
  });
48
57
  }
@@ -70,7 +79,7 @@ export class Fetcher {
70
79
  static create(cfg = {}) {
71
80
  return new Fetcher(cfg);
72
81
  }
73
- // mode=readableStream
82
+ // responseType=readableStream
74
83
  /**
75
84
  * Returns raw fetchResponse.body, which is a ReadableStream<Uint8Array>
76
85
  *
@@ -78,7 +87,7 @@ export class Fetcher {
78
87
  * https://css-tricks.com/web-streams-everywhere-and-fetch-for-node-js/
79
88
  */
80
89
  async getReadableStream(url, opt) {
81
- return await this.fetch(Object.assign({ url, mode: 'readableStream' }, opt));
90
+ return await this.fetch(Object.assign({ url, responseType: 'readableStream' }, opt));
82
91
  }
83
92
  async fetch(opt) {
84
93
  const res = await this.doFetch(opt);
@@ -168,8 +177,8 @@ export class Fetcher {
168
177
  }
169
178
  async onOkResponse(res, timeout) {
170
179
  const { req } = res;
171
- const { mode } = res.req;
172
- if (mode === 'json') {
180
+ const { responseType } = res.req;
181
+ if (responseType === 'json') {
173
182
  if (res.fetchResponse.body) {
174
183
  const text = await res.fetchResponse.text();
175
184
  if (text) {
@@ -203,16 +212,16 @@ export class Fetcher {
203
212
  res.body = {};
204
213
  }
205
214
  }
206
- else if (mode === 'text') {
215
+ else if (responseType === 'text') {
207
216
  res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
208
217
  }
209
- else if (mode === 'arrayBuffer') {
218
+ else if (responseType === 'arrayBuffer') {
210
219
  res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
211
220
  }
212
- else if (mode === 'blob') {
221
+ else if (responseType === 'blob') {
213
222
  res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
214
223
  }
215
- else if (mode === 'readableStream') {
224
+ else if (responseType === 'readableStream') {
216
225
  res.body = res.fetchResponse.body;
217
226
  if (res.body === null) {
218
227
  res.err = new Error(`fetchResponse.body is null`);
@@ -419,7 +428,7 @@ export class Fetcher {
419
428
  const norm = _merge({
420
429
  baseUrl: '',
421
430
  inputUrl: '',
422
- mode: 'void',
431
+ responseType: 'void',
423
432
  searchParams: {},
424
433
  timeoutSeconds: 30,
425
434
  retryPost: false,
@@ -438,7 +447,7 @@ export class Fetcher {
438
447
  retry: Object.assign({}, defRetryOptions),
439
448
  init: {
440
449
  method: cfg.method || 'GET',
441
- headers: cfg.headers || {},
450
+ headers: Object.assign({ 'user-agent': 'fetcher' }, cfg.headers),
442
451
  credentials: cfg.credentials,
443
452
  redirect: cfg.redirect,
444
453
  },
@@ -448,12 +457,13 @@ export class Fetcher {
448
457
  return norm;
449
458
  }
450
459
  normalizeOptions(opt) {
460
+ var _a;
451
461
  const req = Object.assign(Object.assign(Object.assign(Object.assign({}, _pick(this.cfg, [
452
462
  'timeoutSeconds',
453
463
  'retryPost',
454
464
  'retry4xx',
455
465
  'retry5xx',
456
- 'mode',
466
+ 'responseType',
457
467
  'jsonReviver',
458
468
  'logRequest',
459
469
  'logRequestBody',
@@ -485,9 +495,20 @@ export class Fetcher {
485
495
  req.init.body = opt.text;
486
496
  req.init.headers['content-type'] = 'text/plain';
487
497
  }
498
+ else if (opt.form) {
499
+ if (opt.form instanceof URLSearchParams || opt.form instanceof FormData) {
500
+ req.init.body = opt.form;
501
+ }
502
+ else {
503
+ req.init.body = new URLSearchParams(opt.form);
504
+ }
505
+ req.init.headers['content-type'] = 'application/x-www-form-urlencoded';
506
+ }
488
507
  else if (opt.body !== undefined) {
489
508
  req.init.body = opt.body;
490
509
  }
510
+ // Unless `accept` header was already set - set it based on responseType
511
+ (_a = req.init.headers)['accept'] || (_a['accept'] = acceptByResponseType[req.responseType]);
491
512
  return req;
492
513
  }
493
514
  }
package/dist-esm/index.js CHANGED
@@ -80,6 +80,7 @@ export * from './http/fetcher';
80
80
  export * from './http/fetcher.model';
81
81
  export * from './string/hash.util';
82
82
  export * from './env/buildInfo';
83
+ export * from './form.util';
83
84
  export * from './zod/zod.util';
84
85
  export * from './zod/zod.shared.schemas';
85
86
  import { z, ZodSchema, ZodError } from 'zod';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.156.0",
3
+ "version": "14.157.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -21,7 +21,6 @@
21
21
  "crypto-js": "^4.1.1",
22
22
  "jest": "^29.0.0",
23
23
  "prettier": "^3.0.0",
24
- "rxjs": "^7.0.1",
25
24
  "vuepress": "^1.7.1",
26
25
  "vuepress-plugin-typescript": "^0.3.1"
27
26
  },
@@ -0,0 +1,17 @@
1
+ import type { AnyObject } from './types'
2
+
3
+ /**
4
+ * Convert any object to FormData.
5
+ * Please note that every key and value of FormData is `string`.
6
+ * Even if you pass a number - it'll be converted to string.
7
+ * Think URLSearchParams.
8
+ */
9
+ export function objectToFormData(obj: AnyObject = {}): FormData {
10
+ const fd = new FormData()
11
+ Object.entries(obj).forEach(([k, v]) => fd.append(k, v))
12
+ return fd
13
+ }
14
+
15
+ export function formDataToObject<T extends AnyObject>(formData: FormData): T {
16
+ return Object.fromEntries(formData) as T
17
+ }
@@ -1,6 +1,6 @@
1
1
  import type { CommonLogger } from '../log/commonLogger'
2
2
  import type { Promisable } from '../typeFest'
3
- import type { Reviver, UnixTimestampMillisNumber } from '../types'
3
+ import type { AnyObject, Reviver, UnixTimestampMillisNumber } from '../types'
4
4
  import type { HttpMethod, HttpStatusFamily } from './http.model'
5
5
 
6
6
  export interface FetcherNormalizedCfg
@@ -116,7 +116,7 @@ export interface FetcherRequest
116
116
  */
117
117
  fullUrl: string
118
118
  init: RequestInitNormalized
119
- mode: FetcherMode
119
+ responseType: FetcherResponseType
120
120
  timeoutSeconds: number
121
121
  retry: FetcherRetryOptions
122
122
  retryPost: boolean
@@ -145,8 +145,6 @@ export interface FetcherOptions {
145
145
  */
146
146
  timeoutSeconds?: number
147
147
 
148
- json?: any
149
- text?: string
150
148
  /**
151
149
  * Supports all the types that RequestInit.body supports.
152
150
  *
@@ -154,6 +152,26 @@ export interface FetcherOptions {
154
152
  */
155
153
  body?: Blob | BufferSource | FormData | URLSearchParams | string
156
154
 
155
+ /**
156
+ * Same as `body`, but also conveniently sets the
157
+ * Content-Type header to `text/plain`
158
+ */
159
+ text?: string
160
+
161
+ /**
162
+ * Same as `body`, but:
163
+ * 1. JSON.stringifies the passed variable
164
+ * 2. Conveniently sets the Content-Type header to `application/json`
165
+ */
166
+ json?: any
167
+
168
+ /**
169
+ * Same as `body`, but:
170
+ * 1. Transforms the passed plain js object into URLSearchParams and passes it to `body`
171
+ * 2. Conveniently sets the Content-Type header to `application/x-www-form-urlencoded`
172
+ */
173
+ form?: FormData | URLSearchParams | AnyObject
174
+
157
175
  credentials?: RequestCredentials
158
176
  /**
159
177
  * Default to 'follow'.
@@ -167,7 +185,7 @@ export interface FetcherOptions {
167
185
  // init?: Partial<RequestInitNormalized>
168
186
 
169
187
  headers?: Record<string, any>
170
- mode?: FetcherMode // default to 'void'
188
+ responseType?: FetcherResponseType // default to 'void'
171
189
 
172
190
  searchParams?: Record<string, any>
173
191
 
@@ -236,4 +254,10 @@ export type FetcherResponse<BODY = unknown> =
236
254
  | FetcherSuccessResponse<BODY>
237
255
  | FetcherErrorResponse<BODY>
238
256
 
239
- export type FetcherMode = 'json' | 'text' | 'void' | 'arrayBuffer' | 'blob' | 'readableStream'
257
+ export type FetcherResponseType =
258
+ | 'json'
259
+ | 'text'
260
+ | 'void'
261
+ | 'arrayBuffer'
262
+ | 'blob'
263
+ | 'readableStream'
@@ -1,4 +1,5 @@
1
1
  /// <reference lib="dom"/>
2
+ /// <reference lib="dom.iterable"/>
2
3
 
3
4
  import { isServerSide } from '../env'
4
5
  import { ErrorLike, ErrorObject } from '../error/error.model'
@@ -28,11 +29,21 @@ import type {
28
29
  FetcherOptions,
29
30
  FetcherRequest,
30
31
  FetcherResponse,
32
+ FetcherResponseType,
31
33
  FetcherRetryOptions,
32
34
  } from './fetcher.model'
33
35
  import { HTTP_METHODS } from './http.model'
34
36
  import type { HttpStatusFamily } from './http.model'
35
37
 
38
+ const acceptByResponseType: Record<FetcherResponseType, string> = {
39
+ text: 'text/plain',
40
+ json: 'application/json',
41
+ void: '*/*',
42
+ readableStream: 'application/octet-stream',
43
+ arrayBuffer: 'application/octet-stream',
44
+ blob: 'application/octet-stream',
45
+ }
46
+
36
47
  const defRetryOptions: FetcherRetryOptions = {
37
48
  count: 2,
38
49
  timeout: 1000,
@@ -56,32 +67,32 @@ export class Fetcher {
56
67
  HTTP_METHODS.forEach(method => {
57
68
  const m = method.toLowerCase()
58
69
 
59
- // mode=void
70
+ // responseType=void
60
71
  ;(this as any)[`${m}Void`] = async (url: string, opt?: FetcherOptions): Promise<void> => {
61
72
  return await this.fetch<void>({
62
73
  url,
63
74
  method,
64
- mode: 'void',
75
+ responseType: 'void',
65
76
  ...opt,
66
77
  })
67
78
  }
68
79
 
69
- if (method === 'HEAD') return // mode=text
80
+ if (method === 'HEAD') return // responseType=text
70
81
  ;(this as any)[`${m}Text`] = async (url: string, opt?: FetcherOptions): Promise<string> => {
71
82
  return await this.fetch<string>({
72
83
  url,
73
84
  method,
74
- mode: 'text',
85
+ responseType: 'text',
75
86
  ...opt,
76
87
  })
77
88
  }
78
89
 
79
- // Default mode=json, but overridable
90
+ // Default responseType=json, but overridable
80
91
  ;(this as any)[m] = async <T = unknown>(url: string, opt?: FetcherOptions): Promise<T> => {
81
92
  return await this.fetch<T>({
82
93
  url,
83
94
  method,
84
- mode: 'json',
95
+ responseType: 'json',
85
96
  ...opt,
86
97
  })
87
98
  }
@@ -113,21 +124,21 @@ export class Fetcher {
113
124
  }
114
125
 
115
126
  // These methods are generated dynamically in the constructor
116
- // These default methods use mode=json
127
+ // These default methods use responseType=json
117
128
  get!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
118
129
  post!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
119
130
  put!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
120
131
  patch!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
121
132
  delete!: <T = unknown>(url: string, opt?: FetcherOptions) => Promise<T>
122
133
 
123
- // mode=text
134
+ // responseType=text
124
135
  getText!: (url: string, opt?: FetcherOptions) => Promise<string>
125
136
  postText!: (url: string, opt?: FetcherOptions) => Promise<string>
126
137
  putText!: (url: string, opt?: FetcherOptions) => Promise<string>
127
138
  patchText!: (url: string, opt?: FetcherOptions) => Promise<string>
128
139
  deleteText!: (url: string, opt?: FetcherOptions) => Promise<string>
129
140
 
130
- // mode=void (no body fetching/parsing)
141
+ // responseType=void (no body fetching/parsing)
131
142
  getVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
132
143
  postVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
133
144
  putVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
@@ -135,7 +146,7 @@ export class Fetcher {
135
146
  deleteVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
136
147
  headVoid!: (url: string, opt?: FetcherOptions) => Promise<void>
137
148
 
138
- // mode=readableStream
149
+ // responseType=readableStream
139
150
  /**
140
151
  * Returns raw fetchResponse.body, which is a ReadableStream<Uint8Array>
141
152
  *
@@ -145,7 +156,7 @@ export class Fetcher {
145
156
  async getReadableStream(url: string, opt?: FetcherOptions): Promise<ReadableStream<Uint8Array>> {
146
157
  return await this.fetch({
147
158
  url,
148
- mode: 'readableStream',
159
+ responseType: 'readableStream',
149
160
  ...opt,
150
161
  })
151
162
  }
@@ -255,9 +266,9 @@ export class Fetcher {
255
266
  timeout?: number,
256
267
  ): Promise<void> {
257
268
  const { req } = res
258
- const { mode } = res.req
269
+ const { responseType } = res.req
259
270
 
260
- if (mode === 'json') {
271
+ if (responseType === 'json') {
261
272
  if (res.fetchResponse.body) {
262
273
  const text = await res.fetchResponse.text()
263
274
 
@@ -289,13 +300,13 @@ export class Fetcher {
289
300
  // do not throw a "cannot parse null as Json" error
290
301
  res.body = {}
291
302
  }
292
- } else if (mode === 'text') {
303
+ } else if (responseType === 'text') {
293
304
  res.body = res.fetchResponse.body ? await res.fetchResponse.text() : ''
294
- } else if (mode === 'arrayBuffer') {
305
+ } else if (responseType === 'arrayBuffer') {
295
306
  res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {}
296
- } else if (mode === 'blob') {
307
+ } else if (responseType === 'blob') {
297
308
  res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {}
298
- } else if (mode === 'readableStream') {
309
+ } else if (responseType === 'readableStream') {
299
310
  res.body = res.fetchResponse.body
300
311
 
301
312
  if (res.body === null) {
@@ -531,7 +542,7 @@ export class Fetcher {
531
542
  {
532
543
  baseUrl: '',
533
544
  inputUrl: '',
534
- mode: 'void',
545
+ responseType: 'void',
535
546
  searchParams: {},
536
547
  timeoutSeconds: 30,
537
548
  retryPost: false,
@@ -550,7 +561,10 @@ export class Fetcher {
550
561
  retry: { ...defRetryOptions },
551
562
  init: {
552
563
  method: cfg.method || 'GET',
553
- headers: cfg.headers || {},
564
+ headers: {
565
+ 'user-agent': 'fetcher',
566
+ ...cfg.headers,
567
+ },
554
568
  credentials: cfg.credentials,
555
569
  redirect: cfg.redirect,
556
570
  },
@@ -571,7 +585,7 @@ export class Fetcher {
571
585
  'retryPost',
572
586
  'retry4xx',
573
587
  'retry5xx',
574
- 'mode',
588
+ 'responseType',
575
589
  'jsonReviver',
576
590
  'logRequest',
577
591
  'logRequestBody',
@@ -625,10 +639,21 @@ export class Fetcher {
625
639
  } else if (opt.text !== undefined) {
626
640
  req.init.body = opt.text
627
641
  req.init.headers['content-type'] = 'text/plain'
642
+ } else if (opt.form) {
643
+ if (opt.form instanceof URLSearchParams || opt.form instanceof FormData) {
644
+ req.init.body = opt.form
645
+ } else {
646
+ req.init.body = new URLSearchParams(opt.form)
647
+ }
648
+
649
+ req.init.headers['content-type'] = 'application/x-www-form-urlencoded'
628
650
  } else if (opt.body !== undefined) {
629
651
  req.init.body = opt.body
630
652
  }
631
653
 
654
+ // Unless `accept` header was already set - set it based on responseType
655
+ req.init.headers['accept'] ||= acceptByResponseType[req.responseType]
656
+
632
657
  return req
633
658
  }
634
659
  }
package/src/index.ts CHANGED
@@ -80,6 +80,7 @@ export * from './http/fetcher'
80
80
  export * from './http/fetcher.model'
81
81
  export * from './string/hash.util'
82
82
  export * from './env/buildInfo'
83
+ export * from './form.util'
83
84
  export * from './zod/zod.util'
84
85
  export * from './zod/zod.shared.schemas'
85
86
  import { z, ZodSchema, ZodError, ZodIssue } from 'zod'