api 4.4.0 → 4.5.2
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 +15 -1
- package/package.json +5 -4
- package/src/cache.js +35 -19
- package/src/index.js +45 -21
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ npm install api --save
|
|
|
23
23
|
Using `api` is as simple as supplying it an OpenAPI and using the SDK as you would any other!
|
|
24
24
|
|
|
25
25
|
```js
|
|
26
|
-
const sdk = require('api')('https://raw.githubusercontent.com/readmeio/oas/
|
|
26
|
+
const sdk = require('api')('https://raw.githubusercontent.com/readmeio/oas-examples/main/3.0/json/petstore.json');
|
|
27
27
|
|
|
28
28
|
sdk.listPets().then(res => {
|
|
29
29
|
console.log(`My pets name is ${res[0].name}!`);
|
|
@@ -164,3 +164,17 @@ By default we parse the response based on the `content-type` header for you. You
|
|
|
164
164
|
```js
|
|
165
165
|
sdk.config({ parseResponse: false });
|
|
166
166
|
```
|
|
167
|
+
|
|
168
|
+
#### Where is the cache stored?
|
|
169
|
+
|
|
170
|
+
By default the cache is configured with the [find-cache-dir](https://npm.im/find-cache-dir) library so the cache will be in `node_modules/.cache/api`. If placing this cache within the `node_modules/` directory is a problem for your environment (maybe you use `npm prune`) you can configure this by supplying an additional argument to the SDK instantiator:
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
const sdk = require('api')('https://raw.githubusercontent.com/readmeio/oas-examples/main/3.0/json/petstore.json', {
|
|
174
|
+
cacheDir: './path/to/my/custom/cache/dir',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
sdk.listPets().then(res => {
|
|
178
|
+
console.log(`My pets name is ${res[0].name}!`);
|
|
179
|
+
});
|
|
180
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.2",
|
|
4
4
|
"description": "Generate an SDK from an OpenAPI definition",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"make-dir": "^3.1.0",
|
|
35
35
|
"mimer": "^2.0.2",
|
|
36
36
|
"node-fetch": "^2.6.0",
|
|
37
|
-
"oas": "^18.
|
|
37
|
+
"oas": "^18.3.4"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@readme/eslint-config": "^8.0.2",
|
|
@@ -48,8 +48,9 @@
|
|
|
48
48
|
"prettier": "@readme/eslint-config/prettier",
|
|
49
49
|
"jest": {
|
|
50
50
|
"testPathIgnorePatterns": [
|
|
51
|
-
"__tests__/__fixtures__/"
|
|
51
|
+
"__tests__/__fixtures__/",
|
|
52
|
+
".api-test/"
|
|
52
53
|
]
|
|
53
54
|
},
|
|
54
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "a4206f70f041c16dd748735e7ad2a3f808d3cd67"
|
|
55
56
|
}
|
package/src/cache.js
CHANGED
|
@@ -9,18 +9,8 @@ const os = require('os');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const makeDir = require('make-dir');
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// The `find-cache-dir` module returns `undefined` if the `node_modules/` directory isn't writable, or there's no
|
|
15
|
-
// `package.json` in the root-most directory. If this happens, we can instead adhoc create a cache directory in the
|
|
16
|
-
// users OS temp directory and store our data there.
|
|
17
|
-
//
|
|
18
|
-
// @link https://github.com/avajs/find-cache-dir/issues/29
|
|
19
|
-
cacheDir = makeDir.sync(path.join(os.tmpdir(), pkg.name));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
class SdkCache {
|
|
23
|
-
constructor(uri) {
|
|
12
|
+
class Cache {
|
|
13
|
+
constructor(uri, cacheDir = false) {
|
|
24
14
|
/**
|
|
25
15
|
* Resolve OpenAPI definition shorthand accessors from within the ReadMe API Registry.
|
|
26
16
|
*
|
|
@@ -37,8 +27,23 @@ class SdkCache {
|
|
|
37
27
|
: u;
|
|
38
28
|
|
|
39
29
|
this.uri = resolveReadMeRegistryAccessor(uri);
|
|
40
|
-
this.uriHash =
|
|
41
|
-
|
|
30
|
+
this.uriHash = Cache.getCacheHash(this.uri);
|
|
31
|
+
|
|
32
|
+
if (cacheDir) {
|
|
33
|
+
this.dir = cacheDir;
|
|
34
|
+
} else {
|
|
35
|
+
this.dir = findCacheDir({ name: pkg.name });
|
|
36
|
+
if (typeof this.dir === 'undefined') {
|
|
37
|
+
// The `find-cache-dir` module returns `undefined` if the `node_modules/` directory isn't
|
|
38
|
+
// writable, or there's no `package.json` in the root-most directory. If this happens, we
|
|
39
|
+
// can instead adhoc create a cache directory in the users OS temp directory and store our
|
|
40
|
+
// data there.
|
|
41
|
+
//
|
|
42
|
+
// @link https://github.com/avajs/find-cache-dir/issues/29
|
|
43
|
+
this.dir = makeDir.sync(path.join(os.tmpdir(), pkg.name));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
this.cacheStore = path.join(this.dir, 'cache.json');
|
|
43
48
|
this.specsCache = path.join(this.dir, 'specs');
|
|
44
49
|
|
|
@@ -87,7 +92,18 @@ class SdkCache {
|
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
const cache = this.getCache();
|
|
90
|
-
|
|
95
|
+
|
|
96
|
+
// Prior to v4.5.0 we were putting a fully resolved path to the API definition in the cache
|
|
97
|
+
// store but if you had specified a custom caching directory and would generate the cache on
|
|
98
|
+
// your system, that filepath would obviously not be the same in other environments. For this
|
|
99
|
+
// reason the `path` was removed from the cache store in favor of storing the `hash` instead.
|
|
100
|
+
//
|
|
101
|
+
// If we still have `path` in the config cache for backwards compatibility we should use it.
|
|
102
|
+
if ('path' in cache[this.uriHash]) {
|
|
103
|
+
return JSON.parse(fs.readFileSync(cache[this.uriHash].path, 'utf8'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return JSON.parse(fs.readFileSync(path.join(this.specsCache, `${cache[this.uriHash].hash}.json`)));
|
|
91
107
|
}
|
|
92
108
|
|
|
93
109
|
async load() {
|
|
@@ -154,16 +170,16 @@ class SdkCache {
|
|
|
154
170
|
const cache = self.getCache();
|
|
155
171
|
if (!(self.uriHash in cache)) {
|
|
156
172
|
const saved = JSON.stringify(spec, null, 2);
|
|
157
|
-
const
|
|
173
|
+
const fileHash = crypto.createHash('md5').update(saved).digest('hex');
|
|
158
174
|
|
|
159
175
|
cache[self.uriHash] = {
|
|
160
|
-
|
|
176
|
+
hash: fileHash,
|
|
161
177
|
original: self.uri,
|
|
162
178
|
title: 'title' in spec.info ? spec.info.title : undefined,
|
|
163
179
|
version: 'version' in spec.info ? spec.info.version : undefined,
|
|
164
180
|
};
|
|
165
181
|
|
|
166
|
-
fs.writeFileSync(
|
|
182
|
+
fs.writeFileSync(path.join(self.specsCache, `${fileHash}.json`), saved);
|
|
167
183
|
fs.writeFileSync(self.cacheStore, JSON.stringify(cache, null, 2));
|
|
168
184
|
|
|
169
185
|
self.cache = cache;
|
|
@@ -206,4 +222,4 @@ class SdkCache {
|
|
|
206
222
|
}
|
|
207
223
|
}
|
|
208
224
|
|
|
209
|
-
module.exports =
|
|
225
|
+
module.exports = Cache;
|
package/src/index.js
CHANGED
|
@@ -13,24 +13,16 @@ global.Headers = fetch.Headers;
|
|
|
13
13
|
global.FormData = require('form-data');
|
|
14
14
|
|
|
15
15
|
class Sdk {
|
|
16
|
-
constructor(uri) {
|
|
16
|
+
constructor(uri, opts = {}) {
|
|
17
17
|
this.uri = uri;
|
|
18
18
|
this.userAgent = `${pkg.name} (node)/${pkg.version}`;
|
|
19
|
-
}
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
return Object.keys(spec.api.paths)
|
|
23
|
-
.map(path => {
|
|
24
|
-
return Object.keys(spec.api.paths[path]).map(method => {
|
|
25
|
-
return spec.operation(path, method);
|
|
26
|
-
});
|
|
27
|
-
})
|
|
28
|
-
.reduce((prev, next) => prev.concat(next), []);
|
|
20
|
+
this.cacheDir = opts.cacheDir ? opts.cacheDir : false;
|
|
29
21
|
}
|
|
30
22
|
|
|
31
23
|
load() {
|
|
32
24
|
let authKeys = [];
|
|
33
|
-
const cache = new Cache(this.uri);
|
|
25
|
+
const cache = new Cache(this.uri, this.cacheDir);
|
|
34
26
|
const self = this;
|
|
35
27
|
let config = { parseResponse: true };
|
|
36
28
|
let server = false;
|
|
@@ -68,30 +60,62 @@ class Sdk {
|
|
|
68
60
|
});
|
|
69
61
|
}
|
|
70
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Create dynamic accessors for every HTTP method that the OpenAPI specification supports.
|
|
65
|
+
*
|
|
66
|
+
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-7}
|
|
67
|
+
* @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-7}
|
|
68
|
+
*/
|
|
71
69
|
function loadMethods(spec) {
|
|
72
70
|
const supportedVerbs = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
|
|
73
71
|
|
|
74
72
|
return supportedVerbs
|
|
75
|
-
.map(
|
|
73
|
+
.map(httpVerb => {
|
|
76
74
|
return {
|
|
77
|
-
[
|
|
75
|
+
[httpVerb]: ((method, path, ...args) => {
|
|
78
76
|
const operation = spec.operation(path, method);
|
|
79
77
|
return fetchOperation(spec, operation, ...args);
|
|
80
|
-
}).bind(null,
|
|
78
|
+
}).bind(null, httpVerb),
|
|
81
79
|
};
|
|
82
80
|
})
|
|
83
81
|
.reduce((prev, next) => Object.assign(prev, next));
|
|
84
82
|
}
|
|
85
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Create dynamic accessors for every operation with a defined operation ID. If an operation
|
|
86
|
+
* does not have an operation ID it can be accessed by its `.method('/path')` accessor instead.
|
|
87
|
+
*
|
|
88
|
+
* @param spec
|
|
89
|
+
*/
|
|
86
90
|
function loadOperations(spec) {
|
|
87
|
-
return
|
|
88
|
-
.
|
|
91
|
+
return Object.entries(spec.getPaths())
|
|
92
|
+
.map(([, operations]) => Object.values(operations))
|
|
93
|
+
.reduce((prev, next) => prev.concat(next), [])
|
|
94
|
+
.filter(operation => operation.hasOperationId())
|
|
89
95
|
.reduce((prev, next) => {
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
// `getOperationId()` creates dynamic operation IDs when one isn't available but we need
|
|
97
|
+
// to know here if we actually have one present or not. The `camelCase` option here also
|
|
98
|
+
// cleans up any `operationId` that we might have into something that can be used as a
|
|
99
|
+
// valid JS method.
|
|
100
|
+
const operationId = next.getOperationId({ camelCase: true });
|
|
101
|
+
const originalOperationId = next.getOperationId();
|
|
102
|
+
|
|
103
|
+
const op = {
|
|
104
|
+
[operationId]: ((operation, ...args) => {
|
|
92
105
|
return fetchOperation(spec, operation, ...args);
|
|
93
106
|
}).bind(null, next),
|
|
94
|
-
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (operationId !== originalOperationId) {
|
|
110
|
+
// If we cleaned up their operation ID into a friendly method accessor (`findPetById`
|
|
111
|
+
// versus `find pet by id`) we should still let them use the non-friendly version if
|
|
112
|
+
// they want.
|
|
113
|
+
op[originalOperationId] = ((operation, ...args) => {
|
|
114
|
+
return fetchOperation(spec, operation, ...args);
|
|
115
|
+
}).bind(null, next);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return Object.assign(prev, op);
|
|
95
119
|
}, {});
|
|
96
120
|
}
|
|
97
121
|
|
|
@@ -170,6 +194,6 @@ class Sdk {
|
|
|
170
194
|
}
|
|
171
195
|
}
|
|
172
196
|
|
|
173
|
-
module.exports = uri => {
|
|
174
|
-
return new Sdk(uri).load();
|
|
197
|
+
module.exports = (uri, opts = {}) => {
|
|
198
|
+
return new Sdk(uri, opts).load();
|
|
175
199
|
};
|