octokit.js: Upgrading to v17 breaks TypeScript types

Hello!

Here is the error:

'Octokit' refers to a value, but is being used as a type here.
import { Octokit } from "@octokit/rest";

class MyClass {             
  constructor(public logger: Logger, public octokit: Octokit) {}
                                                       ^--- error is here
}

I think the typings might just be pointing to the wrong thing somehow? I notice in package.json that types refers to index.d.ts but when importing the package it is grabbing the types from node_modules/@octokit/rest/dist-types/index.d.ts.

When I open that file, this is all it contains:

import { Octokit as Core } from "@octokit/core";
export declare const Octokit: (new (...args: any[]) => {
    [x: string]: any;
}) & {
    new (...args: any[]): {
        [x: string]: any;
    };
    plugins: any[];
} & typeof Core & import("@octokit/core/dist-types/types").Constructor<void> & import("@octokit/core/dist-types/types").Constructor<{
    paginate: import("@octokit/plugin-paginate-rest").PaginateInterface;
} & import("@octokit/plugin-rest-endpoint-methods/dist-types/generated/types").RestEndpointMethods>;

TypeScript: 3.7.5 Node: 12.15 Yarn: 1.22.0


This code worked prior to v17 and I don’t see anything in the changelog regarding changes to types.

Thanks!

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 17
  • Comments: 52 (21 by maintainers)

Commits related to this issue

Most upvoted comments

Although this works, the usage is very cryptic. The IDE will not help you. I am using the types I created, but they are not a replacement for real typings.

This is awesome in terms of “power of typescript” 🤩, Thank you for the hint! So i did this:

export type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

export type UnwrapArray<T> = T extends Array<infer U> ? U : T;
/**
 * Type of Octokit used instead of Octokit from core
 */
export type Github = InstanceType<typeof Octokit>;
/**
 * This is used to extrapolate Default function signature
 */
export type GithubExampleFunc = Github["teams"]["getByName"];
/**
 * Extrapolated OctokitResponse by remapping Data Payload Type
 * @template T Type of Response payload
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type GithubExampleResponse<T = any> = Omit<
  UnwrapPromise<ReturnType<GithubExampleFunc>>,
  "data"
> & { data: T };
/**
 * Describes default Octokit function signature by mapping
 * endpoint from Example Function
 */
export type GithubFunction = {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  (params?: any): Promise<{ data: any }>;
  endpoint: GithubExampleFunc["endpoint"];
};
/**
 * Extrapolates Octokit default function input extrapolated
 * from Function Signature. Used in Generics and to build
 * Input types for Endpoint Methods
 * @template F type of function to extrapolate params from
 */
export type GithubParams<F extends GithubFunction> = Required<Parameters<F>>[0];

/**
 * Extrapolates datapayload type from a given function return
 */
export type GithubData<F extends GithubFunction> = UnwrapPromise<
  ReturnType<F>
>["data"];

/**
 * Extrapolates a singe item type from a given function return
 */
export type GithubItem<F extends GithubFunction> = UnwrapArray<GithubData<F>>;

/**
 * Extrapolates Octokit default function result extrapolated
 * from Function Signature. Used in Generics and to build
 * Return types for Endpoint Methods
 * @template F type of function to extrapolate params from
 */
export type GithubPromise<F extends GithubFunction> = Promise<GithubData<F>>;

and then this:

  public paginate<F extends GithubFunction, P extends GithubParams<F>>(
    func: F,
    params: P
  ): GithubPromise<F> {
    return this.client.paginate(
      func.endpoint.merge(params) as Required<
        ReturnType<typeof func.endpoint.merge>
      >,
      GithubApi.paginationWithLimits
    );
  }

  public async listTeams(org: Org): GithubPromise<Github["teams"]["list"]> {
    const teams = await this.paginate(this.client.teams.list, {
      org: org.org,
      per_page: 100
    });
    return teams;
  }

But is hope we can use exported types soon 🙏 😀

What we are really missing is

import {​
ActionsCreateRegistrationTokenParam​,
ActionsCreateRegistrationTokenResponse
 } from "@octokit/whatever"

Maybe this is a naive question, but if the various methods can return these types, what makes it so difficult to export them? Why does a builtin solution require type-gymnastics to extract them?

The best way in my opinion, would be to re-export the types via @octokit/rest.

Otherwise people will need to install other packages explicitly.

no, every breaking change in @octokit/types would cause a breaking change in @octokit/rest

But this is the nature of a type’d language and is handled with proper versioning. I am trying to upgrade from @octokit/rest 16.36.0 -> 18.0.0 and it’s a total mess. I’ve got hundreds of TypeScript errors now about conflicts between types and namespaces, and I use the Octokit types (which seem to be buried in @octokit/types) like Octokit.PullsGetResponse and Octokit.ReposListCommitsResponseItem[] and countless others, and none of them seem to be properly exposed here.

Can you provide sample code for how consumers are expected to import the namespace and its types?

I need to create a list of ChecksCreateParamsOutputAnnotations that I’m going to eventually pass to checks.create(), but I can’t store them in the proper type. I don’t understand why types that are part of the API are not exported. Is there a workaround that I’m missing?

This helps a little bit to simplify extracting types (might be the wrapper that @jurijzahn8019 was talking about), but it makes it impossible to (e.g. in vscode) use F12 to get to the original definition of the type to look at its properties.

import {Octokit} from '@octokit/rest';
type OctokitInstanceType = InstanceType<typeof Octokit>;
type GetResponseTypeFromOctokitMethodPath<
  O extends OctokitInstanceType,
  P extends keyof O,
  M extends keyof O[P]
> = GetResponseDataTypeFromEndpointMethod<O[P][M]>;

Hmm I think the right place to import endpoint response types from would be https://github.com/octokit/types.ts. It would require some work though. They currently exist in https://github.com/octokit/plugin-rest-endpoint-methods.js/blob/80132aaaa03a61d4b7645cc25a860ad6b02fd427/src/generated/types.ts#L10-L20928 but are not being exported.

For the time being you should be able to infer the type like this:

type Unwrap<T> = T extends Promise<infer U> ? U : T;

type TeamsListResponseData = Unwrap<
  ReturnType<typeof this.client.teams.list>
>["data"];

Hi, also this does not work anymore:

  public async listTeams(org: Org): Promise<Octokit.TeamsListResponseItem[]> {
    const teams = (await this.client.paginate(
      this.client.teams.list.endpoint.merge({ org: org.org, per_page: 100 }),
      GithubApi.paginationWithLimits
    )) as Octokit.TeamsListResponseItem[];

    return teams;
  }

Neither Octokit.TeamsListResponseItem nor TeamsListResponseItem are exported anymore.

Thanks for digging into it! I might be able to spend some time next week looking into it more. I’ll report back with any findings

The might be some forensic information here for you too: https://github.com/googleapis/repo-automation-bots/pull/311

Do you use the types for anything other than pagination?

Instead of

const teams = (await octokit.paginate(
  octokit.teams.list.endpoint.merge({ org: org.org, per_page: 100 })
)) as Octokit.TeamsListResponseItem[];

I’ll probably implement something like this

const teams = await octokit.paginate(
  octokit.teams.list,
  { org: org.org, per_page: 100 }
)

With the types for teams being set correctly

I’ve an open pull request for @octokit/types that will add two helpers

  1. GetResponseType
  2. GetResponseDataType

https://github.com/octokit/types.ts/pull/32

These allow to extract the response type / response data type from the endpoint methods.

Example:

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

const octokit = new Octokit();
type CreateLabelResponseType = GetResponseTypeFromEndpointMethod<
  typeof octokit.issues.createLabel
>;
type CreateLabelResponseDataType = GetResponseDataTypeFromEndpointMethod<
  typeof octokit.issues.createLabel
>;

I will create a separate pull request which will export a type for each response type in a separate PR. I’m not yet sure if I’ll export the response type in @octokit/types or @octokit/plugin-rest-endpoint-methods, or both

Feedback welcome 😁

@jurijzahn8019 Thanks! It works. 🎉

Hi @prokopsimek,

this could work

import type {
  PullsListParams,
  IssuesListForRepoParams,
  IssuesListCommentsParams,
  PullsListCommitsParams,
} from "@octokit/plugin-rest-endpoint-methods/dist-types/generated/types";
import type { OctokitResponse } from "@octokit/types";

const owner = "foo";
const repo = "foo";
const issueNumber = 1;
const prNumber = 2;

const params1: PullsListParams = { owner, repo };
// and
const params2: IssuesListForRepoParams = { owner, repo };
// and
const params3: IssuesListCommentsParams = {
  owner,
  repo,
  issue_number: issueNumber,
};
// and
const params4: PullsListCommitsParams = {
  owner,
  repo,
  pull_number: prNumber,
};
// and

export class Foo {
  private unwrap<T>(
    clientPromise: Promise<OctokitResponse<T>>
  ): Promise<OctokitResponse<T>> {}
}

@bcoe this is unrleated to the original issue.

I will do two things that will help

  1. Add response types to https://github.com/octokit/types.ts/
  2. Add types to octokit.request, just like they already exist for octokit.endpoint.

By the way you can simplify your code

const pullsResponse = (await this.request(
  'GET /repos/:owner/:repo/pulls',
  {
    owner: this.owner,
    repo: this.repo,
    state: 'open'
    per_page: perPage,
    key: this.proxyKey
  }
)) as Octokit.Response<Octokit.PullsListResponseItem[]>;

What is the key parameter?

I sat down again to try to spend a bit of time upgrading libraries that use octokit/rest.js, and am a bit at a loss. Historically I’d been using this approach:

    const pullsResponse = (await this.request(
      `GET /repos/:owner/:repo/pulls?state=open&per_page=${perPage}${
        this.proxyKey ? `&key=${this.proxyKey}` : ''
      }`,
      {
        owner: this.owner,
        repo: this.repo,
      }
    )) as Octokit.Response<Octokit.PullsListResponseItem[]>;

To perform a request to an arbitrary GitHub endpoint, and provide a type hint about the response that I expect back.

With v17.x.x I can’t find similar type hints exported anywhere.

@disintegrator how about giving the exported type the same name (Octokit), so you don’t have to do a separate import for it:

import { Octokit as Core } from "@octokit/core";
import { requestLog } from "@octokit/plugin-request-log";
import { paginateRest } from "@octokit/plugin-paginate-rest";
import { restEndpointMethods } from "@octokit/plugin-rest-endpoint-methods";

import { VERSION } from "./version";

export const Octokit = Core.plugin([
  (requestLog as unknown) as () => void,
  restEndpointMethods,
  paginateRest
]).defaults({
  userAgent: `octokit-rest.js/${VERSION}`
});

+export type Octokit = InstanceType<typeof Octokit>;