@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.
- package/dist/form.util.d.ts +9 -0
- package/dist/form.util.js +19 -0
- package/dist/http/fetcher.d.ts +1 -0
- package/dist/http/fetcher.js +38 -15
- package/dist/http/fetcher.model.d.ts +21 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist-esm/form.util.js +14 -0
- package/dist-esm/http/fetcher.js +36 -15
- package/dist-esm/index.js +1 -0
- package/package.json +1 -2
- package/src/form.util.ts +17 -0
- package/src/http/fetcher.model.ts +30 -6
- package/src/http/fetcher.ts +45 -20
- package/src/index.ts +1 -0
|
@@ -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;
|
package/dist/http/fetcher.d.ts
CHANGED
|
@@ -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.
|
package/dist/http/fetcher.js
CHANGED
|
@@ -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
|
-
|
|
48
|
+
responseType: 'void',
|
|
40
49
|
...opt,
|
|
41
50
|
});
|
|
42
51
|
};
|
|
43
52
|
if (method === 'HEAD')
|
|
44
|
-
return //
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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 {
|
|
187
|
-
if (
|
|
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 (
|
|
230
|
+
else if (responseType === 'text') {
|
|
222
231
|
res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
|
|
223
232
|
}
|
|
224
|
-
else if (
|
|
233
|
+
else if (responseType === 'arrayBuffer') {
|
|
225
234
|
res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
|
|
226
235
|
}
|
|
227
|
-
else if (
|
|
236
|
+
else if (responseType === 'blob') {
|
|
228
237
|
res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
|
|
229
238
|
}
|
|
230
|
-
else if (
|
|
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
|
-
|
|
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:
|
|
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
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|
package/dist-esm/http/fetcher.js
CHANGED
|
@@ -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,
|
|
43
|
+
method, responseType: 'void' }, opt));
|
|
35
44
|
};
|
|
36
45
|
if (method === 'HEAD')
|
|
37
|
-
return //
|
|
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,
|
|
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,
|
|
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
|
-
//
|
|
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,
|
|
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 {
|
|
172
|
-
if (
|
|
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 (
|
|
215
|
+
else if (responseType === 'text') {
|
|
207
216
|
res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
|
|
208
217
|
}
|
|
209
|
-
else if (
|
|
218
|
+
else if (responseType === 'arrayBuffer') {
|
|
210
219
|
res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
|
|
211
220
|
}
|
|
212
|
-
else if (
|
|
221
|
+
else if (responseType === 'blob') {
|
|
213
222
|
res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
|
|
214
223
|
}
|
|
215
|
-
else if (
|
|
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
|
-
|
|
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:
|
|
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
|
-
'
|
|
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.
|
|
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
|
},
|
package/src/form.util.ts
ADDED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
257
|
+
export type FetcherResponseType =
|
|
258
|
+
| 'json'
|
|
259
|
+
| 'text'
|
|
260
|
+
| 'void'
|
|
261
|
+
| 'arrayBuffer'
|
|
262
|
+
| 'blob'
|
|
263
|
+
| 'readableStream'
|
package/src/http/fetcher.ts
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
75
|
+
responseType: 'void',
|
|
65
76
|
...opt,
|
|
66
77
|
})
|
|
67
78
|
}
|
|
68
79
|
|
|
69
|
-
if (method === 'HEAD') return //
|
|
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
|
-
|
|
85
|
+
responseType: 'text',
|
|
75
86
|
...opt,
|
|
76
87
|
})
|
|
77
88
|
}
|
|
78
89
|
|
|
79
|
-
// Default
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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 {
|
|
269
|
+
const { responseType } = res.req
|
|
259
270
|
|
|
260
|
-
if (
|
|
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 (
|
|
303
|
+
} else if (responseType === 'text') {
|
|
293
304
|
res.body = res.fetchResponse.body ? await res.fetchResponse.text() : ''
|
|
294
|
-
} else if (
|
|
305
|
+
} else if (responseType === 'arrayBuffer') {
|
|
295
306
|
res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {}
|
|
296
|
-
} else if (
|
|
307
|
+
} else if (responseType === 'blob') {
|
|
297
308
|
res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {}
|
|
298
|
-
} else if (
|
|
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
|
-
|
|
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:
|
|
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
|
-
'
|
|
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'
|