restql 1.1.6 → 1.1.8
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/LICENSE +1 -1
- package/README.md +38 -97
- package/dist/cjs/index.js +68 -429
- package/dist/esm/index.js +185 -0
- package/dist/umd/index.js +200 -20508
- package/dist/umd/index.min.js +1 -1
- package/package.json +46 -60
- package/dist/es/index.js +0 -544
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { merge } from 'es-toolkit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @constant {Object} responses The responses to cache.
|
|
5
|
+
*/
|
|
6
|
+
const responses = {};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fetches a resource based on its options, if not cached.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} resource The resource to fetch.
|
|
12
|
+
* @param {Object} options The options to bypass.
|
|
13
|
+
* @returns {Promise<Object>} A promise which resolves into an object.
|
|
14
|
+
*/
|
|
15
|
+
async function fetchResource(resource, options) {
|
|
16
|
+
const key = `${resource}-${JSON.stringify(options)}`;
|
|
17
|
+
if (!(key in responses)) {
|
|
18
|
+
responses[key] = fetch(resource, options).then(response => {
|
|
19
|
+
responses[key] = response;
|
|
20
|
+
return response;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return responses[key];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Determines whether or not a resource is valid.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} resource The resource to test.
|
|
30
|
+
* @returns {boolean} Whether or not a resource is valid.
|
|
31
|
+
*/
|
|
32
|
+
function isResource(resource) {
|
|
33
|
+
return Boolean(URL.parse(resource));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @constant {string} PROP_DELIMITER The delimiter of a property.
|
|
38
|
+
*/
|
|
39
|
+
const PROP_DELIMITER = '.';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @constant {RegExp} REGEX_PROP_IS_ARR_IS_OPT Determines whether or not a property is an array and/or is optional.
|
|
43
|
+
*/
|
|
44
|
+
const REGEX_PROP_IS_ARR_IS_OPT = /^([^[\]?]+)(\[])?(\?)?$/;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @constant {Object} constants The constants of the library.
|
|
48
|
+
*/
|
|
49
|
+
const constants = Object.freeze({
|
|
50
|
+
PROP_DELIMITER,
|
|
51
|
+
REGEX_PROP_IS_ARR_IS_OPT
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Gets the parsed properties from an object.
|
|
56
|
+
*
|
|
57
|
+
* @param {Object} obj The object to use.
|
|
58
|
+
* @param {string} props The properties to apply.
|
|
59
|
+
* @returns {Array} An array.
|
|
60
|
+
* @throws {RuntimeError} If a property could not be got.
|
|
61
|
+
*/
|
|
62
|
+
function objectGet(obj, props) {
|
|
63
|
+
const nextPropsArr = props.split(constants.PROP_DELIMITER);
|
|
64
|
+
const [, prop, isArr, isOpt] = constants.REGEX_PROP_IS_ARR_IS_OPT.exec(nextPropsArr.shift()) || [];
|
|
65
|
+
const nextProps = nextPropsArr.join(constants.PROP_DELIMITER);
|
|
66
|
+
if (!prop) {
|
|
67
|
+
return obj;
|
|
68
|
+
}
|
|
69
|
+
if (!(prop in obj)) {
|
|
70
|
+
if (isOpt) {
|
|
71
|
+
return isArr ? [] : null;
|
|
72
|
+
}
|
|
73
|
+
throw new Error(`RuntimeError: could not get property \`${prop}\``);
|
|
74
|
+
}
|
|
75
|
+
const nextObjs = obj[prop];
|
|
76
|
+
return isArr ? nextObjs.map(nextObj => objectGet(nextObj, nextProps)) : objectGet(nextObjs, nextProps);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Sets the parsed properties to an object.
|
|
81
|
+
*
|
|
82
|
+
* @param {Array} data The data to use.
|
|
83
|
+
* @param {string} props The properties to apply.
|
|
84
|
+
* @returns {Object} An object.
|
|
85
|
+
*/
|
|
86
|
+
function objectSet(data, props) {
|
|
87
|
+
const nextPropsArr = props.split(constants.PROP_DELIMITER);
|
|
88
|
+
const [, prop, isArr] = constants.REGEX_PROP_IS_ARR_IS_OPT.exec(nextPropsArr.shift()) || [];
|
|
89
|
+
const nextProps = nextPropsArr.join(constants.PROP_DELIMITER);
|
|
90
|
+
if (!prop) {
|
|
91
|
+
return data;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
[prop]: isArr ? data.map(nextData => objectSet(nextData, nextProps)) : objectSet(data, nextProps)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Resolves the nested-linked resources of a RESTful API.
|
|
100
|
+
*
|
|
101
|
+
* @param {string} resource The resource to fetch.
|
|
102
|
+
* @param {Object} resolver The resolver to apply.
|
|
103
|
+
* @param {Object} options The options to bypass.
|
|
104
|
+
* @returns {Promise<Object>} A promise which resolves into an object.
|
|
105
|
+
* @throws {InvalidArgumentError} If a resource is invalid.
|
|
106
|
+
* @throws {RuntimeError} If a resource could not be fetched.
|
|
107
|
+
*/
|
|
108
|
+
async function resolve(resource, resolver, options) {
|
|
109
|
+
if (!resource) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (!isResource(resource)) {
|
|
113
|
+
throw new Error(`InvalidArgumentError: invalid resource \`${resource}\``);
|
|
114
|
+
}
|
|
115
|
+
const response = (await fetchResource(resource, options)).clone();
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
throw new Error(`RuntimeError: could not fetch resource \`${resource}\``);
|
|
118
|
+
}
|
|
119
|
+
const obj = await response.json();
|
|
120
|
+
if (!resolver) {
|
|
121
|
+
return obj;
|
|
122
|
+
}
|
|
123
|
+
const resourcesObj = Object.keys(resolver).map(props => ({
|
|
124
|
+
[props]: objectGet(obj, props)
|
|
125
|
+
}));
|
|
126
|
+
const resourcesArr = Object.entries(resourcesObj.reduce((result, val) => ({
|
|
127
|
+
...result,
|
|
128
|
+
...val
|
|
129
|
+
}), {}));
|
|
130
|
+
const responses = await Promise.all(resourcesArr.map(async ([props, resources]) => {
|
|
131
|
+
const nextResolver = resolver[props];
|
|
132
|
+
const data = Array.isArray(resources) ? await Promise.all(resources.map(async nextResource => resolve(nextResource, nextResolver, options))) : await resolve(resources, nextResolver, options);
|
|
133
|
+
return [props, data];
|
|
134
|
+
}));
|
|
135
|
+
return responses.reduce((result, [props, data]) => merge(result, objectSet(data, props)), obj);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Determines whether or not an object is valid.
|
|
140
|
+
*
|
|
141
|
+
* @param {Object} obj The object to test.
|
|
142
|
+
* @returns {boolean} Whether or not an object is valid.
|
|
143
|
+
*/
|
|
144
|
+
function isObject(obj) {
|
|
145
|
+
return obj !== null && typeof obj === 'object' && !Array.isArray(obj);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Determines whether or not a resolver is valid.
|
|
150
|
+
*
|
|
151
|
+
* @param {Object} resolver The resolver to test.
|
|
152
|
+
* @returns {boolean} Whether or not a resolver is valid.
|
|
153
|
+
*/
|
|
154
|
+
function isResolver(resolver) {
|
|
155
|
+
if (!resolver) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
const keys = Object.keys(resolver);
|
|
159
|
+
if (!keys.length) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return keys.map(key => !key.startsWith(constants.PROP_DELIMITER) && !key.endsWith(constants.PROP_DELIMITER) && isResolver(resolver[key])).reduce((result, val) => result && val, true);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Resolves the nested-linked resources of a RESTful API.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} resource The resource to fetch.
|
|
169
|
+
* @param {Object} resolver The resolver to apply.
|
|
170
|
+
* @param {Object} [options] The options to bypass.
|
|
171
|
+
* @returns {Promise<Object>} A promise which resolves into an object.
|
|
172
|
+
* @throws {InvalidArgumentError} If a resolver is invalid.
|
|
173
|
+
* @throws {InvalidArgumentError} If an options are invalid.
|
|
174
|
+
*/
|
|
175
|
+
async function restql(resource, resolver, options = {}) {
|
|
176
|
+
if (!isObject(resolver) || !isResolver(resolver)) {
|
|
177
|
+
throw new Error(`InvalidArgumentError: invalid resolver \`${JSON.stringify(resolver)}\``);
|
|
178
|
+
}
|
|
179
|
+
if (!isObject(options)) {
|
|
180
|
+
throw new Error(`InvalidArgumentError: invalid options \`${JSON.stringify(options)}\``);
|
|
181
|
+
}
|
|
182
|
+
return resolve(resource, resolver, options);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export { restql as default };
|