Skip to content

Commit

Permalink
feat: endpoint response types, GetResponseTypeFromEndpointMethod an…
Browse files Browse the repository at this point in the history
…d `GetResponseDataTypeFromEndpointMethod` helpers (#34)

Co-authored-by: Martin Kemp <me+github@martinke.mp>
  • Loading branch information
gr2m and MartiUK authored Apr 8, 2020
1 parent 9d42028 commit cc94eab
Show file tree
Hide file tree
Showing 11 changed files with 32,390 additions and 3,444 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@

## Usage

Get response types from endpoint methods

```ts
import {
GetResponseTypeFromEndpointMethod,
GetResponseDataTypeFromEndpointMethod,
} from "@octokit/types";
import { Octokit } from "@octokit/rest";

const octokit = new Octokit();
type CreateLabelResponseType = GetResponseType<
typeof octokit.issues.createLabel
>;
type CreateLabelResponseDataType = GetResponseDataType<
typeof octokit.issues.createLabel
>;
```

See https://octokit.github.io/types.ts for all exported types

## Contributing
Expand Down
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"author": "Gregor Martynus (https://twitter.com/gr2m)",
"license": "MIT",
"devDependencies": {
"@gimenete/type-writer": "^0.1.5",
"@octokit/graphql": "^4.2.2",
"handlebars": "^4.7.6",
"lodash.set": "^4.3.2",
Expand Down
10 changes: 10 additions & 0 deletions scripts/update-endpoints/fetch-json.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ const QUERY = `
type
required
}
responses {
code
description
examples {
data
}
}
renamed {
note
}
}
}`;

Expand Down
8,133 changes: 7,453 additions & 680 deletions scripts/update-endpoints/generated/endpoints.json

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions scripts/update-endpoints/templates/endpoints.ts.template
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@ import { RequestRequestOptions } from "../RequestRequestOptions";

export interface Endpoints {
{{#each endpointsByRoute}}
"{{@key}}": [{{union this "optionsTypeName"}}, {{union this "requestOptionsTypeName"}}]
"{{@key}}": [{{union this "optionsTypeName"}}, {{union this "requestOptionsTypeName"}}, {{union this "responseTypeName"}}]
{{/each}}
}

{{#each options}}
type {{in.name}} = {
{{#each in.parameters}}
type {{parameters.name}} = {
{{#each parameters.parameters}}
{{&jsdoc}}
{{{name this}}}: {{{type this}}}
{{/each}}
}
type {{out.name}} = {
method: "{{out.method}}",
url: "{{out.url}}",
type {{request.name}} = {
method: "{{request.method}}",
url: "{{request.url}}",
headers: RequestHeaders,
request: RequestRequestOptions
}
{{&response}}
{{/each}}

{{#childParams}}
Expand Down
59 changes: 49 additions & 10 deletions scripts/update-endpoints/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { pascalCase } = require("pascal-case");
const prettier = require("prettier");
const { stringToJsdocComment } = require("string-to-jsdoc-comment");
const sortKeys = require("sort-keys");
const TypeWriter = require("@gimenete/type-writer");

const ENDPOINTS = require("./generated/endpoints.json");
const ENDPOINTS_PATH = resolve(
Expand Down Expand Up @@ -61,6 +62,8 @@ const typeMap = {
};

for (const endpoint of ENDPOINTS) {
if (endpoint.renamed) continue;

const route = `${endpoint.method} ${endpoint.url.replace(
/\{([^}]+)}/g,
":$1"
Expand All @@ -71,27 +74,47 @@ for (const endpoint of ENDPOINTS) {
}

endpointsByRoute[route].push({
optionsTypeName:
pascalCase(`${endpoint.scope} ${endpoint.id}`) + "Endpoint",
requestOptionsTypeName:
pascalCase(`${endpoint.scope} ${endpoint.id}`) + "RequestOptions",
optionsTypeName: pascalCase(`${endpoint.scope} ${endpoint.id} Endpoint`),
requestOptionsTypeName: pascalCase(
`${endpoint.scope} ${endpoint.id} RequestOptions`
),
responseTypeName: endpointToResponseTypeName(endpoint),
});
}

const options = [];
const childParams = {};

for (const endpoint of ENDPOINTS) {
if (endpoint.renamed) continue;

const typeWriter = new TypeWriter();
const { method, parameters } = endpoint;
const url = endpoint.url.replace(/\{([^}]+)}/g, ":$1");

const optionsTypeName =
pascalCase(`${endpoint.scope} ${endpoint.id}`) + "Endpoint";
const requestOptionsTypeName =
pascalCase(`${endpoint.scope} ${endpoint.id}`) + "RequestOptions";
const optionsTypeName = pascalCase(
`${endpoint.scope} ${endpoint.id} Endpoint`
);
const requestOptionsTypeName = pascalCase(
`${endpoint.scope} ${endpoint.id} RequestOptions`
);

const responses =
endpoint.responses.length && endpoint.responses[0].examples
? endpoint.responses[0].examples.map((example) =>
JSON.parse(example.data)
)
: undefined;
if (responses) {
typeWriter.add(responses, {
rootTypeName: endpointToResponseTypeName(endpoint),
});

typeWriter.generate("typescript");
}

options.push({
in: {
parameters: {
name: optionsTypeName,
parameters: parameters
.map(parameterize)
Expand Down Expand Up @@ -133,14 +156,19 @@ for (const endpoint of ENDPOINTS) {
})
.filter(Boolean),
},
out: {
request: {
name: requestOptionsTypeName,
method,
url,
},
response: responses ? typeWriter.generate("typescript") : "",
});

process.stdout.write(".");
}

console.log("\ndone.");

const result = template({
endpointsByRoute: sortKeys(endpointsByRoute, { deep: true }),
options,
Expand Down Expand Up @@ -176,3 +204,14 @@ function parameterize(parameter) {
jsdoc: stringToJsdocComment(parameter.description),
};
}

function endpointToResponseTypeName(endpoint) {
const hasResponses =
endpoint.responses.length && endpoint.responses[0].examples;

if (hasResponses) {
return pascalCase(`${endpoint.scope} ${endpoint.id} ResponseData`);
}

return "any";
}
9 changes: 9 additions & 0 deletions src/GetResponseTypeFromEndpointMethod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type AnyFunction = (...args) => any;

export type GetResponseTypeFromEndpointMethod<T extends AnyFunction> = Unwrap<
ReturnType<T>
>;
export type GetResponseDataTypeFromEndpointMethod<
T extends AnyFunction
> = Unwrap<ReturnType<T>>["data"];
13 changes: 10 additions & 3 deletions src/RequestInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { OctokitResponse } from "./OctokitResponse";
import { RequestParameters } from "./RequestParameters";
import { Route } from "./Route";

import { Endpoints } from "./generated/Endpoints";

export interface RequestInterface {
/**
* Sends a request based on endpoint options
Expand All @@ -18,9 +20,14 @@ export interface RequestInterface {
* @param {string} route Request method + URL. Example: `'GET /orgs/:org'`
* @param {object} [parameters] URL, query or body parameters, as well as `headers`, `mediaType.{format|previews}`, `request`, or `baseUrl`.
*/
<T = any>(route: Route, parameters?: RequestParameters): Promise<
OctokitResponse<T>
>;
<R extends Route>(
route: keyof Endpoints | R,
options?: R extends keyof Endpoints
? Endpoints[R][0] & RequestParameters
: RequestParameters
): R extends keyof Endpoints
? Promise<OctokitResponse<Endpoints[R][2]>>
: Promise<OctokitResponse<any>>;

/**
* Returns a new `endpoint` with updated route and parameters
Expand Down
Loading

0 comments on commit cc94eab

Please # to comment.