restql 1.1.8 → 1.2.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/README.md +11 -13
- package/dist/index.cjs +128 -0
- package/dist/index.d.ts +8 -0
- package/dist/{umd/index.js → index.js} +37 -96
- package/dist/index.min.js +2 -0
- package/dist/index.min.js.map +1 -0
- package/dist/index.mjs +126 -0
- package/package.json +34 -23
- package/dist/cjs/index.js +0 -187
- package/dist/esm/index.js +0 -185
- package/dist/umd/index.min.js +0 -1
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# RestQL
|
|
1
|
+
# RestQL
|
|
2
2
|
|
|
3
3
|
RESTful API Resolver for Nested-Linked Resources | 🕸 🕷
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/restql/)
|
|
8
|
-

|
|
9
9
|

|
|
10
10
|
|
|
11
|
-
RestQL allows you to dynamically resolve
|
|
11
|
+
RestQL allows you to dynamically resolve nested-linked resources of a RESTful API.
|
|
12
12
|
By specifying a set of properties to describe the paths.
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
@@ -22,17 +22,16 @@ npm install restql
|
|
|
22
22
|
### CDN
|
|
23
23
|
|
|
24
24
|
```html
|
|
25
|
-
<script src="https://unpkg.com/restql/dist/
|
|
25
|
+
<script src="https://unpkg.com/restql/dist/index.min.js"></script>
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
## Usage
|
|
29
29
|
|
|
30
|
-
### `restql(resource, resolver[, options])`
|
|
30
|
+
### `restql<T>(resource, resolver[, options])`
|
|
31
31
|
|
|
32
32
|
#### Parameters
|
|
33
33
|
|
|
34
34
|
- `resource` (`string`): The resource to fetch.
|
|
35
|
-
- Self-explanatory.
|
|
36
35
|
|
|
37
36
|
##### Example
|
|
38
37
|
|
|
@@ -40,10 +39,10 @@ npm install restql
|
|
|
40
39
|
'https://pokeapi.co/api/v2/pokemon/1/'
|
|
41
40
|
```
|
|
42
41
|
|
|
43
|
-
- `resolver` (`
|
|
42
|
+
- `resolver` ([`Resolver`](https://github.com/relztic/restql/blob/main/src/types.ts#L1)): The resolver to apply.
|
|
44
43
|
- At every level, each property describes a path to the nested resources within the current one.
|
|
45
|
-
- RestQL
|
|
46
|
-
- Until the base case (`null`) is reached
|
|
44
|
+
- RestQL parses the same and calls the next resolver against them.
|
|
45
|
+
- Until the base case (`null`) is reached, from which it returns the merged responses.
|
|
47
46
|
|
|
48
47
|
##### Quantifiers
|
|
49
48
|
|
|
@@ -71,8 +70,7 @@ Following is a table of the quantifiers you can use:
|
|
|
71
70
|
}
|
|
72
71
|
```
|
|
73
72
|
|
|
74
|
-
- `[options]` (`
|
|
75
|
-
- [See `RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit)
|
|
73
|
+
- `[options]` ([`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit)): The options to bypass.
|
|
76
74
|
|
|
77
75
|
##### Example
|
|
78
76
|
|
|
@@ -82,7 +80,7 @@ Following is a table of the quantifiers you can use:
|
|
|
82
80
|
|
|
83
81
|
#### Returns
|
|
84
82
|
|
|
85
|
-
(`Promise<
|
|
83
|
+
(`Promise<T>`): A promise which resolves into a generic.
|
|
86
84
|
|
|
87
85
|
## Try It
|
|
88
86
|
|
|
@@ -90,7 +88,7 @@ Following is a table of the quantifiers you can use:
|
|
|
90
88
|
npm run playground
|
|
91
89
|
```
|
|
92
90
|
|
|
93
|
-
[See Playground](https://github.com/relztic/restql/blob/main/playground/index.
|
|
91
|
+
[See Playground](https://github.com/relztic/restql/blob/main/playground/index.ts)
|
|
94
92
|
|
|
95
93
|
## Roadmap
|
|
96
94
|
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var esToolkit = require('es-toolkit');
|
|
4
|
+
|
|
5
|
+
const responses = {};
|
|
6
|
+
async function fetchResource(resource, options) {
|
|
7
|
+
const key = `${resource}-${JSON.stringify(options)}`;
|
|
8
|
+
if (!(key in responses)) {
|
|
9
|
+
responses[key] = fetch(resource, options).then((response) => {
|
|
10
|
+
responses[key] = response;
|
|
11
|
+
return response;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return responses[key];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isResource(resource) {
|
|
18
|
+
return Boolean(URL.parse(resource));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const PROP_DELIMITER = ".";
|
|
22
|
+
const REGEX_PROP_IS_ARR_IS_OPT = /^([^[\]?]+)(\[])?(\?)?$/;
|
|
23
|
+
const constants = Object.freeze({
|
|
24
|
+
PROP_DELIMITER,
|
|
25
|
+
REGEX_PROP_IS_ARR_IS_OPT
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function objectGet(obj, props) {
|
|
29
|
+
const nextPropsArr = props.split(constants.PROP_DELIMITER);
|
|
30
|
+
const [, prop, isArr, isOpt] = constants.REGEX_PROP_IS_ARR_IS_OPT.exec(nextPropsArr.shift()) || [];
|
|
31
|
+
const nextProps = nextPropsArr.join(constants.PROP_DELIMITER);
|
|
32
|
+
if (!prop) {
|
|
33
|
+
return obj;
|
|
34
|
+
}
|
|
35
|
+
if (!(prop in obj)) {
|
|
36
|
+
if (isOpt) {
|
|
37
|
+
return isArr ? [] : null;
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`RuntimeError: could not get property \`${prop}\``);
|
|
40
|
+
}
|
|
41
|
+
const nextObjs = obj[prop];
|
|
42
|
+
return isArr ? nextObjs.map(
|
|
43
|
+
(nextObj) => objectGet(nextObj, nextProps)
|
|
44
|
+
) : objectGet(nextObjs, nextProps);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function objectSet(data, props) {
|
|
48
|
+
const nextPropsArr = props.split(constants.PROP_DELIMITER);
|
|
49
|
+
const [, prop, isArr] = constants.REGEX_PROP_IS_ARR_IS_OPT.exec(nextPropsArr.shift()) || [];
|
|
50
|
+
const nextProps = nextPropsArr.join(constants.PROP_DELIMITER);
|
|
51
|
+
if (!prop) {
|
|
52
|
+
return data;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
[prop]: isArr ? data.map((nextData) => objectSet(nextData, nextProps)) : objectSet(data, nextProps)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function resolve(resource, resolver, options) {
|
|
60
|
+
if (!resource) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (!isResource(resource)) {
|
|
64
|
+
throw new Error(`InvalidArgumentError: invalid resource \`${resource}\``);
|
|
65
|
+
}
|
|
66
|
+
const response = (await fetchResource(resource, options)).clone();
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
throw new Error(`RuntimeError: could not fetch resource \`${resource}\``);
|
|
69
|
+
}
|
|
70
|
+
const obj = await response.json();
|
|
71
|
+
if (resolver === null) {
|
|
72
|
+
return obj;
|
|
73
|
+
}
|
|
74
|
+
const resourcesObj = Object.keys(resolver).map((props) => ({
|
|
75
|
+
[props]: objectGet(obj, props)
|
|
76
|
+
}));
|
|
77
|
+
const resourcesArr = Object.entries(
|
|
78
|
+
resourcesObj.reduce((result, val) => ({ ...result, ...val }), {})
|
|
79
|
+
);
|
|
80
|
+
const responses = await Promise.all(
|
|
81
|
+
resourcesArr.map(async ([props, resources]) => {
|
|
82
|
+
const nextResolver = resolver[props];
|
|
83
|
+
const data = Array.isArray(resources) ? await Promise.all(
|
|
84
|
+
resources.map(
|
|
85
|
+
async (nextResource) => resolve(nextResource, nextResolver, options)
|
|
86
|
+
)
|
|
87
|
+
) : await resolve(resources, nextResolver, options);
|
|
88
|
+
return [props, data];
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
return responses.reduce(
|
|
92
|
+
(result, [props, data]) => esToolkit.merge(result, objectSet(data, props)),
|
|
93
|
+
obj
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isObject(obj) {
|
|
98
|
+
return obj !== null && typeof obj === "object" && !Array.isArray(obj);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isResolver(resolver) {
|
|
102
|
+
if (resolver === null) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
const keys = Object.keys(resolver);
|
|
106
|
+
if (!keys.length) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return keys.map(
|
|
110
|
+
(key) => !key.startsWith(constants.PROP_DELIMITER) && !key.endsWith(constants.PROP_DELIMITER) && isResolver(resolver[key])
|
|
111
|
+
).reduce((result, val) => result && val, true);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function restql(resource, resolver, options = {}) {
|
|
115
|
+
if (!isObject(resolver) || !isResolver(resolver)) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`InvalidArgumentError: invalid resolver \`${JSON.stringify(resolver)}\``
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (!isObject(options)) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`InvalidArgumentError: invalid options \`${JSON.stringify(options)}\``
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return resolve(resource, resolver, options);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = restql;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface Resolver {
|
|
2
|
+
[key: string]: Resolver | null;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
declare function restql<T extends object = Record<string, unknown>>(resource: string, resolver: Resolver, options?: RequestInit): Promise<T>;
|
|
6
|
+
|
|
7
|
+
export { restql as default };
|
|
8
|
+
export type { Resolver };
|
|
@@ -50,22 +50,11 @@
|
|
|
50
50
|
return isPlainObject(value) || Array.isArray(value);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/**
|
|
54
|
-
* @constant {Object} responses The responses to cache.
|
|
55
|
-
*/
|
|
56
53
|
const responses = {};
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Fetches a resource based on its options, if not cached.
|
|
60
|
-
*
|
|
61
|
-
* @param {string} resource The resource to fetch.
|
|
62
|
-
* @param {Object} options The options to bypass.
|
|
63
|
-
* @returns {Promise<Object>} A promise which resolves into an object.
|
|
64
|
-
*/
|
|
65
54
|
async function fetchResource(resource, options) {
|
|
66
55
|
const key = `${resource}-${JSON.stringify(options)}`;
|
|
67
56
|
if (!(key in responses)) {
|
|
68
|
-
responses[key] = fetch(resource, options).then(response => {
|
|
57
|
+
responses[key] = fetch(resource, options).then((response) => {
|
|
69
58
|
responses[key] = response;
|
|
70
59
|
return response;
|
|
71
60
|
});
|
|
@@ -73,42 +62,17 @@
|
|
|
73
62
|
return responses[key];
|
|
74
63
|
}
|
|
75
64
|
|
|
76
|
-
/**
|
|
77
|
-
* Determines whether or not a resource is valid.
|
|
78
|
-
*
|
|
79
|
-
* @param {string} resource The resource to test.
|
|
80
|
-
* @returns {boolean} Whether or not a resource is valid.
|
|
81
|
-
*/
|
|
82
65
|
function isResource(resource) {
|
|
83
66
|
return Boolean(URL.parse(resource));
|
|
84
67
|
}
|
|
85
68
|
|
|
86
|
-
|
|
87
|
-
* @constant {string} PROP_DELIMITER The delimiter of a property.
|
|
88
|
-
*/
|
|
89
|
-
const PROP_DELIMITER = '.';
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* @constant {RegExp} REGEX_PROP_IS_ARR_IS_OPT Determines whether or not a property is an array and/or is optional.
|
|
93
|
-
*/
|
|
69
|
+
const PROP_DELIMITER = ".";
|
|
94
70
|
const REGEX_PROP_IS_ARR_IS_OPT = /^([^[\]?]+)(\[])?(\?)?$/;
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* @constant {Object} constants The constants of the library.
|
|
98
|
-
*/
|
|
99
71
|
const constants = Object.freeze({
|
|
100
72
|
PROP_DELIMITER,
|
|
101
73
|
REGEX_PROP_IS_ARR_IS_OPT
|
|
102
74
|
});
|
|
103
75
|
|
|
104
|
-
/**
|
|
105
|
-
* Gets the parsed properties from an object.
|
|
106
|
-
*
|
|
107
|
-
* @param {Object} obj The object to use.
|
|
108
|
-
* @param {string} props The properties to apply.
|
|
109
|
-
* @returns {Array} An array.
|
|
110
|
-
* @throws {RuntimeError} If a property could not be got.
|
|
111
|
-
*/
|
|
112
76
|
function objectGet(obj, props) {
|
|
113
77
|
const nextPropsArr = props.split(constants.PROP_DELIMITER);
|
|
114
78
|
const [, prop, isArr, isOpt] = constants.REGEX_PROP_IS_ARR_IS_OPT.exec(nextPropsArr.shift()) || [];
|
|
@@ -123,16 +87,11 @@
|
|
|
123
87
|
throw new Error(`RuntimeError: could not get property \`${prop}\``);
|
|
124
88
|
}
|
|
125
89
|
const nextObjs = obj[prop];
|
|
126
|
-
return isArr ? nextObjs.map(
|
|
90
|
+
return isArr ? nextObjs.map(
|
|
91
|
+
(nextObj) => objectGet(nextObj, nextProps)
|
|
92
|
+
) : objectGet(nextObjs, nextProps);
|
|
127
93
|
}
|
|
128
94
|
|
|
129
|
-
/**
|
|
130
|
-
* Sets the parsed properties to an object.
|
|
131
|
-
*
|
|
132
|
-
* @param {Array} data The data to use.
|
|
133
|
-
* @param {string} props The properties to apply.
|
|
134
|
-
* @returns {Object} An object.
|
|
135
|
-
*/
|
|
136
95
|
function objectSet(data, props) {
|
|
137
96
|
const nextPropsArr = props.split(constants.PROP_DELIMITER);
|
|
138
97
|
const [, prop, isArr] = constants.REGEX_PROP_IS_ARR_IS_OPT.exec(nextPropsArr.shift()) || [];
|
|
@@ -141,20 +100,10 @@
|
|
|
141
100
|
return data;
|
|
142
101
|
}
|
|
143
102
|
return {
|
|
144
|
-
[prop]: isArr ? data.map(nextData => objectSet(nextData, nextProps)) : objectSet(data, nextProps)
|
|
103
|
+
[prop]: isArr ? data.map((nextData) => objectSet(nextData, nextProps)) : objectSet(data, nextProps)
|
|
145
104
|
};
|
|
146
105
|
}
|
|
147
106
|
|
|
148
|
-
/**
|
|
149
|
-
* Resolves the nested-linked resources of a RESTful API.
|
|
150
|
-
*
|
|
151
|
-
* @param {string} resource The resource to fetch.
|
|
152
|
-
* @param {Object} resolver The resolver to apply.
|
|
153
|
-
* @param {Object} options The options to bypass.
|
|
154
|
-
* @returns {Promise<Object>} A promise which resolves into an object.
|
|
155
|
-
* @throws {InvalidArgumentError} If a resource is invalid.
|
|
156
|
-
* @throws {RuntimeError} If a resource could not be fetched.
|
|
157
|
-
*/
|
|
158
107
|
async function resolve(resource, resolver, options) {
|
|
159
108
|
if (!resource) {
|
|
160
109
|
return null;
|
|
@@ -167,67 +116,59 @@
|
|
|
167
116
|
throw new Error(`RuntimeError: could not fetch resource \`${resource}\``);
|
|
168
117
|
}
|
|
169
118
|
const obj = await response.json();
|
|
170
|
-
if (
|
|
119
|
+
if (resolver === null) {
|
|
171
120
|
return obj;
|
|
172
121
|
}
|
|
173
|
-
const resourcesObj = Object.keys(resolver).map(props => ({
|
|
122
|
+
const resourcesObj = Object.keys(resolver).map((props) => ({
|
|
174
123
|
[props]: objectGet(obj, props)
|
|
175
124
|
}));
|
|
176
|
-
const resourcesArr = Object.entries(
|
|
177
|
-
...result,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
125
|
+
const resourcesArr = Object.entries(
|
|
126
|
+
resourcesObj.reduce((result, val) => ({ ...result, ...val }), {})
|
|
127
|
+
);
|
|
128
|
+
const responses = await Promise.all(
|
|
129
|
+
resourcesArr.map(async ([props, resources]) => {
|
|
130
|
+
const nextResolver = resolver[props];
|
|
131
|
+
const data = Array.isArray(resources) ? await Promise.all(
|
|
132
|
+
resources.map(
|
|
133
|
+
async (nextResource) => resolve(nextResource, nextResolver, options)
|
|
134
|
+
)
|
|
135
|
+
) : await resolve(resources, nextResolver, options);
|
|
136
|
+
return [props, data];
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
return responses.reduce(
|
|
140
|
+
(result, [props, data]) => merge(result, objectSet(data, props)),
|
|
141
|
+
obj
|
|
142
|
+
);
|
|
186
143
|
}
|
|
187
144
|
|
|
188
|
-
/**
|
|
189
|
-
* Determines whether or not an object is valid.
|
|
190
|
-
*
|
|
191
|
-
* @param {Object} obj The object to test.
|
|
192
|
-
* @returns {boolean} Whether or not an object is valid.
|
|
193
|
-
*/
|
|
194
145
|
function isObject(obj) {
|
|
195
|
-
return obj !== null && typeof obj ===
|
|
146
|
+
return obj !== null && typeof obj === "object" && !Array.isArray(obj);
|
|
196
147
|
}
|
|
197
148
|
|
|
198
|
-
/**
|
|
199
|
-
* Determines whether or not a resolver is valid.
|
|
200
|
-
*
|
|
201
|
-
* @param {Object} resolver The resolver to test.
|
|
202
|
-
* @returns {boolean} Whether or not a resolver is valid.
|
|
203
|
-
*/
|
|
204
149
|
function isResolver(resolver) {
|
|
205
|
-
if (
|
|
150
|
+
if (resolver === null) {
|
|
206
151
|
return true;
|
|
207
152
|
}
|
|
208
153
|
const keys = Object.keys(resolver);
|
|
209
154
|
if (!keys.length) {
|
|
210
155
|
return false;
|
|
211
156
|
}
|
|
212
|
-
return keys.map(
|
|
157
|
+
return keys.map(
|
|
158
|
+
(key) => !key.startsWith(constants.PROP_DELIMITER) && !key.endsWith(constants.PROP_DELIMITER) && isResolver(resolver[key])
|
|
159
|
+
).reduce((result, val) => result && val, true);
|
|
213
160
|
}
|
|
214
161
|
|
|
215
|
-
/**
|
|
216
|
-
* Resolves the nested-linked resources of a RESTful API.
|
|
217
|
-
*
|
|
218
|
-
* @param {string} resource The resource to fetch.
|
|
219
|
-
* @param {Object} resolver The resolver to apply.
|
|
220
|
-
* @param {Object} [options] The options to bypass.
|
|
221
|
-
* @returns {Promise<Object>} A promise which resolves into an object.
|
|
222
|
-
* @throws {InvalidArgumentError} If a resolver is invalid.
|
|
223
|
-
* @throws {InvalidArgumentError} If an options are invalid.
|
|
224
|
-
*/
|
|
225
162
|
async function restql(resource, resolver, options = {}) {
|
|
226
163
|
if (!isObject(resolver) || !isResolver(resolver)) {
|
|
227
|
-
throw new Error(
|
|
164
|
+
throw new Error(
|
|
165
|
+
`InvalidArgumentError: invalid resolver \`${JSON.stringify(resolver)}\``
|
|
166
|
+
);
|
|
228
167
|
}
|
|
229
168
|
if (!isObject(options)) {
|
|
230
|
-
throw new Error(
|
|
169
|
+
throw new Error(
|
|
170
|
+
`InvalidArgumentError: invalid options \`${JSON.stringify(options)}\``
|
|
171
|
+
);
|
|
231
172
|
}
|
|
232
173
|
return resolve(resource, resolver, options);
|
|
233
174
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(l,a){typeof exports=="object"&&typeof module<"u"?module.exports=a():typeof define=="function"&&define.amd?define(a):(l=typeof globalThis<"u"?globalThis:l||self,l.restql=a())})(this,(function(){"use strict";function l(e){if(!e||typeof e!="object")return!1;const n=Object.getPrototypeOf(e);return n===null||n===Object.prototype||Object.getPrototypeOf(n)===null?Object.prototype.toString.call(e)==="[object Object]":!1}function a(e){return e==="__proto__"}function y(e,n){const t=Object.keys(n);for(let r=0;r<t.length;r++){const o=t[r];if(a(o))continue;const i=n[o],c=e[o];d(i)&&d(c)?e[o]=y(c,i):Array.isArray(i)?e[o]=y([],i):l(i)?e[o]=y({},i):(c===void 0||i!==void 0)&&(e[o]=i)}return e}function d(e){return l(e)||Array.isArray(e)}const R={};async function j(e,n){const t=`${e}-${JSON.stringify(n)}`;return t in R||(R[t]=fetch(e,n).then(r=>(R[t]=r,r))),R[t]}function h(e){return!!URL.parse(e)}const b=/^([^[\]?]+)(\[])?(\?)?$/,f=Object.freeze({PROP_DELIMITER:".",REGEX_PROP_IS_ARR_IS_OPT:b});function O(e,n){const t=n.split(f.PROP_DELIMITER),[,r,o,i]=f.REGEX_PROP_IS_ARR_IS_OPT.exec(t.shift())||[],c=t.join(f.PROP_DELIMITER);if(!r)return e;if(!(r in e)){if(i)return o?[]:null;throw new Error(`RuntimeError: could not get property \`${r}\``)}const s=e[r];return o?s.map(u=>O(u,c)):O(s,c)}function P(e,n){const t=n.split(f.PROP_DELIMITER),[,r,o]=f.REGEX_PROP_IS_ARR_IS_OPT.exec(t.shift())||[],i=t.join(f.PROP_DELIMITER);return r?{[r]:o?e.map(c=>P(c,i)):P(e,i)}:e}async function E(e,n,t){if(!e)return null;if(!h(e))throw new Error(`InvalidArgumentError: invalid resource \`${e}\``);const r=(await j(e,t)).clone();if(!r.ok)throw new Error(`RuntimeError: could not fetch resource \`${e}\``);const o=await r.json();if(n===null)return o;const i=Object.keys(n).map(s=>({[s]:O(o,s)})),c=Object.entries(i.reduce((s,u)=>({...s,...u}),{}));return(await Promise.all(c.map(async([s,u])=>{const p=n[s],w=Array.isArray(u)?await Promise.all(u.map(async A=>E(A,p,t))):await E(u,p,t);return[s,w]}))).reduce((s,[u,p])=>y(s,P(p,u)),o)}function _(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function I(e){if(e===null)return!0;const n=Object.keys(e);return n.length?n.map(t=>!t.startsWith(f.PROP_DELIMITER)&&!t.endsWith(f.PROP_DELIMITER)&&I(e[t])).reduce((t,r)=>t&&r,!0):!1}async function m(e,n,t={}){if(!_(n)||!I(n))throw new Error(`InvalidArgumentError: invalid resolver \`${JSON.stringify(n)}\``);if(!_(t))throw new Error(`InvalidArgumentError: invalid options \`${JSON.stringify(t)}\``);return E(e,n,t)}return m}));
|
|
2
|
+
//# sourceMappingURL=index.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.min.js","sources":["../node_modules/es-toolkit/dist/predicate/isPlainObject.mjs","../node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs","../node_modules/es-toolkit/dist/object/merge.mjs","../src/utils/fetchResource.ts","../src/utils/isResource.ts","../src/constants.ts","../src/utils/objectGet.ts","../src/utils/objectSet.ts","../src/resolve.ts","../src/utils/isObject.ts","../src/utils/isResolver.ts","../src/restql.ts"],"sourcesContent":["function isPlainObject(value) {\n if (!value || typeof value !== 'object') {\n return false;\n }\n const proto = Object.getPrototypeOf(value);\n const hasObjectPrototype = proto === null ||\n proto === Object.prototype ||\n Object.getPrototypeOf(proto) === null;\n if (!hasObjectPrototype) {\n return false;\n }\n return Object.prototype.toString.call(value) === '[object Object]';\n}\n\nexport { isPlainObject };\n","function isUnsafeProperty(key) {\n return key === '__proto__';\n}\n\nexport { isUnsafeProperty };\n","import { isUnsafeProperty } from '../_internal/isUnsafeProperty.mjs';\nimport { isPlainObject } from '../predicate/isPlainObject.mjs';\n\nfunction merge(target, source) {\n const sourceKeys = Object.keys(source);\n for (let i = 0; i < sourceKeys.length; i++) {\n const key = sourceKeys[i];\n if (isUnsafeProperty(key)) {\n continue;\n }\n const sourceValue = source[key];\n const targetValue = target[key];\n if (isMergeableValue(sourceValue) && isMergeableValue(targetValue)) {\n target[key] = merge(targetValue, sourceValue);\n }\n else if (Array.isArray(sourceValue)) {\n target[key] = merge([], sourceValue);\n }\n else if (isPlainObject(sourceValue)) {\n target[key] = merge({}, sourceValue);\n }\n else if (targetValue === undefined || sourceValue !== undefined) {\n target[key] = sourceValue;\n }\n }\n return target;\n}\nfunction isMergeableValue(value) {\n return isPlainObject(value) || Array.isArray(value);\n}\n\nexport { merge };\n","const responses: Record<string, Promise<Response> | Response> = {}\n\nexport default async function fetchResource(\n resource: string,\n options: RequestInit,\n): Promise<Response> {\n const key = `${resource}-${JSON.stringify(options)}`\n\n if (!(key in responses)) {\n responses[key] = fetch(resource, options).then((response) => {\n responses[key] = response\n\n return response\n })\n }\n\n return responses[key]\n}\n","export default function isResource(resource: string): boolean {\n return Boolean(URL.parse(resource))\n}\n","const PROP_DELIMITER: string = '.'\n\nconst REGEX_PROP_IS_ARR_IS_OPT: RegExp = /^([^[\\]?]+)(\\[])?(\\?)?$/\n\nconst constants = Object.freeze({\n PROP_DELIMITER,\n REGEX_PROP_IS_ARR_IS_OPT,\n})\n\nexport default constants\n","import constants from '../constants'\n\nexport default function objectGet(\n obj: Record<string, unknown>,\n props: string,\n): unknown {\n const nextPropsArr = props.split(constants.PROP_DELIMITER)\n\n const [, prop, isArr, isOpt] =\n constants.REGEX_PROP_IS_ARR_IS_OPT.exec(nextPropsArr.shift()!) || []\n\n const nextProps = nextPropsArr.join(constants.PROP_DELIMITER)\n\n if (!prop) {\n return obj\n }\n\n if (!(prop in obj)) {\n if (isOpt) {\n return isArr ? [] : null\n }\n\n throw new Error(`RuntimeError: could not get property \\`${prop}\\``)\n }\n\n const nextObjs = obj[prop]\n\n return isArr\n ? (nextObjs as unknown[]).map((nextObj) =>\n objectGet(nextObj as Record<string, unknown>, nextProps),\n )\n : objectGet(nextObjs as Record<string, unknown>, nextProps)\n}\n","import constants from '../constants'\n\nexport default function objectSet(\n data: unknown,\n props: string,\n): Record<string, unknown> {\n const nextPropsArr = props.split(constants.PROP_DELIMITER)\n\n const [, prop, isArr] =\n constants.REGEX_PROP_IS_ARR_IS_OPT.exec(nextPropsArr.shift()!) || []\n\n const nextProps = nextPropsArr.join(constants.PROP_DELIMITER)\n\n if (!prop) {\n return data as Record<string, unknown>\n }\n\n return {\n [prop]: isArr\n ? (data as unknown[]).map((nextData) => objectSet(nextData, nextProps))\n : objectSet(data, nextProps),\n }\n}\n","import { merge } from 'es-toolkit'\n\nimport type { Resolver } from './types'\nimport fetchResource from './utils/fetchResource'\nimport isResource from './utils/isResource'\nimport objectGet from './utils/objectGet'\nimport objectSet from './utils/objectSet'\n\nexport default async function resolve(\n resource: string,\n resolver: Resolver | null,\n options: RequestInit,\n): Promise<Record<string, unknown> | null> {\n if (!resource) {\n return null\n }\n\n if (!isResource(resource)) {\n throw new Error(`InvalidArgumentError: invalid resource \\`${resource}\\``)\n }\n\n const response = (await fetchResource(resource, options)).clone()\n\n if (!response.ok) {\n throw new Error(`RuntimeError: could not fetch resource \\`${resource}\\``)\n }\n\n const obj = await response.json()\n\n if (resolver === null) {\n return obj\n }\n\n const resourcesObj = Object.keys(resolver).map((props) => ({\n [props]: objectGet(obj, props),\n }))\n\n const resourcesArr = Object.entries(\n resourcesObj.reduce((result, val) => ({ ...result, ...val }), {}),\n )\n\n const responses = await Promise.all(\n resourcesArr.map(async ([props, resources]) => {\n const nextResolver = resolver[props]\n\n const data = Array.isArray(resources)\n ? await Promise.all(\n resources.map(async (nextResource: string) =>\n resolve(nextResource, nextResolver, options),\n ),\n )\n : await resolve(resources as string, nextResolver, options)\n\n return [props, data]\n }),\n )\n\n return responses.reduce(\n (result, [props, data]) => merge(result, objectSet(data, props as string)),\n obj,\n )\n}\n","export default function isObject(obj: unknown): boolean {\n return obj !== null && typeof obj === 'object' && !Array.isArray(obj)\n}\n","import constants from '../constants'\nimport type { Resolver } from '../types'\n\nexport default function isResolver(resolver: Resolver | null): boolean {\n if (resolver === null) {\n return true\n }\n\n const keys = Object.keys(resolver)\n\n if (!keys.length) {\n return false\n }\n\n return keys\n .map(\n (key) =>\n !key.startsWith(constants.PROP_DELIMITER) &&\n !key.endsWith(constants.PROP_DELIMITER) &&\n isResolver(resolver[key]),\n )\n .reduce((result, val) => result && val, true)\n}\n","import resolve from './resolve'\nimport type { Resolver } from './types'\nimport isObject from './utils/isObject'\nimport isResolver from './utils/isResolver'\n\nexport default async function restql<\n T extends object = Record<string, unknown>,\n>(resource: string, resolver: Resolver, options: RequestInit = {}): Promise<T> {\n if (!isObject(resolver) || !isResolver(resolver)) {\n throw new Error(\n `InvalidArgumentError: invalid resolver \\`${JSON.stringify(resolver)}\\``,\n )\n }\n\n if (!isObject(options)) {\n throw new Error(\n `InvalidArgumentError: invalid options \\`${JSON.stringify(options)}\\``,\n )\n }\n\n return resolve(resource, resolver, options) as Promise<T>\n}\n"],"names":["isPlainObject","value","proto","isUnsafeProperty","key","merge","target","source","sourceKeys","i","sourceValue","targetValue","isMergeableValue","responses","fetchResource","resource","options","response","isResource","REGEX_PROP_IS_ARR_IS_OPT","constants","objectGet","obj","props","nextPropsArr","prop","isArr","isOpt","nextProps","nextObjs","nextObj","objectSet","data","nextData","resolve","resolver","resourcesObj","resourcesArr","result","val","resources","nextResolver","nextResource","isObject","isResolver","keys","restql"],"mappings":"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","x_google_ignoreList":[0,1,2]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { merge } from 'es-toolkit';
|
|
2
|
+
|
|
3
|
+
const responses = {};
|
|
4
|
+
async function fetchResource(resource, options) {
|
|
5
|
+
const key = `${resource}-${JSON.stringify(options)}`;
|
|
6
|
+
if (!(key in responses)) {
|
|
7
|
+
responses[key] = fetch(resource, options).then((response) => {
|
|
8
|
+
responses[key] = response;
|
|
9
|
+
return response;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return responses[key];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isResource(resource) {
|
|
16
|
+
return Boolean(URL.parse(resource));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const PROP_DELIMITER = ".";
|
|
20
|
+
const REGEX_PROP_IS_ARR_IS_OPT = /^([^[\]?]+)(\[])?(\?)?$/;
|
|
21
|
+
const constants = Object.freeze({
|
|
22
|
+
PROP_DELIMITER,
|
|
23
|
+
REGEX_PROP_IS_ARR_IS_OPT
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function objectGet(obj, props) {
|
|
27
|
+
const nextPropsArr = props.split(constants.PROP_DELIMITER);
|
|
28
|
+
const [, prop, isArr, isOpt] = constants.REGEX_PROP_IS_ARR_IS_OPT.exec(nextPropsArr.shift()) || [];
|
|
29
|
+
const nextProps = nextPropsArr.join(constants.PROP_DELIMITER);
|
|
30
|
+
if (!prop) {
|
|
31
|
+
return obj;
|
|
32
|
+
}
|
|
33
|
+
if (!(prop in obj)) {
|
|
34
|
+
if (isOpt) {
|
|
35
|
+
return isArr ? [] : null;
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`RuntimeError: could not get property \`${prop}\``);
|
|
38
|
+
}
|
|
39
|
+
const nextObjs = obj[prop];
|
|
40
|
+
return isArr ? nextObjs.map(
|
|
41
|
+
(nextObj) => objectGet(nextObj, nextProps)
|
|
42
|
+
) : objectGet(nextObjs, nextProps);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function objectSet(data, props) {
|
|
46
|
+
const nextPropsArr = props.split(constants.PROP_DELIMITER);
|
|
47
|
+
const [, prop, isArr] = constants.REGEX_PROP_IS_ARR_IS_OPT.exec(nextPropsArr.shift()) || [];
|
|
48
|
+
const nextProps = nextPropsArr.join(constants.PROP_DELIMITER);
|
|
49
|
+
if (!prop) {
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
[prop]: isArr ? data.map((nextData) => objectSet(nextData, nextProps)) : objectSet(data, nextProps)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function resolve(resource, resolver, options) {
|
|
58
|
+
if (!resource) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
if (!isResource(resource)) {
|
|
62
|
+
throw new Error(`InvalidArgumentError: invalid resource \`${resource}\``);
|
|
63
|
+
}
|
|
64
|
+
const response = (await fetchResource(resource, options)).clone();
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new Error(`RuntimeError: could not fetch resource \`${resource}\``);
|
|
67
|
+
}
|
|
68
|
+
const obj = await response.json();
|
|
69
|
+
if (resolver === null) {
|
|
70
|
+
return obj;
|
|
71
|
+
}
|
|
72
|
+
const resourcesObj = Object.keys(resolver).map((props) => ({
|
|
73
|
+
[props]: objectGet(obj, props)
|
|
74
|
+
}));
|
|
75
|
+
const resourcesArr = Object.entries(
|
|
76
|
+
resourcesObj.reduce((result, val) => ({ ...result, ...val }), {})
|
|
77
|
+
);
|
|
78
|
+
const responses = await Promise.all(
|
|
79
|
+
resourcesArr.map(async ([props, resources]) => {
|
|
80
|
+
const nextResolver = resolver[props];
|
|
81
|
+
const data = Array.isArray(resources) ? await Promise.all(
|
|
82
|
+
resources.map(
|
|
83
|
+
async (nextResource) => resolve(nextResource, nextResolver, options)
|
|
84
|
+
)
|
|
85
|
+
) : await resolve(resources, nextResolver, options);
|
|
86
|
+
return [props, data];
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
return responses.reduce(
|
|
90
|
+
(result, [props, data]) => merge(result, objectSet(data, props)),
|
|
91
|
+
obj
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isObject(obj) {
|
|
96
|
+
return obj !== null && typeof obj === "object" && !Array.isArray(obj);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isResolver(resolver) {
|
|
100
|
+
if (resolver === null) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
const keys = Object.keys(resolver);
|
|
104
|
+
if (!keys.length) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return keys.map(
|
|
108
|
+
(key) => !key.startsWith(constants.PROP_DELIMITER) && !key.endsWith(constants.PROP_DELIMITER) && isResolver(resolver[key])
|
|
109
|
+
).reduce((result, val) => result && val, true);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function restql(resource, resolver, options = {}) {
|
|
113
|
+
if (!isObject(resolver) || !isResolver(resolver)) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`InvalidArgumentError: invalid resolver \`${JSON.stringify(resolver)}\``
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (!isObject(options)) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`InvalidArgumentError: invalid options \`${JSON.stringify(options)}\``
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return resolve(resource, resolver, options);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { restql as default };
|
package/package.json
CHANGED
|
@@ -1,28 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "restql",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "RESTful API Resolver for Nested-Linked Resources | 🕸 🕷",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
6
|
+
"unpkg": "./dist/index.js",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.cjs",
|
|
15
|
+
"default": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
9
18
|
"files": [
|
|
10
19
|
"dist/"
|
|
11
20
|
],
|
|
12
21
|
"scripts": {
|
|
13
|
-
"prepare": "npm run
|
|
14
|
-
"
|
|
22
|
+
"prepare": "npm run clean && npm run lint && npm run test && npm run build",
|
|
23
|
+
"clean": "rm -fr ./coverage/ ./dist/ ./stats.html",
|
|
15
24
|
"lint": "eslint ./src/",
|
|
16
25
|
"lint:fix": "npm run lint -- --fix",
|
|
17
|
-
"test": "jest
|
|
26
|
+
"test": "jest ./src/",
|
|
18
27
|
"test:coverage": "npm run test -- --coverage",
|
|
19
28
|
"test:watch": "npm run test -- --watch",
|
|
20
|
-
"
|
|
21
|
-
"build": "
|
|
22
|
-
"build:
|
|
23
|
-
"build:
|
|
24
|
-
"build:umd": "
|
|
25
|
-
"build:
|
|
29
|
+
"build": "npm run build:cjs && npm run build:esm && npm run build:umd && npm run build:umd:min && npm run build:types",
|
|
30
|
+
"build:cjs": "MODULE_FMT=cjs rollup --config",
|
|
31
|
+
"build:esm": "MODULE_FMT=esm rollup --config",
|
|
32
|
+
"build:umd": "MODULE_FMT=umd rollup --config",
|
|
33
|
+
"build:umd:min": "MODULE_FMT=umd MINIFY=true rollup --config",
|
|
34
|
+
"build:types": "MODULE_FMT=esm TYPES=true rollup --config",
|
|
35
|
+
"playground": "tsx ./playground/index.ts"
|
|
26
36
|
},
|
|
27
37
|
"repository": {
|
|
28
38
|
"type": "git",
|
|
@@ -30,7 +40,7 @@
|
|
|
30
40
|
},
|
|
31
41
|
"keywords": [
|
|
32
42
|
"package",
|
|
33
|
-
"
|
|
43
|
+
"typescript",
|
|
34
44
|
"rest",
|
|
35
45
|
"api",
|
|
36
46
|
"recursive",
|
|
@@ -46,25 +56,26 @@
|
|
|
46
56
|
"es-toolkit": "^1.44.0"
|
|
47
57
|
},
|
|
48
58
|
"devDependencies": {
|
|
49
|
-
"@babel/core": "^7.26.0",
|
|
50
|
-
"@babel/node": "^7.29.0",
|
|
51
|
-
"@babel/preset-env": "^7.26.0",
|
|
52
|
-
"@rollup/plugin-babel": "^6.1.0",
|
|
53
|
-
"@rollup/plugin-commonjs": "^29.0.0",
|
|
54
|
-
"@rollup/plugin-json": "^6.1.0",
|
|
55
59
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
56
|
-
"@rollup/plugin-terser": "^0.4.4",
|
|
57
60
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
|
58
|
-
"
|
|
61
|
+
"@types/jest": "^30.0.0",
|
|
62
|
+
"eslint": "^10.0.3",
|
|
59
63
|
"eslint-config-prettier": "^10.1.8",
|
|
60
64
|
"eslint-flat-config-airbnb": "^2.0.5",
|
|
65
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
61
66
|
"eslint-plugin-jest": "^29.15.0",
|
|
62
67
|
"eslint-plugin-prettier": "^5.5.5",
|
|
63
68
|
"globals": "^17.3.0",
|
|
64
69
|
"jest": "^30.2.0",
|
|
65
70
|
"prettier": "^3.8.1",
|
|
66
71
|
"rollup": "^4.59.0",
|
|
67
|
-
"rollup-plugin-
|
|
72
|
+
"rollup-plugin-dts": "^6.3.0",
|
|
73
|
+
"rollup-plugin-esbuild": "^6.2.1",
|
|
74
|
+
"rollup-plugin-visualizer": "^7.0.0",
|
|
75
|
+
"ts-jest": "^29.4.6",
|
|
76
|
+
"tsx": "^4.21.0",
|
|
77
|
+
"typescript": "^5.9.3",
|
|
78
|
+
"typescript-eslint": "^8.56.1"
|
|
68
79
|
},
|
|
69
80
|
"overrides": {
|
|
70
81
|
"serialize-javascript": "^7.0.3"
|
package/dist/cjs/index.js
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var esToolkit = require('es-toolkit');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @constant {Object} responses The responses to cache.
|
|
7
|
-
*/
|
|
8
|
-
const responses = {};
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Fetches a resource based on its options, if not cached.
|
|
12
|
-
*
|
|
13
|
-
* @param {string} resource The resource to fetch.
|
|
14
|
-
* @param {Object} options The options to bypass.
|
|
15
|
-
* @returns {Promise<Object>} A promise which resolves into an object.
|
|
16
|
-
*/
|
|
17
|
-
async function fetchResource(resource, options) {
|
|
18
|
-
const key = `${resource}-${JSON.stringify(options)}`;
|
|
19
|
-
if (!(key in responses)) {
|
|
20
|
-
responses[key] = fetch(resource, options).then(response => {
|
|
21
|
-
responses[key] = response;
|
|
22
|
-
return response;
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
return responses[key];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Determines whether or not a resource is valid.
|
|
30
|
-
*
|
|
31
|
-
* @param {string} resource The resource to test.
|
|
32
|
-
* @returns {boolean} Whether or not a resource is valid.
|
|
33
|
-
*/
|
|
34
|
-
function isResource(resource) {
|
|
35
|
-
return Boolean(URL.parse(resource));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @constant {string} PROP_DELIMITER The delimiter of a property.
|
|
40
|
-
*/
|
|
41
|
-
const PROP_DELIMITER = '.';
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @constant {RegExp} REGEX_PROP_IS_ARR_IS_OPT Determines whether or not a property is an array and/or is optional.
|
|
45
|
-
*/
|
|
46
|
-
const REGEX_PROP_IS_ARR_IS_OPT = /^([^[\]?]+)(\[])?(\?)?$/;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* @constant {Object} constants The constants of the library.
|
|
50
|
-
*/
|
|
51
|
-
const constants = Object.freeze({
|
|
52
|
-
PROP_DELIMITER,
|
|
53
|
-
REGEX_PROP_IS_ARR_IS_OPT
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Gets the parsed properties from an object.
|
|
58
|
-
*
|
|
59
|
-
* @param {Object} obj The object to use.
|
|
60
|
-
* @param {string} props The properties to apply.
|
|
61
|
-
* @returns {Array} An array.
|
|
62
|
-
* @throws {RuntimeError} If a property could not be got.
|
|
63
|
-
*/
|
|
64
|
-
function objectGet(obj, props) {
|
|
65
|
-
const nextPropsArr = props.split(constants.PROP_DELIMITER);
|
|
66
|
-
const [, prop, isArr, isOpt] = constants.REGEX_PROP_IS_ARR_IS_OPT.exec(nextPropsArr.shift()) || [];
|
|
67
|
-
const nextProps = nextPropsArr.join(constants.PROP_DELIMITER);
|
|
68
|
-
if (!prop) {
|
|
69
|
-
return obj;
|
|
70
|
-
}
|
|
71
|
-
if (!(prop in obj)) {
|
|
72
|
-
if (isOpt) {
|
|
73
|
-
return isArr ? [] : null;
|
|
74
|
-
}
|
|
75
|
-
throw new Error(`RuntimeError: could not get property \`${prop}\``);
|
|
76
|
-
}
|
|
77
|
-
const nextObjs = obj[prop];
|
|
78
|
-
return isArr ? nextObjs.map(nextObj => objectGet(nextObj, nextProps)) : objectGet(nextObjs, nextProps);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Sets the parsed properties to an object.
|
|
83
|
-
*
|
|
84
|
-
* @param {Array} data The data to use.
|
|
85
|
-
* @param {string} props The properties to apply.
|
|
86
|
-
* @returns {Object} An object.
|
|
87
|
-
*/
|
|
88
|
-
function objectSet(data, props) {
|
|
89
|
-
const nextPropsArr = props.split(constants.PROP_DELIMITER);
|
|
90
|
-
const [, prop, isArr] = constants.REGEX_PROP_IS_ARR_IS_OPT.exec(nextPropsArr.shift()) || [];
|
|
91
|
-
const nextProps = nextPropsArr.join(constants.PROP_DELIMITER);
|
|
92
|
-
if (!prop) {
|
|
93
|
-
return data;
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
[prop]: isArr ? data.map(nextData => objectSet(nextData, nextProps)) : objectSet(data, nextProps)
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Resolves the nested-linked resources of a RESTful API.
|
|
102
|
-
*
|
|
103
|
-
* @param {string} resource The resource to fetch.
|
|
104
|
-
* @param {Object} resolver The resolver to apply.
|
|
105
|
-
* @param {Object} options The options to bypass.
|
|
106
|
-
* @returns {Promise<Object>} A promise which resolves into an object.
|
|
107
|
-
* @throws {InvalidArgumentError} If a resource is invalid.
|
|
108
|
-
* @throws {RuntimeError} If a resource could not be fetched.
|
|
109
|
-
*/
|
|
110
|
-
async function resolve(resource, resolver, options) {
|
|
111
|
-
if (!resource) {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
if (!isResource(resource)) {
|
|
115
|
-
throw new Error(`InvalidArgumentError: invalid resource \`${resource}\``);
|
|
116
|
-
}
|
|
117
|
-
const response = (await fetchResource(resource, options)).clone();
|
|
118
|
-
if (!response.ok) {
|
|
119
|
-
throw new Error(`RuntimeError: could not fetch resource \`${resource}\``);
|
|
120
|
-
}
|
|
121
|
-
const obj = await response.json();
|
|
122
|
-
if (!resolver) {
|
|
123
|
-
return obj;
|
|
124
|
-
}
|
|
125
|
-
const resourcesObj = Object.keys(resolver).map(props => ({
|
|
126
|
-
[props]: objectGet(obj, props)
|
|
127
|
-
}));
|
|
128
|
-
const resourcesArr = Object.entries(resourcesObj.reduce((result, val) => ({
|
|
129
|
-
...result,
|
|
130
|
-
...val
|
|
131
|
-
}), {}));
|
|
132
|
-
const responses = await Promise.all(resourcesArr.map(async ([props, resources]) => {
|
|
133
|
-
const nextResolver = resolver[props];
|
|
134
|
-
const data = Array.isArray(resources) ? await Promise.all(resources.map(async nextResource => resolve(nextResource, nextResolver, options))) : await resolve(resources, nextResolver, options);
|
|
135
|
-
return [props, data];
|
|
136
|
-
}));
|
|
137
|
-
return responses.reduce((result, [props, data]) => esToolkit.merge(result, objectSet(data, props)), obj);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Determines whether or not an object is valid.
|
|
142
|
-
*
|
|
143
|
-
* @param {Object} obj The object to test.
|
|
144
|
-
* @returns {boolean} Whether or not an object is valid.
|
|
145
|
-
*/
|
|
146
|
-
function isObject(obj) {
|
|
147
|
-
return obj !== null && typeof obj === 'object' && !Array.isArray(obj);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Determines whether or not a resolver is valid.
|
|
152
|
-
*
|
|
153
|
-
* @param {Object} resolver The resolver to test.
|
|
154
|
-
* @returns {boolean} Whether or not a resolver is valid.
|
|
155
|
-
*/
|
|
156
|
-
function isResolver(resolver) {
|
|
157
|
-
if (!resolver) {
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
const keys = Object.keys(resolver);
|
|
161
|
-
if (!keys.length) {
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
return keys.map(key => !key.startsWith(constants.PROP_DELIMITER) && !key.endsWith(constants.PROP_DELIMITER) && isResolver(resolver[key])).reduce((result, val) => result && val, true);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Resolves the nested-linked resources of a RESTful API.
|
|
169
|
-
*
|
|
170
|
-
* @param {string} resource The resource to fetch.
|
|
171
|
-
* @param {Object} resolver The resolver to apply.
|
|
172
|
-
* @param {Object} [options] The options to bypass.
|
|
173
|
-
* @returns {Promise<Object>} A promise which resolves into an object.
|
|
174
|
-
* @throws {InvalidArgumentError} If a resolver is invalid.
|
|
175
|
-
* @throws {InvalidArgumentError} If an options are invalid.
|
|
176
|
-
*/
|
|
177
|
-
async function restql(resource, resolver, options = {}) {
|
|
178
|
-
if (!isObject(resolver) || !isResolver(resolver)) {
|
|
179
|
-
throw new Error(`InvalidArgumentError: invalid resolver \`${JSON.stringify(resolver)}\``);
|
|
180
|
-
}
|
|
181
|
-
if (!isObject(options)) {
|
|
182
|
-
throw new Error(`InvalidArgumentError: invalid options \`${JSON.stringify(options)}\``);
|
|
183
|
-
}
|
|
184
|
-
return resolve(resource, resolver, options);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
module.exports = restql;
|
package/dist/esm/index.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
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 };
|
package/dist/umd/index.min.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
!function(t,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(t="undefined"!=typeof globalThis?globalThis:t||self).restql=r()}(this,function(){"use strict";function t(t){if(!t||"object"!=typeof t)return!1;const r=Object.getPrototypeOf(t);return!(null!==r&&r!==Object.prototype&&null!==Object.getPrototypeOf(r))&&"[object Object]"===Object.prototype.toString.call(t)}function r(t){return"__proto__"===t}function n(o,i){const c=Object.keys(i);for(let u=0;u<c.length;u++){const s=c[u];if(r(s))continue;const f=i[s],a=o[s];e(f)&&e(a)?o[s]=n(a,f):Array.isArray(f)?o[s]=n([],f):t(f)?o[s]=n({},f):void 0!==a&&void 0===f||(o[s]=f)}return o}function e(r){return t(r)||Array.isArray(r)}const o={};async function i(t,r){const n=`${t}-${JSON.stringify(r)}`;return n in o||(o[n]=fetch(t,r).then(t=>(o[n]=t,t))),o[n]}const c=Object.freeze({PROP_DELIMITER:".",REGEX_PROP_IS_ARR_IS_OPT:/^([^[\]?]+)(\[])?(\?)?$/});function u(t,r){const n=r.split(c.PROP_DELIMITER),[,e,o,i]=c.REGEX_PROP_IS_ARR_IS_OPT.exec(n.shift())||[],s=n.join(c.PROP_DELIMITER);if(!e)return t;if(!(e in t)){if(i)return o?[]:null;throw new Error(`RuntimeError: could not get property \`${e}\``)}const f=t[e];return o?f.map(t=>u(t,s)):u(f,s)}function s(t,r){const n=r.split(c.PROP_DELIMITER),[,e,o]=c.REGEX_PROP_IS_ARR_IS_OPT.exec(n.shift())||[],i=n.join(c.PROP_DELIMITER);return e?{[e]:o?t.map(t=>s(t,i)):s(t,i)}:t}async function f(t,r,e){if(!t)return null;if(!function(t){return Boolean(URL.parse(t))}(t))throw new Error(`InvalidArgumentError: invalid resource \`${t}\``);const o=(await i(t,e)).clone();if(!o.ok)throw new Error(`RuntimeError: could not fetch resource \`${t}\``);const c=await o.json();if(!r)return c;const a=Object.keys(r).map(t=>({[t]:u(c,t)})),l=Object.entries(a.reduce((t,r)=>({...t,...r}),{}));return(await Promise.all(l.map(async([t,n])=>{const o=r[t];return[t,Array.isArray(n)?await Promise.all(n.map(async t=>f(t,o,e))):await f(n,o,e)]}))).reduce((t,[r,e])=>n(t,s(e,r)),c)}function a(t){return null!==t&&"object"==typeof t&&!Array.isArray(t)}function l(t){if(!t)return!0;const r=Object.keys(t);return!!r.length&&r.map(r=>!r.startsWith(c.PROP_DELIMITER)&&!r.endsWith(c.PROP_DELIMITER)&&l(t[r])).reduce((t,r)=>t&&r,!0)}return async function(t,r,n={}){if(!a(r)||!l(r))throw new Error(`InvalidArgumentError: invalid resolver \`${JSON.stringify(r)}\``);if(!a(n))throw new Error(`InvalidArgumentError: invalid options \`${JSON.stringify(n)}\``);return f(t,r,n)}});
|