@webqit/fetch-plus 0.1.1
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/.github/FUNDING.yml +13 -0
- package/.github/workflows/publish.yml +48 -0
- package/.gitignore +6 -0
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/main.js +2 -0
- package/dist/main.js.map +7 -0
- package/package.json +54 -0
- package/src/FormDataPlus.js +57 -0
- package/src/HeadersPlus.js +152 -0
- package/src/LiveResponse.js +494 -0
- package/src/RequestPlus.js +80 -0
- package/src/ResponsePlus.js +56 -0
- package/src/URLSearchParamsPlus.js +80 -0
- package/src/core.js +172 -0
- package/src/fetchPlus.js +7 -0
- package/src/index.browser.js +4 -0
- package/src/index.js +8 -0
- package/test/basic.test.js +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { _isString, _isNumeric, _isTypeObject } from '@webqit/util/js/index.js';
|
|
2
|
+
|
|
3
|
+
export class URLSearchParamsPlus extends URLSearchParams {
|
|
4
|
+
|
|
5
|
+
// Parse a search params string into an object
|
|
6
|
+
static eval(targetObject, str, delim = '&') {
|
|
7
|
+
str = str || '';
|
|
8
|
+
(str.startsWith('?') ? str.substr(1) : str)
|
|
9
|
+
.split(delim).filter(q => q).map(q => q.split('=').map(q => q.trim()))
|
|
10
|
+
.forEach(q => this.set(targetObject, q[0], decodeURIComponent(q[1])));
|
|
11
|
+
return targetObject;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Stringify an object into a search params string
|
|
15
|
+
static stringify(targetObject, delim = '&') {
|
|
16
|
+
const q = [];
|
|
17
|
+
Object.keys(targetObject).forEach(key => {
|
|
18
|
+
this.reduceValue(targetObject[key], key, (_value, _pathNotation, suggestedKeys = undefined) => {
|
|
19
|
+
if (suggestedKeys) return suggestedKeys;
|
|
20
|
+
q.push(`${_pathNotation}=${encodeURIComponent(_value)}`);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
return q.join(delim);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Get value by path notation
|
|
27
|
+
static get(targetObject, pathNotation) {
|
|
28
|
+
return this.reducePath(pathNotation, targetObject, (key, _targetObject) => {
|
|
29
|
+
if (!_targetObject && _targetObject !== 0) return;
|
|
30
|
+
return _targetObject[key];
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Set value by path notation
|
|
35
|
+
static set(targetObject, pathNotation, value) {
|
|
36
|
+
this.reducePath(pathNotation, targetObject, function(_key, _targetObject, suggestedBranch = undefined) {
|
|
37
|
+
let _value = value;
|
|
38
|
+
if (suggestedBranch) { _value = suggestedBranch; }
|
|
39
|
+
if (_key === '' && Array.isArray(_targetObject)) {
|
|
40
|
+
_targetObject.push(_value);
|
|
41
|
+
} else {
|
|
42
|
+
_targetObject[_key] = _value;
|
|
43
|
+
}
|
|
44
|
+
return _value;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Resolve a value to its leaf nodes
|
|
49
|
+
static reduceValue(value, contextPath, callback) {
|
|
50
|
+
if (_isTypeObject(value)) {
|
|
51
|
+
let suggestedKeys = Object.keys(value);
|
|
52
|
+
let keys = callback(value, contextPath, suggestedKeys);
|
|
53
|
+
if (Array.isArray(keys)) {
|
|
54
|
+
return keys.forEach(key => {
|
|
55
|
+
this.reduceValue(value[key], contextPath ? `${contextPath}[${key}]` : key, callback);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
callback(value, contextPath);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Resolve a path to its leaf index
|
|
63
|
+
static reducePath(pathNotation, contextObject, callback) {
|
|
64
|
+
if (_isString(pathNotation) && pathNotation.endsWith(']') && _isTypeObject(contextObject)) {
|
|
65
|
+
let [ key, ...rest ] = pathNotation.split('[');
|
|
66
|
+
if (_isNumeric(key)) { key = parseInt(key); }
|
|
67
|
+
rest = rest.join('[').replace(']', '');
|
|
68
|
+
let branch;
|
|
69
|
+
if (key in contextObject) {
|
|
70
|
+
branch = contextObject[key];
|
|
71
|
+
} else {
|
|
72
|
+
let suggestedBranch = rest === '' || _isNumeric(rest.split('[')[0]) ? [] : {};
|
|
73
|
+
branch = callback(key, contextObject, suggestedBranch);
|
|
74
|
+
}
|
|
75
|
+
return this.reducePath(rest, branch, callback);
|
|
76
|
+
}
|
|
77
|
+
if (_isNumeric(pathNotation)) { pathNotation = parseInt(pathNotation); }
|
|
78
|
+
return callback(pathNotation, contextObject);
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/core.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { _isString, _isObject, _isTypeObject, _isNumber, _isBoolean } from '@webqit/util/js/index.js';
|
|
2
|
+
import { _wq as $wq } from '@webqit/util/js/index.js';
|
|
3
|
+
import { FormDataPlus } from './FormDataPlus.js';
|
|
4
|
+
|
|
5
|
+
export const _wq = (target, ...args) => $wq(target, 'fetch+', ...args);
|
|
6
|
+
export const _meta = (target) => $wq(target, 'fetch+', 'meta');
|
|
7
|
+
|
|
8
|
+
export function messageParserMixin(superClass) {
|
|
9
|
+
return class extends superClass {
|
|
10
|
+
|
|
11
|
+
static from(httpMessageInit) {
|
|
12
|
+
const headers = (httpMessageInit.headers instanceof Headers)
|
|
13
|
+
|
|
14
|
+
? [...httpMessageInit.headers.entries()].reduce((_headers, [name, value]) => {
|
|
15
|
+
const key = name.toLowerCase();
|
|
16
|
+
_headers[key] = _headers[key] ? [].concat(_headers[key], value) : value;
|
|
17
|
+
return _headers;
|
|
18
|
+
}, {})
|
|
19
|
+
|
|
20
|
+
: Object.keys(httpMessageInit.headers || {}).reduce((_headers, name) => {
|
|
21
|
+
_headers[name.toLowerCase()] = httpMessageInit.headers[name];
|
|
22
|
+
return _headers;
|
|
23
|
+
}, {});
|
|
24
|
+
|
|
25
|
+
// Process body
|
|
26
|
+
let body = httpMessageInit.body;
|
|
27
|
+
let type = dataType(body);
|
|
28
|
+
|
|
29
|
+
// Binary bodies
|
|
30
|
+
if (['Blob', 'File'].includes(type)) {
|
|
31
|
+
|
|
32
|
+
headers['content-type'] ??= body.type;
|
|
33
|
+
headers['content-length'] ??= body.size;
|
|
34
|
+
|
|
35
|
+
} else if (['Uint8Array', 'Uint16Array', 'Uint32Array', 'ArrayBuffer'].includes(type)) {
|
|
36
|
+
headers['content-length'] ??= body.byteLength;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// JSON objects
|
|
40
|
+
else if (type === 'json' && _isTypeObject(body)) {
|
|
41
|
+
const [_body, isJsonfiable] = FormDataPlus.json(body, { recursive: true, getIsJsonfiable: true });
|
|
42
|
+
if (isJsonfiable) {
|
|
43
|
+
|
|
44
|
+
body = JSON.stringify(body, (k, v) => v instanceof Error ? { ...v, message: v.message } : v);
|
|
45
|
+
headers['content-type'] = 'application/json';
|
|
46
|
+
headers['content-length'] = (new Blob([body])).size;
|
|
47
|
+
|
|
48
|
+
} else {
|
|
49
|
+
body = _body;
|
|
50
|
+
type = 'FormData';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// JSON strings
|
|
55
|
+
else if (type === 'json' && !headers['content-length']) {
|
|
56
|
+
headers['content-length'] = (body + '').length;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Return canonical init object with type info
|
|
60
|
+
return { body, headers, $type: type };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async parse({ to = null, memoize = false } = {}) {
|
|
64
|
+
const cache = _meta(this, 'cache');
|
|
65
|
+
const toOther = ['text', 'arrayBuffer', 'blob', 'bytes'].includes(to);
|
|
66
|
+
|
|
67
|
+
const contentType = (this.headers.get('Content-Type') || '').split(';')[0].trim();
|
|
68
|
+
let result;
|
|
69
|
+
|
|
70
|
+
const throw_cantConvert = () => {
|
|
71
|
+
throw new Error(`Can't convert response of type ${contentType} to: ${to}`);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (!toOther
|
|
75
|
+
&& ['multipart/form-data', 'application/x-www-form-urlencoded'].includes(contentType)) {
|
|
76
|
+
if (to && !['formData', 'json'].includes(to)) throw_cantConvert();
|
|
77
|
+
|
|
78
|
+
if (memoize && cache.has(to || 'formData')) {
|
|
79
|
+
return cache.get(to || 'formData');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let fd = await this.formData();
|
|
83
|
+
if (fd) {
|
|
84
|
+
fd = FormDataPlus.upgradeInPlace(fd);
|
|
85
|
+
if (memoize) cache.set('formData', fd);
|
|
86
|
+
|
|
87
|
+
if (to === 'json') {
|
|
88
|
+
fd = await fd.json({ recursive: true });
|
|
89
|
+
if (memoize) cache.set('json', { ...fd });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
result = fd;
|
|
94
|
+
} else if (!toOther
|
|
95
|
+
&& contentType === 'application/json') {
|
|
96
|
+
if (to && !['json', 'formData'].includes(to)) throw_cantConvert();
|
|
97
|
+
|
|
98
|
+
if (memoize && cache.has(to || 'json')) {
|
|
99
|
+
return cache.get(to || 'json');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let json = await this.json();
|
|
103
|
+
if (json) {
|
|
104
|
+
if (memoize) cache.set('json', { ...json });
|
|
105
|
+
|
|
106
|
+
if (to === 'formData') {
|
|
107
|
+
json = FormDataPlus.json(json, { recursive: true });
|
|
108
|
+
if (memoize) cache.set('formData', json);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
result = json;
|
|
113
|
+
} else /*if (contentType === 'text/plain')*/ {
|
|
114
|
+
if (to && !toOther) throw_cantConvert();
|
|
115
|
+
|
|
116
|
+
if (memoize) {
|
|
117
|
+
const result = cache.get(to || 'text') || cache.get('original');
|
|
118
|
+
if (result) return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
result = await this[to || 'text']();
|
|
122
|
+
|
|
123
|
+
if (memoize) cache.set(to || 'text', result);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ------ Util
|
|
132
|
+
|
|
133
|
+
export function dataType(value) {
|
|
134
|
+
if (value instanceof FormData) {
|
|
135
|
+
return 'FormData';
|
|
136
|
+
}
|
|
137
|
+
if (value === null || _isNumber(value) || _isBoolean(value)) {
|
|
138
|
+
return 'json';
|
|
139
|
+
}
|
|
140
|
+
if (_isString(value)
|
|
141
|
+
|| _isTypeObject(value) && 'toString' in value) {
|
|
142
|
+
return 'text';
|
|
143
|
+
}
|
|
144
|
+
if (!_isTypeObject(value)) return null;
|
|
145
|
+
|
|
146
|
+
const toStringTag = value[Symbol.toStringTag];
|
|
147
|
+
const type = [
|
|
148
|
+
'Uint8Array', 'Uint16Array', 'Uint32Array', 'ArrayBuffer', 'Blob', 'File', 'FormData', 'Stream', 'ReadableStream'
|
|
149
|
+
].reduce((_toStringTag, type) => _toStringTag || (toStringTag === type ? type : null), null);
|
|
150
|
+
|
|
151
|
+
if (type) return type;
|
|
152
|
+
|
|
153
|
+
if ((_isObject(value)) || Array.isArray(value)) {
|
|
154
|
+
return 'json';
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function isTypeReadable(obj) {
|
|
160
|
+
return (
|
|
161
|
+
obj !== null &&
|
|
162
|
+
typeof obj === 'object' &&
|
|
163
|
+
typeof obj.read === 'function' && // streams have .read()
|
|
164
|
+
typeof obj.pipe === 'function' && // streams have .pipe()
|
|
165
|
+
typeof obj.on === 'function' // streams have event listeners
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function isTypeStream(obj) {
|
|
170
|
+
return obj instanceof ReadableStream
|
|
171
|
+
|| isTypeReadable(obj);
|
|
172
|
+
}
|
package/src/fetchPlus.js
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { fetchPlus } from './fetchPlus.js';
|
|
2
|
+
export { RequestPlus } from './RequestPlus.js';
|
|
3
|
+
export { ResponsePlus } from './ResponsePlus.js';
|
|
4
|
+
export { HeadersPlus } from './HeadersPlus.js';
|
|
5
|
+
export { FormDataPlus } from './FormDataPlus.js';
|
|
6
|
+
export { LiveResponse } from './LiveResponse.js';
|
|
7
|
+
export { URLSearchParamsPlus } from './URLSearchParamsPlus.js';
|
|
8
|
+
export { default as Observer } from '@webqit/observer';
|
|
File without changes
|