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 CHANGED
@@ -1,14 +1,14 @@
1
- # RestQL.js
1
+ # RestQL
2
2
 
3
3
  RESTful API Resolver for Nested-Linked Resources | 🕸 🕷
4
4
 
5
5
  ---
6
6
 
7
7
  [![npm](https://img.shields.io/npm/v/restql.svg?style=for-the-badge)](https://www.npmjs.com/package/restql/)
8
- ![JavaScript](https://img.shields.io/badge/-JavaScript-F0DB4F?style=for-the-badge&logo=typescript&logoColor=black)
8
+ ![TypeScript](https://img.shields.io/badge/-TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)
9
9
  ![Prettier](https://img.shields.io/badge/-Prettier-FF69B4?style=for-the-badge&logo=prettier&logoColor=white)
10
10
 
11
- RestQL allows you to dynamically resolve the nested-linked resources of a RESTful API.
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/umd/index.min.js"></script>
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` (`Object`): The resolver to apply.
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 resolves the sames and call the subsequent resolver against them.
46
- - Until the base case (`null`) is reached; from which it returns back the merged responses.
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]` (`Object`): The options to bypass.
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<Object>`): A promise which resolves into an object.
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.js)
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;
@@ -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(nextObj => objectGet(nextObj, nextProps)) : objectGet(nextObjs, nextProps);
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 (!resolver) {
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(resourcesObj.reduce((result, val) => ({
177
- ...result,
178
- ...val
179
- }), {}));
180
- const responses = await Promise.all(resourcesArr.map(async ([props, resources]) => {
181
- const nextResolver = resolver[props];
182
- const data = Array.isArray(resources) ? await Promise.all(resources.map(async nextResource => resolve(nextResource, nextResolver, options))) : await resolve(resources, nextResolver, options);
183
- return [props, data];
184
- }));
185
- return responses.reduce((result, [props, data]) => merge(result, objectSet(data, props)), obj);
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 === 'object' && !Array.isArray(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 (!resolver) {
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(key => !key.startsWith(constants.PROP_DELIMITER) && !key.endsWith(constants.PROP_DELIMITER) && isResolver(resolver[key])).reduce((result, val) => result && val, true);
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(`InvalidArgumentError: invalid resolver \`${JSON.stringify(resolver)}\``);
164
+ throw new Error(
165
+ `InvalidArgumentError: invalid resolver \`${JSON.stringify(resolver)}\``
166
+ );
228
167
  }
229
168
  if (!isObject(options)) {
230
- throw new Error(`InvalidArgumentError: invalid options \`${JSON.stringify(options)}\``);
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":"wNAAA,SAASA,EAAcC,EAAO,CAC1B,GAAI,CAACA,GAAS,OAAOA,GAAU,SAC3B,MAAO,GAEX,MAAMC,EAAQ,OAAO,eAAeD,CAAK,EAIzC,OAH2BC,IAAU,MACjCA,IAAU,OAAO,WACjB,OAAO,eAAeA,CAAK,IAAM,KAI9B,OAAO,UAAU,SAAS,KAAKD,CAAK,IAAM,kBAFtC,EAGf,CCZA,SAASE,EAAiBC,EAAK,CAC3B,OAAOA,IAAQ,WACnB,CCCA,SAASC,EAAMC,EAAQC,EAAQ,CAC3B,MAAMC,EAAa,OAAO,KAAKD,CAAM,EACrC,QAASE,EAAI,EAAGA,EAAID,EAAW,OAAQC,IAAK,CACxC,MAAML,EAAMI,EAAWC,CAAC,EACxB,GAAIN,EAAiBC,CAAG,EACpB,SAEJ,MAAMM,EAAcH,EAAOH,CAAG,EACxBO,EAAcL,EAAOF,CAAG,EAC1BQ,EAAiBF,CAAW,GAAKE,EAAiBD,CAAW,EAC7DL,EAAOF,CAAG,EAAIC,EAAMM,EAAaD,CAAW,EAEvC,MAAM,QAAQA,CAAW,EAC9BJ,EAAOF,CAAG,EAAIC,EAAM,CAAA,EAAIK,CAAW,EAE9BV,EAAcU,CAAW,EAC9BJ,EAAOF,CAAG,EAAIC,EAAM,CAAA,EAAIK,CAAW,GAE9BC,IAAgB,QAAaD,IAAgB,UAClDJ,EAAOF,CAAG,EAAIM,EAEtB,CACA,OAAOJ,CACX,CACA,SAASM,EAAiBX,EAAO,CAC7B,OAAOD,EAAcC,CAAK,GAAK,MAAM,QAAQA,CAAK,CACtD,CC7BA,MAAMY,EAA0D,CAAA,EAEhE,eAA8BC,EAC5BC,EACAC,EACmB,CACnB,MAAMZ,EAAM,GAAGW,CAAQ,IAAI,KAAK,UAAUC,CAAO,CAAC,GAElD,OAAMZ,KAAOS,IACXA,EAAUT,CAAG,EAAI,MAAMW,EAAUC,CAAO,EAAE,KAAMC,IAC9CJ,EAAUT,CAAG,EAAIa,EAEVA,EACR,GAGIJ,EAAUT,CAAG,CACtB,CCjBA,SAAwBc,EAAWH,EAA2B,CAC5D,MAAO,EAAQ,IAAI,MAAMA,CAAQ,CACnC,CCFA,MAEMI,EAAmC,0BAEnCC,EAAY,OAAO,OAAO,CAC9B,eAAA,IACA,yBAAAD,CACF,CAAC,ECLD,SAAwBE,EACtBC,EACAC,EACS,CACT,MAAMC,EAAeD,EAAM,MAAMH,EAAU,cAAc,EAEnD,CAAA,CAAGK,EAAMC,EAAOC,CAAK,EACzBP,EAAU,yBAAyB,KAAKI,EAAa,MAAA,CAAQ,GAAK,CAAA,EAE9DI,EAAYJ,EAAa,KAAKJ,EAAU,cAAc,EAE5D,GAAI,CAACK,EACH,OAAOH,EAGT,GAAI,EAAEG,KAAQH,GAAM,CAClB,GAAIK,EACF,OAAOD,EAAQ,CAAA,EAAK,KAGtB,MAAM,IAAI,MAAM,0CAA0CD,CAAI,IAAI,CACpE,CAEA,MAAMI,EAAWP,EAAIG,CAAI,EAEzB,OAAOC,EACFG,EAAuB,IAAKC,GAC3BT,EAAUS,EAAoCF,CAAS,CACzD,EACAP,EAAUQ,EAAqCD,CAAS,CAC9D,CC9BA,SAAwBG,EACtBC,EACAT,EACyB,CACzB,MAAMC,EAAeD,EAAM,MAAMH,EAAU,cAAc,EAEnD,CAAA,CAAGK,EAAMC,CAAK,EAClBN,EAAU,yBAAyB,KAAKI,EAAa,OAAQ,GAAK,GAE9DI,EAAYJ,EAAa,KAAKJ,EAAU,cAAc,EAE5D,OAAKK,EAIE,CACL,CAACA,CAAI,EAAGC,EACHM,EAAmB,IAAKC,GAAaF,EAAUE,EAAUL,CAAS,CAAC,EACpEG,EAAUC,EAAMJ,CAAS,CAC/B,EAPSI,CAQX,CCdA,eAA8BE,EAC5BnB,EACAoB,EACAnB,EACyC,CACzC,GAAI,CAACD,EACH,OAAO,KAGT,GAAI,CAACG,EAAWH,CAAQ,EACtB,MAAM,IAAI,MAAM,4CAA4CA,CAAQ,IAAI,EAG1E,MAAME,GAAY,MAAMH,EAAcC,EAAUC,CAAO,GAAG,MAAA,EAE1D,GAAI,CAACC,EAAS,GACZ,MAAM,IAAI,MAAM,4CAA4CF,CAAQ,IAAI,EAG1E,MAAMO,EAAM,MAAML,EAAS,KAAA,EAE3B,GAAIkB,IAAa,KACf,OAAOb,EAGT,MAAMc,EAAe,OAAO,KAAKD,CAAQ,EAAE,IAAKZ,IAAW,CACzD,CAACA,CAAK,EAAGF,EAAUC,EAAKC,CAAK,CAC/B,EAAE,EAEIc,EAAe,OAAO,QAC1BD,EAAa,OAAO,CAACE,EAAQC,KAAS,CAAE,GAAGD,EAAQ,GAAGC,CAAI,GAAI,CAAA,CAAE,CAClE,EAkBA,OAhBkB,MAAM,QAAQ,IAC9BF,EAAa,IAAI,MAAO,CAACd,EAAOiB,CAAS,IAAM,CAC7C,MAAMC,EAAeN,EAASZ,CAAK,EAE7BS,EAAO,MAAM,QAAQQ,CAAS,EAChC,MAAM,QAAQ,IACZA,EAAU,IAAI,MAAOE,GACnBR,EAAQQ,EAAcD,EAAczB,CAAO,CAC7C,CACF,EACA,MAAMkB,EAAQM,EAAqBC,EAAczB,CAAO,EAE5D,MAAO,CAACO,EAAOS,CAAI,CACrB,CAAC,CACH,GAEiB,OACf,CAACM,EAAQ,CAACf,EAAOS,CAAI,IAAM3B,EAAMiC,EAAQP,EAAUC,EAAMT,CAAe,CAAC,EACzED,CACF,CACF,CC7DA,SAAwBqB,EAASrB,EAAuB,CACtD,OAAOA,IAAQ,MAAQ,OAAOA,GAAQ,UAAY,CAAC,MAAM,QAAQA,CAAG,CACtE,CCCA,SAAwBsB,EAAWT,EAAoC,CACrE,GAAIA,IAAa,KACf,MAAO,GAGT,MAAMU,EAAO,OAAO,KAAKV,CAAQ,EAEjC,OAAKU,EAAK,OAIHA,EACJ,IACEzC,GACC,CAACA,EAAI,WAAWgB,EAAU,cAAc,GACxC,CAAChB,EAAI,SAASgB,EAAU,cAAc,GACtCwB,EAAWT,EAAS/B,CAAG,CAAC,CAC5B,EACC,OAAO,CAACkC,EAAQC,IAAQD,GAAUC,EAAK,EAAI,EAVrC,EAWX,CCjBA,eAA8BO,EAE5B/B,EAAkBoB,EAAoBnB,EAAuB,CAAA,EAAgB,CAC7E,GAAI,CAAC2B,EAASR,CAAQ,GAAK,CAACS,EAAWT,CAAQ,EAC7C,MAAM,IAAI,MACR,4CAA4C,KAAK,UAAUA,CAAQ,CAAC,IACtE,EAGF,GAAI,CAACQ,EAAS3B,CAAO,EACnB,MAAM,IAAI,MACR,2CAA2C,KAAK,UAAUA,CAAO,CAAC,IACpE,EAGF,OAAOkB,EAAQnB,EAAUoB,EAAUnB,CAAO,CAC5C","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.8",
3
+ "version": "1.2.1",
4
4
  "description": "RESTful API Resolver for Nested-Linked Resources | 🕸 🕷",
5
5
  "type": "module",
6
- "main": "./dist/cjs/index.js",
7
- "module": "./dist/esm/index.js",
8
- "unpkg": "./dist/umd/index.js",
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 clear && npm run lint && npm run test && npm run build",
14
- "clear": "rm -fr ./coverage/ ./dist/ ./stats.html",
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 --config ./.jest.config.json",
26
+ "test": "jest ./src/",
18
27
  "test:coverage": "npm run test -- --coverage",
19
28
  "test:watch": "npm run test -- --watch",
20
- "playground": "BABEL_ENV=development babel-node --config-file ./.babelrc ./playground/index.js",
21
- "build": "npm run build:cjs && npm run build:esm && npm run build:umd && npm run build:umd:min",
22
- "build:cjs": "BABEL_ENV=production MODULE_FMT=cjs rollup --config",
23
- "build:esm": "BABEL_ENV=production MODULE_FMT=esm rollup --config",
24
- "build:umd": "BABEL_ENV=production MODULE_FMT=umd rollup --config",
25
- "build:umd:min": "BABEL_ENV=production MODULE_FMT=umd MINIFY=true rollup --config"
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
- "javascript",
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
- "eslint": "^10.0.2",
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-visualizer": "^7.0.0"
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 };
@@ -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)}});