abaca-cli
v0.18.0
Published
OpenAPI SDK generator with strong type guarantees and minimal boilerplate
Downloads
215
Maintainers
Readme
Abaca
An OpenAPI SDK generator with very strong type guarantees and minimal boilerplate.
Quickstart
- Install this package as development dependency:
npm i -D abaca-cli
- Use its
generate
command (org
for short) to create a TypeScript SDK from an OpenAPI specification file, typically from annpm
script:
abaca g resources/openapi.yaml -o src/sdk.gen.ts
- Import the generated SDK from your code and instantiate it:
import {createSdk} from './sdk.gen.js';
const sdk = createSdk(/* Optional configuration */);
- That's it! The instantiated SDK exposes a strongly typed method for each operation in OpenAPI specification which you can use to make requests.
const res = await sdk.someOperation(/* Request body, parameters, ... */);
switch (res.code) {
case 200:
doSomething(res.body); // Narrowed response body
break;
// ...
}
Take a look at the repository's README for more information, examples, and extensions (e.g. Koa integrations).
Type safety
Abaca checks request types and narrows response types extensively. This section describes the major components and highlights common patterns.
Overview
const res = await sdk.doSomething({
headers: {
'content-type': 'application/json', // 1
accept: 'application/json', // 2
},
params: {/* ... */}, // 3
body: {/* ... */}, // 4
options: {/* ... */}, // 5
});
if (res.code === 200) { // 6
return res.body; // 7
}
- The
content-type
header (or, if omitted, the SDK's default) must match one of the operation's request body's mime types. The type of the request's body automatically reflects this value. - The
accept
header (or, if omitted, the SDK's default) must match one of the operation's response mime types. The type of the response automatically reflects this value. - Each of the
parameters
(query, path, and headers) must have the expected type and be present if required. - The request's
body
can only be specified if the operation expects one and must be present if required. Its type must be valid for the operation and chosencontent-type
. - Request options are type checked against the SDK's local
fetch
implementation. - Expected response codes are statically determined from the spec, supporting
both status numbers and ranges (
2XX
, ...). - The response's type is automatically narrowed to both the
accept
header and response code.
Request body type inference
Abaca automatically type checks each request's body against its 'content-type'
header. In the common case where the header is omitted, the SDK's default is
used (application/json
, unless overridden). For example, using the
uploadTable
operation defined here, its body should by default
contain a Table
:
await sdk.uploadTable({
headers: {'content-type': 'application/json'}, // Can be omitted
params: {id: 'my-id'},
body: {/* ... */}, // Expected type: `Table`
});
Switching to CSV will automatically change the body's expected type:
await sdk.uploadTable({
headers: {'content-type': 'text/csv'}, // Different content-type
params: {id: 'my-id'},
body: '...', // Expected type: `string`
});
Additionally the 'content-type'
header is statically checked to match one of
the defined body types. It also can be auto-completed in compatible editors:
await sdk.uploadTable({
headers: {'content-type': 'application/xml'}, // Compile time error
params: {id: 'my-id'},
});
Response type inference
Abaca automatically narrows the types of responses according to the response's
code and request's 'accept'
header. When the header is omitted, it uses the
SDK's default (similar to request typing above, defaulting to
application/json;q=1, text/*;q=0.5
). For example, using the downloadTable
operation defined here:
const res = await sdk.downloadTable({params: {id: 'my-id'}});
switch (res.code) {
case 200:
res.body; // Narrowed type: `Table`
break;
case 404:
res.body; // Narrowed type: `undefined`
break;
}
Setting the accept header to CSV updates the response's type accordingly:
const res = await sdk.downloadTable({
headers: {accept: 'text/csv'},
params: {id: 'my-id'},
});
switch (res.code) {
case 200:
res.body; // Narrowed type: `string`
break;
case 404:
res.body; // Narrowed type: `undefined`
break;
}
Wildcards are also supported. In this case the returned type will be the union of all possible response values:
const res = await sdk.downloadTable({
params: {id: 'my-id'},
headers: {accept: '*/*'},
});
if (res.code === 200) {
res.body; // Narrowed type: `Table | string`
}
Finally, the accept
header itself is type-checked (and auto-completable):
const res = await sdk.downloadTable({
params: {id: 'my-id'},
headers: {
// Valid examples:
accept: 'application/json',
accept: 'application/*',
accept: 'text/csv',
accept: 'text/*',
accept: '*/*',
// Invalid examples:
accept: 'application/xml',
accept: 'text/plain',
accept: 'image/*',
},
});
Custom fetch
implementation
The fetch
SDK creation option allows swapping the underlying fetch
implementation. SDK method typings will automatically be updated to accept any
additional arguments it supports. For example to use
node-fetch
:
import fetch from 'node-fetch'
const nodeFetchSdk = createSdk({fetch, /** Other options... */});
await nodeFetchSdk.uploadTable({
options: {
compress: true, // OK: `node-fetch` argument
},
// ...
});
const fetchSdk = createSdk();
await fetchSdk.uploadTable({
options: {
compress: true, // Type error: default `fetch` does not support `compress`
},
})
const sdk = createSdk();
const res = await sdk.runSomeOperation({
params: {/* ... */}, // Checked
body: {/* ... */}, // Checked
headers: {
accept: 'application/json', // Checked (and optional)
'content-type': 'application/json', // Checked (and optional)
},
});
switch (res.code) {
case 200:
res.body; // Narrowed (based on code and `accept` header)
// ...
}