@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.
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ import { ResponsePlus } from './ResponsePlus.js';
2
+
3
+ export async function fetchPlus(url, options = {}) {
4
+ const response = await fetch(url, options);
5
+ ResponsePlus.upgradeInPlace(response);
6
+ return response;
7
+ }
@@ -0,0 +1,4 @@
1
+ import * as FetchPlus from './index.js';
2
+
3
+ if (!globalThis.webqit) globalThis.webqit = {}
4
+ Object.assign(globalThis.webqit, FetchPlus);
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