api 5.0.0-beta.1 â 5.0.0-beta.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 +32 -161
- package/dist/cli/codegen/languages/typescript.js +15 -8
- package/dist/core/prepareParams.js +1 -1
- package/dist/fetcher.d.ts +1 -0
- package/dist/fetcher.js +20 -4
- package/dist/packageInfo.d.ts +1 -1
- package/dist/packageInfo.js +1 -1
- package/package.json +12 -12
- package/src/cli/codegen/languages/typescript.ts +17 -8
- package/src/core/prepareParams.ts +2 -2
- package/src/fetcher.ts +19 -4
- package/src/packageInfo.ts +1 -1
package/README.md
CHANGED
|
@@ -1,179 +1,50 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img width="400" src="https://raw.githubusercontent.com/readmeio/api/main/docs/images/logo.svg" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
Magical SDK generation from an OpenAPI definition đĒ
|
|
7
|
+
</p>
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://npm.im/api"><img src="https://img.shields.io/npm/v/api.svg?style=for-the-badge" alt="NPM Version"></a>
|
|
11
|
+
<a href="https://npm.im/api"><img src="https://img.shields.io/node/v/api.svg?style=for-the-badge" alt="Node Version"></a>
|
|
12
|
+
<a href="https://npm.im/api"><img src="https://img.shields.io/npm/l/api.svg?style=for-the-badge" alt="MIT License"></a>
|
|
13
|
+
<a href="https://github.com/readmeio/api"><img src="https://img.shields.io/github/workflow/status/readmeio/api/CI.svg?style=for-the-badge" alt="Build status"></a>
|
|
14
|
+
</p>
|
|
6
15
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* [FAQ](#faq)
|
|
16
|
+
- [Installation](https://api.readme.dev/docs/installation)
|
|
17
|
+
- [Usage](https://api.readme.dev/docs/usage)
|
|
18
|
+
- [Authentication](https://api.readme.dev/docs/authentication)
|
|
19
|
+
- [Parameters and Payloads](https://api.readme.dev/docs/parameters-and-payloads)
|
|
20
|
+
- [HTTP requests](https://api.readme.dev/docs/http-requests)
|
|
21
|
+
- [Server configurations](https://api.readme.dev/docs/server-configurations)
|
|
22
|
+
- [How does it work?](https://api.readme.dev/docs/how-it-works)
|
|
23
|
+
- [FAQ](https://api.readme.dev/docs/faq)
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
npm install api --save
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Usage
|
|
23
|
-
All you need to use `api` is to supply it an OpenAPI definition and then use the SDK as you would any other!
|
|
24
|
-
|
|
25
|
-
```js
|
|
26
|
-
const sdk = require('api')('https://raw.githubusercontent.com/readmeio/oas-examples/main/3.0/json/petstore.json');
|
|
27
|
-
|
|
28
|
-
sdk.listPets().then(res => {
|
|
29
|
-
console.log(`My pets name is ${res[0].name}!`);
|
|
30
|
-
});
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
The OpenAPI definition is automatically downloaded, cached, and transformed into a chainable [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) Promise that you can use to make API requests.
|
|
34
|
-
|
|
35
|
-
### Authentication
|
|
36
|
-
`api` supports API authentication through an `.auth()` method:
|
|
37
|
-
|
|
38
|
-
```js
|
|
39
|
-
sdk.auth('myApiToken');
|
|
40
|
-
sdk.listPets().then(...);
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
With the exception of OpenID, it supports all forms of authentication supported by the OpenAPI specification! Supply `.auth()` with your auth credentials and it'll magically figure out how to use it according to the API you're using. đ§ââī¸
|
|
44
|
-
|
|
45
|
-
For example:
|
|
46
|
-
|
|
47
|
-
* HTTP Basic auth: `sdk.auth('username', 'password')`
|
|
48
|
-
* Bearer tokens (HTTP or OAuth 2): `sdk.auth('myBearerToken')`
|
|
49
|
-
* API Keys: `sdk.auth('myApiKey')`
|
|
50
|
-
|
|
51
|
-
> âšī¸ Note that `sdk.auth()` is not chainable.
|
|
25
|
+
`api` is a library that facilitates creating an SDK from an OpenAPI definition. You can use its codegen offering to create an opinionated SDK for TypeScript or JS (+ TypeScript types).
|
|
52
26
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* `body`: This will contain all data required for a request body payload for a POST, PUT, etc. request. It can either be an array or an object â whichever you need to use the API operation you're using.
|
|
57
|
-
* `metadata`: This is an object where all parameters (path, query, header, cookie) go. Again, don't worry about telling the SDK that a path parameter is for the path, that's all handled for you.
|
|
58
|
-
|
|
59
|
-
For example, if you wanted to make a GET request:
|
|
60
|
-
|
|
61
|
-
```js
|
|
62
|
-
sdk.showPetById({ petId: 1234 }).then(...)
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Since `petId` matches up with the `petId` path parameter, the SDK here will issue a GET request against `/pets/1234`.
|
|
66
|
-
|
|
67
|
-
What about a POST request?
|
|
68
|
-
|
|
69
|
-
```js
|
|
70
|
-
sdk.createPets({ name: 'Buster' }).then(...)
|
|
27
|
+
```sh
|
|
28
|
+
$ npx api install https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.json
|
|
71
29
|
```
|
|
72
30
|
|
|
73
|
-
Since `name` here would correspond on `createPets` to request body payload, this will issue a POST request against `/pets` to make a new pet named "Buster".
|
|
74
|
-
|
|
75
|
-
What about operations that require both? Well you can mix them too!
|
|
76
|
-
|
|
77
31
|
```js
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Since we've supplied two objects here, the SDK automatically knows that you're supplying both a `body` and `metadata`, and can make a PUT request against `/pets/1234` for you.
|
|
82
|
-
|
|
83
|
-
What about a `multipart/form-data` request? That works too, and you don't even have to worry about the fun of multipart boundaries!
|
|
84
|
-
|
|
85
|
-
```js
|
|
86
|
-
sdk.uploadFile({ file: '/path/to/a/file.txt' }).then(...)
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
You can also give it a stream and it'll handle all of the hard work for you.
|
|
32
|
+
const SDK = require('@api/petstore');
|
|
90
33
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
### HTTP requests
|
|
96
|
-
If the API you're using doesn't have any documented operation IDs, you can make requests with HTTP verbs instead:
|
|
97
|
-
|
|
98
|
-
```js
|
|
99
|
-
sdk.get('/pets/{petId}', { petId: 1234 }).then(...)
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
The SDK supports GET, PUT, POST, DELETE, OPTIONS, HEAD, and TRACE requests.
|
|
103
|
-
|
|
104
|
-
### Server configurations
|
|
105
|
-
If the API you're using offers alternate server URLs and server variables in its [`servers`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#serverObject) definition you can supply this to the SDK with `.server()`:
|
|
106
|
-
|
|
107
|
-
```js
|
|
108
|
-
sdk.server('https://{region}.api.example.com/{basePath}', {
|
|
109
|
-
name: 'eu',
|
|
110
|
-
basePath: 'v14',
|
|
34
|
+
const petstore = new SDK();
|
|
35
|
+
petstore.listPets().then(res => {
|
|
36
|
+
console.log(`My pets name is ${res[0].name}!`);
|
|
111
37
|
});
|
|
112
|
-
|
|
113
|
-
sdk.get('/pets').then(...)
|
|
114
38
|
```
|
|
115
39
|
|
|
116
|
-
|
|
40
|
+
Or you can use it dynamically (though you won't have fancy TypeScript types):
|
|
117
41
|
|
|
118
42
|
```js
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
## How does it work?
|
|
123
|
-
Behind the scenes, `api` will:
|
|
124
|
-
|
|
125
|
-
1. Download the supplied OpenAPI definition, either from a publically accessible URLs or an absolute/relative path.
|
|
126
|
-
2. Dereference the definition so it's easier for us to handle.
|
|
127
|
-
3. Hash the definition and cache it into a directory in `node_modules/.cache/api/`.
|
|
128
|
-
4. Process the definition and instantiate chainable methods for HTTP verbs and operation IDs the API contains via a JS [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy).
|
|
129
|
-
|
|
130
|
-
On subsequent requests, `api` will look in its cache, and if the supplied definition exists there, it'll retrieve it from the cache instead of re-retrieving it again.
|
|
131
|
-
|
|
132
|
-
## Interested in contributing?
|
|
133
|
-
Welcome! Have a look at [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
134
|
-
|
|
135
|
-
## FAQ
|
|
136
|
-
#### Does this support YAML definitions?
|
|
137
|
-
Yes! YAML definitions will be automatically converted to JSON before they're cached and loaded as an SDK.
|
|
43
|
+
const petstore = require('api')(
|
|
44
|
+
'https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.json'
|
|
45
|
+
);
|
|
138
46
|
|
|
139
|
-
|
|
140
|
-
At the moment it does not. If you wish to use an API that has a Swagger 2.0 file, you'll need to first convert it to an OpenAPI 3 definition.
|
|
141
|
-
|
|
142
|
-
#### Does this support traditional OAuth 2 flows of creating tokens?
|
|
143
|
-
Not yet, unfortunately. For APIs that use OAuth 2, you'll need a fully-qualified token already for `api` to make requests.
|
|
144
|
-
|
|
145
|
-
#### Does this support APIs that use multiple forms of authentication on a single request?
|
|
146
|
-
Not yet! This is something we're thinking about how to handle, but it's difficult with the simplified nature of the `.auth()` method as it currently does not require the user to inform the SDK of what kind of authentication scheme the token they're supplying it should match up against.
|
|
147
|
-
|
|
148
|
-
#### Will this work in browsers?
|
|
149
|
-
Not at the moment as the library requires some filesystem handling in order to manage its cache state, but it's something we're actively thinking about. If you'd like to help us out in making this compatible with browsers we'd love to help you out on a pull request.
|
|
150
|
-
|
|
151
|
-
#### Will this validate my data before it reaches the API?
|
|
152
|
-
Not yet! This is something we've got planned down the road.
|
|
153
|
-
|
|
154
|
-
#### Does this support OpenAPI definitions that require authentication to download?
|
|
155
|
-
Not yet! The URL that you give the module must be publicy accessible. If it isn't, you can download it to your computer/server and then use the absolute path to that file instead.
|
|
156
|
-
|
|
157
|
-
```js
|
|
158
|
-
const sdk = require('api')('/path/to/downloaded.json');
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
#### How do I access the Response object (for status and headers)?
|
|
162
|
-
By default we parse the response based on the `content-type` header for you. You can disable this by doing the following:
|
|
163
|
-
|
|
164
|
-
```js
|
|
165
|
-
sdk.config({ parseResponse: false });
|
|
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
|
-
sdk.listPets().then(res => {
|
|
47
|
+
petstore.listPets().then(res => {
|
|
177
48
|
console.log(`My pets name is ${res[0].name}!`);
|
|
178
49
|
});
|
|
179
50
|
```
|
|
@@ -354,7 +354,7 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
354
354
|
var parameters = [{ name: 'path', type: 'string' }];
|
|
355
355
|
var docblock = {
|
|
356
356
|
description: function (writer) {
|
|
357
|
-
writer.writeLine("Access any ".concat(method, " endpoint on your API."));
|
|
357
|
+
writer.writeLine("Access any ".concat(method.toUpperCase(), " endpoint on your API."));
|
|
358
358
|
return writer;
|
|
359
359
|
},
|
|
360
360
|
tags: [{ tagName: 'param', text: 'path API path to make a request against.' }]
|
|
@@ -540,7 +540,7 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
540
540
|
this.methodGenerics.get(operation.method).addOverload({
|
|
541
541
|
typeParameters: typeParameters,
|
|
542
542
|
parameters: [
|
|
543
|
-
{ name: 'path', type: '
|
|
543
|
+
{ name: 'path', type: "'".concat(operation.path, "'") },
|
|
544
544
|
__assign(__assign({}, parameters.body), { hasQuestionToken: false }),
|
|
545
545
|
__assign(__assign({}, parameters.metadata), { hasQuestionToken: false }),
|
|
546
546
|
],
|
|
@@ -550,7 +550,7 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
550
550
|
// Create an overload that just has a single `metadata` parameter.
|
|
551
551
|
this.methodGenerics.get(operation.method).addOverload({
|
|
552
552
|
typeParameters: typeParameters,
|
|
553
|
-
parameters: [{ name: 'path', type: '
|
|
553
|
+
parameters: [{ name: 'path', type: "'".concat(operation.path, "'") }, parameters.metadata],
|
|
554
554
|
returnType: returnType,
|
|
555
555
|
docs: docblock ? [docblock] : null
|
|
556
556
|
});
|
|
@@ -558,7 +558,7 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
558
558
|
else {
|
|
559
559
|
this.methodGenerics.get(operation.method).addOverload({
|
|
560
560
|
typeParameters: responseTypes ? null : ['T = unknown'],
|
|
561
|
-
parameters: __spreadArray([{ name: 'path', type: '
|
|
561
|
+
parameters: __spreadArray([{ name: 'path', type: "'".concat(operation.path, "'") }], Object.values(parameters), true),
|
|
562
562
|
returnType: returnType,
|
|
563
563
|
docs: docblock ? [docblock] : null
|
|
564
564
|
});
|
|
@@ -629,11 +629,15 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
629
629
|
Object.entries(ops).forEach(function (_a) {
|
|
630
630
|
var method = _a[0], operation = _a[1];
|
|
631
631
|
methods.add(method);
|
|
632
|
-
var operationId = operation.getOperationId(
|
|
632
|
+
var operationId = operation.getOperationId({
|
|
633
|
+
// This `camelCase` option will clean up any weird characters that might be present in
|
|
634
|
+
// the `operationId` so as we don't break TS compilation with an invalid method accessor.
|
|
635
|
+
camelCase: true
|
|
636
|
+
});
|
|
633
637
|
var params = _this.prepareParameterTypesForOperation(operation, operationId);
|
|
634
638
|
var responses = _this.prepareResponseTypesForOperation(operation, operationId);
|
|
635
639
|
if (operation.hasOperationId()) {
|
|
636
|
-
operations[
|
|
640
|
+
operations[operationId] = {
|
|
637
641
|
types: {
|
|
638
642
|
params: params,
|
|
639
643
|
responses: responses
|
|
@@ -722,8 +726,11 @@ var TSGenerator = /** @class */ (function (_super) {
|
|
|
722
726
|
*/
|
|
723
727
|
TSGenerator.prototype.prepareResponseTypesForOperation = function (operation, operationId) {
|
|
724
728
|
var _this = this;
|
|
725
|
-
var
|
|
726
|
-
|
|
729
|
+
var responseStatusCodes = operation.getResponseStatusCodes();
|
|
730
|
+
if (!responseStatusCodes.length) {
|
|
731
|
+
return undefined;
|
|
732
|
+
}
|
|
733
|
+
var schemas = responseStatusCodes
|
|
727
734
|
.map(function (status) {
|
|
728
735
|
var _a;
|
|
729
736
|
var schema = operation.getResponseAsJsonSchema(status);
|
|
@@ -142,7 +142,7 @@ function processFile(paramName, file) {
|
|
|
142
142
|
}
|
|
143
143
|
return Promise.reject(new TypeError(paramName
|
|
144
144
|
? "The data supplied for the `".concat(paramName, "` request body parameter is not a file handler that we support.")
|
|
145
|
-
:
|
|
145
|
+
: 'The data supplied for the request body payload is not a file handler that we support.'));
|
|
146
146
|
}
|
|
147
147
|
/**
|
|
148
148
|
* With potentially supplied body and/or metadata we need to run through them against a given API
|
package/dist/fetcher.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export default class Fetcher {
|
|
|
9
9
|
static registryUUIDRegex: RegExp;
|
|
10
10
|
constructor(uri: string | OASDocument);
|
|
11
11
|
static isAPIRegistryUUID(uri: string): boolean;
|
|
12
|
+
static isGitHubBlobURL(uri: string): boolean;
|
|
12
13
|
static getProjectPrefixFromRegistryUUID(uri: string): string;
|
|
13
14
|
load(): Promise<(Omit<Omit<import("openapi-types").OpenAPIV3.Document<{}>, "paths" | "components">, "paths" | "components" | "info" | "servers" | "webhooks" | "jsonSchemaDialect"> & {
|
|
14
15
|
info: import("openapi-types").OpenAPIV3_1.InfoObject;
|
package/dist/fetcher.js
CHANGED
|
@@ -47,10 +47,23 @@ var path_1 = __importDefault(require("path"));
|
|
|
47
47
|
var Fetcher = /** @class */ (function () {
|
|
48
48
|
function Fetcher(uri) {
|
|
49
49
|
if (typeof uri === 'string') {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
if (Fetcher.isAPIRegistryUUID(uri)) {
|
|
51
|
+
// Resolve OpenAPI definition shorthand accessors from within the ReadMe API Registry.
|
|
52
|
+
this.uri = uri.replace(Fetcher.registryUUIDRegex, 'https://dash.readme.com/api/v1/api-registry/$4');
|
|
53
|
+
}
|
|
54
|
+
else if (Fetcher.isGitHubBlobURL(uri)) {
|
|
55
|
+
/**
|
|
56
|
+
* People may try to use a public repository URL to the source viewer on GitHub not knowing
|
|
57
|
+
* that this page actually serves HTML. In this case we want to rewrite these to the "raw"
|
|
58
|
+
* version of this page that'll allow us to access the API definition.
|
|
59
|
+
*
|
|
60
|
+
* @example https://github.com/readmeio/oas-examples/blob/main/3.1/json/petstore.json
|
|
61
|
+
*/
|
|
62
|
+
this.uri = uri.replace(/\/\/github.com/, '//raw.githubusercontent.com').replace(/\/blob\//, '/');
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
this.uri = uri;
|
|
66
|
+
}
|
|
54
67
|
}
|
|
55
68
|
else {
|
|
56
69
|
this.uri = uri;
|
|
@@ -59,6 +72,9 @@ var Fetcher = /** @class */ (function () {
|
|
|
59
72
|
Fetcher.isAPIRegistryUUID = function (uri) {
|
|
60
73
|
return Fetcher.registryUUIDRegex.test(uri);
|
|
61
74
|
};
|
|
75
|
+
Fetcher.isGitHubBlobURL = function (uri) {
|
|
76
|
+
return /\/\/github.com\/[-_a-zA-Z0-9]+\/[-_a-zA-Z0-9]+\/blob\/(.*).(yaml|json|yml)/.test(uri);
|
|
77
|
+
};
|
|
62
78
|
Fetcher.getProjectPrefixFromRegistryUUID = function (uri) {
|
|
63
79
|
var matches = uri.match(Fetcher.registryUUIDRegex);
|
|
64
80
|
if (!matches) {
|
package/dist/packageInfo.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const PACKAGE_NAME = "api";
|
|
2
|
-
export declare const PACKAGE_VERSION = "5.0.0-beta.
|
|
2
|
+
export declare const PACKAGE_VERSION = "5.0.0-beta.2";
|
package/dist/packageInfo.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "5.0.0-beta.2",
|
|
4
|
+
"description": "Magical SDK generation from an OpenAPI definition đĒ",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"bin": {
|
|
@@ -10,12 +10,9 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
12
12
|
"debug:bin": "node -r ts-node/register src/bin.ts",
|
|
13
|
-
"lint": "eslint . --ext .js,.ts",
|
|
14
13
|
"prebuild": "rm -rf dist/; npm run prebuild.packageConfig",
|
|
15
|
-
"prebuild.packageConfig": "node -p \"'// This file is automatically updated by the build script.\\nexport const PACKAGE_NAME = \\'' + require('./package.json').name + '\\';\\nexport const PACKAGE_VERSION = \\'' + require('./package.json').version + '\\';'\" > src/packageInfo.ts",
|
|
14
|
+
"prebuild.packageConfig": "node -p \"'// This file is automatically updated by the build script.\\nexport const PACKAGE_NAME = \\'' + require('./package.json').name + '\\';\\nexport const PACKAGE_VERSION = \\'' + require('./package.json').version + '\\';'\" > src/packageInfo.ts; git add src/packageInfo.ts",
|
|
16
15
|
"prepack": "npm run build",
|
|
17
|
-
"pretest": "npm run lint",
|
|
18
|
-
"prettier": "prettier --list-different --write \"./**/**.{js,ts}\"",
|
|
19
16
|
"test": "nyc mocha \"test/**/*.test.ts\""
|
|
20
17
|
},
|
|
21
18
|
"repository": {
|
|
@@ -23,7 +20,7 @@
|
|
|
23
20
|
"url": "https://github.com/readmeio/api.git",
|
|
24
21
|
"directory": "packages/api"
|
|
25
22
|
},
|
|
26
|
-
"homepage": "https://
|
|
23
|
+
"homepage": "https://api.readme.dev",
|
|
27
24
|
"bugs": {
|
|
28
25
|
"url": "https://github.com/readmeio/api/issues"
|
|
29
26
|
},
|
|
@@ -32,6 +29,12 @@
|
|
|
32
29
|
"engines": {
|
|
33
30
|
"node": ">=14"
|
|
34
31
|
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"api",
|
|
34
|
+
"openapi",
|
|
35
|
+
"sdk",
|
|
36
|
+
"swagger"
|
|
37
|
+
],
|
|
35
38
|
"dependencies": {
|
|
36
39
|
"@readme/oas-to-har": "^17.0.8",
|
|
37
40
|
"@readme/openapi-parser": "^2.2.0",
|
|
@@ -60,7 +63,6 @@
|
|
|
60
63
|
"validate-npm-package-name": "^4.0.0"
|
|
61
64
|
},
|
|
62
65
|
"devDependencies": {
|
|
63
|
-
"@readme/eslint-config": "^8.7.3",
|
|
64
66
|
"@readme/oas-examples": "^5.4.1",
|
|
65
67
|
"@types/chai": "^4.3.1",
|
|
66
68
|
"@types/find-cache-dir": "^3.2.1",
|
|
@@ -73,15 +75,13 @@
|
|
|
73
75
|
"@types/ssri": "^7.1.1",
|
|
74
76
|
"@types/validate-npm-package-name": "^4.0.0",
|
|
75
77
|
"chai": "^4.3.6",
|
|
76
|
-
"eslint": "^8.14.0",
|
|
77
78
|
"fetch-mock": "^9.11.0",
|
|
78
79
|
"mocha": "^10.0.0",
|
|
79
80
|
"mock-require": "^3.0.3",
|
|
80
81
|
"nyc": "^15.1.0",
|
|
81
|
-
"prettier": "^2.6.2",
|
|
82
82
|
"sinon": "^14.0.0",
|
|
83
83
|
"sinon-chai": "^3.7.0",
|
|
84
|
-
"typescript": "^4.
|
|
84
|
+
"typescript": "^4.7.4",
|
|
85
85
|
"unique-temp-dir": "^1.0.0"
|
|
86
86
|
},
|
|
87
87
|
"prettier": "@readme/eslint-config/prettier",
|
|
@@ -91,5 +91,5 @@
|
|
|
91
91
|
"test/"
|
|
92
92
|
]
|
|
93
93
|
},
|
|
94
|
-
"gitHead": "
|
|
94
|
+
"gitHead": "aa738b1bf46b447afed38507d3fc49acbbad6309"
|
|
95
95
|
}
|
|
@@ -373,7 +373,7 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
373
373
|
const parameters: OptionalKind<ParameterDeclarationStructure>[] = [{ name: 'path', type: 'string' }];
|
|
374
374
|
const docblock: OptionalKind<JSDocStructure> = {
|
|
375
375
|
description: writer => {
|
|
376
|
-
writer.writeLine(`Access any ${method} endpoint on your API.`);
|
|
376
|
+
writer.writeLine(`Access any ${method.toUpperCase()} endpoint on your API.`);
|
|
377
377
|
return writer;
|
|
378
378
|
},
|
|
379
379
|
tags: [{ tagName: 'param', text: 'path API path to make a request against.' }],
|
|
@@ -589,7 +589,7 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
589
589
|
this.methodGenerics.get(operation.method).addOverload({
|
|
590
590
|
typeParameters,
|
|
591
591
|
parameters: [
|
|
592
|
-
{ name: 'path', type: '
|
|
592
|
+
{ name: 'path', type: `'${operation.path}'` },
|
|
593
593
|
{ ...parameters.body, hasQuestionToken: false },
|
|
594
594
|
{ ...parameters.metadata, hasQuestionToken: false },
|
|
595
595
|
],
|
|
@@ -600,14 +600,14 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
600
600
|
// Create an overload that just has a single `metadata` parameter.
|
|
601
601
|
this.methodGenerics.get(operation.method).addOverload({
|
|
602
602
|
typeParameters,
|
|
603
|
-
parameters: [{ name: 'path', type: '
|
|
603
|
+
parameters: [{ name: 'path', type: `'${operation.path}'` }, parameters.metadata],
|
|
604
604
|
returnType,
|
|
605
605
|
docs: docblock ? [docblock] : null,
|
|
606
606
|
});
|
|
607
607
|
} else {
|
|
608
608
|
this.methodGenerics.get(operation.method).addOverload({
|
|
609
609
|
typeParameters: responseTypes ? null : ['T = unknown'],
|
|
610
|
-
parameters: [{ name: 'path', type: '
|
|
610
|
+
parameters: [{ name: 'path', type: `'${operation.path}'` }, ...Object.values(parameters)],
|
|
611
611
|
returnType,
|
|
612
612
|
docs: docblock ? [docblock] : null,
|
|
613
613
|
});
|
|
@@ -673,12 +673,17 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
673
673
|
Object.entries(ops).forEach(([method, operation]: [HttpMethods, Operation]) => {
|
|
674
674
|
methods.add(method);
|
|
675
675
|
|
|
676
|
-
const operationId = operation.getOperationId(
|
|
676
|
+
const operationId = operation.getOperationId({
|
|
677
|
+
// This `camelCase` option will clean up any weird characters that might be present in
|
|
678
|
+
// the `operationId` so as we don't break TS compilation with an invalid method accessor.
|
|
679
|
+
camelCase: true,
|
|
680
|
+
});
|
|
681
|
+
|
|
677
682
|
const params = this.prepareParameterTypesForOperation(operation, operationId);
|
|
678
683
|
const responses = this.prepareResponseTypesForOperation(operation, operationId);
|
|
679
684
|
|
|
680
685
|
if (operation.hasOperationId()) {
|
|
681
|
-
operations[
|
|
686
|
+
operations[operationId] = {
|
|
682
687
|
types: {
|
|
683
688
|
params,
|
|
684
689
|
responses,
|
|
@@ -758,8 +763,12 @@ sdk.server('https://eu.api.example.com/v14');`)
|
|
|
758
763
|
* @param operationId
|
|
759
764
|
*/
|
|
760
765
|
prepareResponseTypesForOperation(operation: Operation, operationId: string) {
|
|
761
|
-
const
|
|
762
|
-
|
|
766
|
+
const responseStatusCodes = operation.getResponseStatusCodes();
|
|
767
|
+
if (!responseStatusCodes.length) {
|
|
768
|
+
return undefined;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const schemas = responseStatusCodes
|
|
763
772
|
.map(status => {
|
|
764
773
|
const schema = operation.getResponseAsJsonSchema(status);
|
|
765
774
|
if (!schema) {
|
|
@@ -22,7 +22,7 @@ import getJSONSchemaDefaults from './getJSONSchemaDefaults';
|
|
|
22
22
|
function digestParameters(parameters: ParameterObject[]): Record<string, ParameterObject> {
|
|
23
23
|
return parameters.reduce((prev, param) => {
|
|
24
24
|
if ('$ref' in param || 'allOf' in param || 'anyOf' in param || 'oneOf' in param) {
|
|
25
|
-
throw new Error(
|
|
25
|
+
throw new Error("The OpenAPI document for this operation wasn't dereferenced before processing.");
|
|
26
26
|
} else if (param.name in prev) {
|
|
27
27
|
throw new Error(
|
|
28
28
|
`The operation you are using has the same parameter, ${param.name}, spread across multiple entry points. We unfortunately can't handle this right now.`
|
|
@@ -123,7 +123,7 @@ function processFile(
|
|
|
123
123
|
new TypeError(
|
|
124
124
|
paramName
|
|
125
125
|
? `The data supplied for the \`${paramName}\` request body parameter is not a file handler that we support.`
|
|
126
|
-
:
|
|
126
|
+
: 'The data supplied for the request body payload is not a file handler that we support.'
|
|
127
127
|
)
|
|
128
128
|
);
|
|
129
129
|
}
|
package/src/fetcher.ts
CHANGED
|
@@ -18,10 +18,21 @@ export default class Fetcher {
|
|
|
18
18
|
|
|
19
19
|
constructor(uri: string | OASDocument) {
|
|
20
20
|
if (typeof uri === 'string') {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
if (Fetcher.isAPIRegistryUUID(uri)) {
|
|
22
|
+
// Resolve OpenAPI definition shorthand accessors from within the ReadMe API Registry.
|
|
23
|
+
this.uri = uri.replace(Fetcher.registryUUIDRegex, 'https://dash.readme.com/api/v1/api-registry/$4');
|
|
24
|
+
} else if (Fetcher.isGitHubBlobURL(uri)) {
|
|
25
|
+
/**
|
|
26
|
+
* People may try to use a public repository URL to the source viewer on GitHub not knowing
|
|
27
|
+
* that this page actually serves HTML. In this case we want to rewrite these to the "raw"
|
|
28
|
+
* version of this page that'll allow us to access the API definition.
|
|
29
|
+
*
|
|
30
|
+
* @example https://github.com/readmeio/oas-examples/blob/main/3.1/json/petstore.json
|
|
31
|
+
*/
|
|
32
|
+
this.uri = uri.replace(/\/\/github.com/, '//raw.githubusercontent.com').replace(/\/blob\//, '/');
|
|
33
|
+
} else {
|
|
34
|
+
this.uri = uri;
|
|
35
|
+
}
|
|
25
36
|
} else {
|
|
26
37
|
this.uri = uri;
|
|
27
38
|
}
|
|
@@ -31,6 +42,10 @@ export default class Fetcher {
|
|
|
31
42
|
return Fetcher.registryUUIDRegex.test(uri);
|
|
32
43
|
}
|
|
33
44
|
|
|
45
|
+
static isGitHubBlobURL(uri: string) {
|
|
46
|
+
return /\/\/github.com\/[-_a-zA-Z0-9]+\/[-_a-zA-Z0-9]+\/blob\/(.*).(yaml|json|yml)/.test(uri);
|
|
47
|
+
}
|
|
48
|
+
|
|
34
49
|
static getProjectPrefixFromRegistryUUID(uri: string) {
|
|
35
50
|
const matches = uri.match(Fetcher.registryUUIDRegex);
|
|
36
51
|
if (!matches) {
|
package/src/packageInfo.ts
CHANGED