From 9ed3302836c192de0381db3f47b84943f5efc9b9 Mon Sep 17 00:00:00 2001 From: Nathaniel Tucker Date: Mon, 20 Mar 2023 00:24:30 -0500 Subject: [PATCH] internal: Test TypeScript 4.1, 4.3 --- .circleci/config.yml | 12 +- .github/workflows/benchmark.yml | 6 +- examples/todo-app/tsconfig.typetest.json | 7 + examples/todo-app/typetest.ts | 29 +++ packages/endpoint/.gitignore | 2 + packages/endpoint/package.json | 15 +- packages/endpoint/src-4.2-types/endpoint.d.ts | 217 ++++++++++++++++++ .../endpoint/src-legacy-types/endpoint.d.ts | 7 +- packages/rest/package.json | 14 +- packages/rest/src-4.0-types/pathTypes.d.ts | 20 ++ packages/rest/src-4.1-types/pathTypes.d.ts | 29 --- packages/rest/src/RestEndpoint.d.ts | 7 + packages/rest/src/__tests__/RestEndpoint.ts | 18 ++ packages/rest/src/utiltypes.ts | 3 + .../editor-types/@rest-hooks/rest.d.ts | 42 +++- 15 files changed, 373 insertions(+), 55 deletions(-) create mode 100644 examples/todo-app/tsconfig.typetest.json create mode 100644 examples/todo-app/typetest.ts create mode 100644 packages/endpoint/src-4.2-types/endpoint.d.ts create mode 100644 packages/rest/src-4.0-types/pathTypes.d.ts delete mode 100644 packages/rest/src-4.1-types/pathTypes.d.ts create mode 100644 packages/rest/src/utiltypes.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 63377bdc849e..a5514f2f6cb1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -121,10 +121,11 @@ jobs: - run: name: typecheck command: | - yarn run tsc --project examples/todo-app/tsconfig.json if [ "<< parameters.typescript-version >>" == "latest" ] || [ "<< parameters.typescript-version >>" == "~4.8" ]; then + yarn run tsc --project examples/todo-app/tsconfig.json yarn run tsc --project examples/github-app/tsconfig.json fi + yarn run tsc --project examples/todo-app/tsconfig.typetest.json esmodule-loosenulltypes: docker: *docker @@ -177,10 +178,11 @@ workflows: matrix: parameters: # 3.7 is min version for 'full enforcement' (TODO: we need to do a test without rest lib for this to work) - # 4.1 is min version for rest package working - # 4.3 is min version for rest package working well - # TODO: Add back "~4.1", "~4.3", once we can get around linaria types - typescript-version: ["~4.8", "latest"] + # 4.0 is min version for rest package working + # 4.1 is min version for rest package working well + # 4.3 + # 4.7 (but its broken so we do 4.8) lets you apply a generic type to a function type to see its return value + typescript-version: ["~4.0", "~4.1", "~4.3", "~4.8", "latest"] requires: - setup - esmodule-loosenulltypes: diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 8abe7a6694e2..105218fcc73f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -9,6 +9,7 @@ on: - 'packages/endpoint/src/schemas/**' - 'packages/core/**' - 'examples/benchmark/**' + - '.github/workflows/benchmark.yml' push: branches: - master @@ -29,11 +30,14 @@ jobs: - uses: actions/setup-node@v3 with: node-version: '18' - - name: Build packages + cache: 'yarn' + - name: Install packages run: | npm install -g corepack corepack enable yarn install --immutable + - name: Build packages + run: | NODE_ENV=production BROWSERSLIST_ENV=modern yarn workspaces foreach -ptivR --from @rest-hooks/core run build:lib && NODE_ENV=production BROWSERSLIST_ENV=modern yarn workspaces foreach -ptivR --from @rest-hooks/endpoint run build:lib - name: Run benchmark run: yarn workspace example-benchmark build && yarn workspace example-benchmark start | tee output.txt diff --git a/examples/todo-app/tsconfig.typetest.json b/examples/todo-app/tsconfig.typetest.json new file mode 100644 index 000000000000..da9b25c9cf5b --- /dev/null +++ b/examples/todo-app/tsconfig.typetest.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig", + "compilerOptions": { + "skipDefaultLibCheck": true, + }, + "include": ["typetest.ts"], +} diff --git a/examples/todo-app/typetest.ts b/examples/todo-app/typetest.ts new file mode 100644 index 000000000000..ee861c63df50 --- /dev/null +++ b/examples/todo-app/typetest.ts @@ -0,0 +1,29 @@ +import { useCache, useController, useSuspense } from '@rest-hooks/react'; + +import { queryRemaining, TodoResource } from './src/resources/TodoResource'; +import { UserResource } from './src/resources/UserResource'; + +function useTest() { + const ctrl = useController(); + const payload = { id: 1, title: '', userId: 1 }; + ctrl.fetch(TodoResource.create, payload); + + const todos = useSuspense(TodoResource.getList, { userId: 1 }); + useSuspense(TodoResource.getList); + todos.map((todo) => { + todo.pk(); + todo.title; + ctrl.fetch( + TodoResource.partialUpdate, + { id: todo.id }, + { completed: true }, + ); + }); + + const remaining = useCache(queryRemaining, { userId: 1 }); + + const users = useSuspense(UserResource.getList); + users.map((user) => { + user.name; + }); +} diff --git a/packages/endpoint/.gitignore b/packages/endpoint/.gitignore index d908f02c2ec8..b15e39b2009d 100644 --- a/packages/endpoint/.gitignore +++ b/packages/endpoint/.gitignore @@ -2,4 +2,6 @@ /dist /legacy /index.d.ts +/ts3.4 /ts4.0 +/ts4.2 diff --git a/packages/endpoint/package.json b/packages/endpoint/package.json index c4487eae4321..34ec600154c5 100644 --- a/packages/endpoint/package.json +++ b/packages/endpoint/package.json @@ -9,7 +9,7 @@ "unpkg": "dist/index.umd.min.js", "types": "lib/index.d.ts", "typesVersions": { - ">=4.2": { + ">=4.8": { "": [ "lib/index.d.ts" ], @@ -17,6 +17,14 @@ "lib/index.d.ts" ] }, + ">=4.2": { + "": [ + "ts4.2/index.d.ts" + ], + "*": [ + "ts4.2/index.d.ts" + ] + }, ">=4.0": { "": [ "ts4.0/index.d.ts" @@ -51,6 +59,7 @@ "src", "dist", "lib", + "ts4.2", "ts4.0", "ts3.4", "node.mjs", @@ -64,9 +73,9 @@ "build:js:node": "BROWSERSLIST_ENV=node12 rollup -c && echo '{\"type\":\"commonjs\"}' > dist/package.json", "build:js:browser": "BROWSERSLIST_ENV=legacy rollup -c", "build:bundle": "run-s build:js:\\*", - "build:clean": "rimraf lib dist legacy ts3.4 ts4.0 *.tsbuildinfo", + "build:clean": "rimraf lib dist legacy ts3.4 ts4.0 ts4.2 *.tsbuildinfo", "build": "yarn run build:lib && yarn run build:legacy:lib && yarn run build:bundle", - "build:legacy-types": "yarn run downlevel-dts lib ts3.4 && yarn run downlevel-dts lib ts4.0 --to=4.0 && copyfiles --up 1 ./src-4.0-types/**/*.d.ts ./ts3.4/ && copyfiles --up 1 ./src-4.0-types/**/*.d.ts ./ts4.0 && copyfiles --up 1 ./src-legacy-types/**/*.d.ts ./ts3.4/", + "build:legacy-types": "yarn run downlevel-dts lib ts3.4 && yarn run downlevel-dts lib ts4.0 --to=4.0 && yarn run downlevel-dts lib ts4.2 --to=4.2 && copyfiles --up 1 ./src-4.2-types/**/*.d.ts ./ts4.0/ && copyfiles --up 1 ./src-4.2-types/**/*.d.ts ./ts4.2 && copyfiles --up 1 ./src-4.0-types/**/*.d.ts ./ts3.4/ && copyfiles --up 1 ./src-4.0-types/**/*.d.ts ./ts4.0 && copyfiles --up 1 ./src-legacy-types/**/*.d.ts ./ts3.4/", "dev": "yarn run build:lib -w", "prepare": "yarn run build:lib", "prepack": "yarn prepare", diff --git a/packages/endpoint/src-4.2-types/endpoint.d.ts b/packages/endpoint/src-4.2-types/endpoint.d.ts new file mode 100644 index 000000000000..7c97a0924594 --- /dev/null +++ b/packages/endpoint/src-4.2-types/endpoint.d.ts @@ -0,0 +1,217 @@ +// relaxed constraints on call,apply,bind *this* + +/* eslint-disable @typescript-eslint/ban-types */ +import type { EndpointInterface, Schema } from './interface.js'; +import type { + EndpointExtraOptions, + FetchFunction, + PartialArray, +} from './types.js'; + +export interface EndpointOptions< + F extends FetchFunction = FetchFunction, + S extends Schema | undefined = undefined, + M extends true | undefined = undefined, +> extends EndpointExtraOptions { + key?: (...args: Parameters) => string; + sideEffect?: M; + schema?: S; + [k: string]: any; +} + +export interface EndpointExtendOptions< + F extends FetchFunction = FetchFunction, + S extends Schema | undefined = Schema | undefined, + M extends true | undefined = true | undefined, +> extends EndpointOptions { + fetch?: FetchFunction; +} + +export type ParamFromFetch = F extends ( + params: infer P, + body?: any, +) => Promise + ? P + : never; + +type Overwrite = Pick> & U; + +export type KeyofEndpointInstance = keyof EndpointInstance; + +export type ExtendedEndpoint< + O extends EndpointExtendOptions, + E extends EndpointInstance< + FetchFunction, + Schema | undefined, + true | undefined + >, + F extends FetchFunction, +> = EndpointInstance< + 'fetch' extends keyof O ? Exclude : E['fetch'], + 'schema' extends keyof O ? O['schema'] : E['schema'], + 'sideEffect' extends keyof O ? O['sideEffect'] : E['sideEffect'] +> & + Omit & + Omit; + +export function Make(...args: any[]): EndpointInstance; + +/** + * Defines an async data source. + * @see https://resthooks.io/docs/api/Endpoint + */ +export interface EndpointInstance< + F extends (...args: any) => Promise = FetchFunction, + S extends Schema | undefined = Schema | undefined, + M extends true | undefined = true | undefined, +> extends EndpointInstanceInterface { + extend< + E extends EndpointInstance< + (...args: any) => Promise, + Schema | undefined, + true | undefined + >, + O extends EndpointExtendOptions & + Partial>> & + Record, + >( + this: E, + options: Readonly, + ): ExtendedEndpoint; +} + +/** + * Defines an async data source. + * @see https://resthooks.io/docs/api/Endpoint + */ +export interface EndpointInstanceInterface< + F extends FetchFunction = FetchFunction, + S extends Schema | undefined = Schema | undefined, + M extends true | undefined = true | undefined, +> extends EndpointInterface { + constructor: EndpointConstructor; + + /** + * Calls the function, substituting the specified object for the this value of the function, and the specified array for the arguments of the function. + * @param thisArg The object to be used as the this object. + * @param argArray A set of arguments to be passed to the function. + */ + apply( + this: E, + thisArg: any, + argArray?: Parameters, + ): ReturnType; + + /** + * Calls a method of an object, substituting another object for the current object. + * @param thisArg The object to be used as the current object. + * @param argArray A list of arguments to be passed to the method. + */ + call( + this: E, + thisArg: any, + ...argArray: Parameters + ): ReturnType; + + /** + * For a given function, creates a bound function that has the same body as the original function. + * The this object of the bound function is associated with the specified object, and has the specified initial parameters. + * @param thisArg An object to which the this keyword can refer inside the new function. + * @param argArray A list of arguments to be passed to the new function. + */ + bind>>( + this: E, + thisArg: any, + ...args: readonly [...P] + ): EndpointInstance< + (...args: readonly [...RemoveArray, P>]) => ReturnType, + S, + M + > & + Omit>; + + /** Returns a string representation of a function. */ + toString(): string; + + prototype: any; + readonly length: number; + + // Non-standard extensions + arguments: any; + caller: F; + + key(...args: Parameters): string; + + readonly sideEffect: M; + + readonly schema: S; + + fetch: F; + + /* utilities */ + /** @see https://resthooks.io/rest/api/Endpoint#testKey */ + testKey(key: string): boolean; + + /** The following is for compatibility with FetchShape */ + /** @deprecated */ + readonly type: M extends undefined + ? 'read' + : IfAny>; + + /** @deprecated */ + getFetchKey(...args: OnlyFirst>): string; + /** @deprecated */ + options?: EndpointExtraOptions; +} + +interface EndpointConstructor { + new < + F extends ( + this: EndpointInstance & E, + params?: any, + body?: any, + ) => Promise, + S extends Schema | undefined = undefined, + M extends true | undefined = undefined, + E extends Record = {}, + >( + fetchFunction: F, + options?: EndpointOptions & E, + ): EndpointInstance & E; + readonly prototype: Function; +} +declare let Endpoint: EndpointConstructor; + +export default Endpoint; + +interface ExtendableEndpointConstructor { + new < + F extends ( + this: EndpointInstanceInterface & E, + params?: any, + body?: any, + ) => Promise, + S extends Schema | undefined = undefined, + M extends true | undefined = undefined, + E extends Record = {}, + >( + RestFetch: F, + options?: Readonly> & E, + ): EndpointInstanceInterface & E; + readonly prototype: Function; +} +export declare let ExtendableEndpoint: ExtendableEndpointConstructor; + +type IfAny = 0 extends 1 & T ? Y : N; +type IfTypeScriptLooseNull = 1 | undefined extends 1 ? Y : N; + +type OnlyFirst = A extends [] ? [] : [A[0]]; + +type RemoveArray = Rem extends [ + any, + ...infer RestRem, +] + ? Orig extends [any, ...infer RestOrig] + ? RemoveArray + : never + : Orig; diff --git a/packages/endpoint/src-legacy-types/endpoint.d.ts b/packages/endpoint/src-legacy-types/endpoint.d.ts index a211b5e41972..bb13e7aac0d3 100644 --- a/packages/endpoint/src-legacy-types/endpoint.d.ts +++ b/packages/endpoint/src-legacy-types/endpoint.d.ts @@ -1,4 +1,5 @@ // had to remove all [...T], which included importing PartialArray from types +// also relaxed constraints on call,apply,bind *this* /* eslint-disable @typescript-eslint/ban-types */ import { EndpointInterface, Schema } from './interface.js'; @@ -84,7 +85,7 @@ export interface EndpointInstanceInterface< */ apply( this: E, - thisArg: ThisParameterType, + thisArg: any, argArray?: Parameters, ): ReturnType; /** @@ -94,7 +95,7 @@ export interface EndpointInstanceInterface< */ call( this: E, - thisArg: ThisParameterType, + thisArg: any, ...argArray: Parameters ): ReturnType; /** @@ -105,7 +106,7 @@ export interface EndpointInstanceInterface< */ bind>( this: E, - thisArg: ThisParameterType, + thisArg: any, ...args: P ): EndpointInstance<() => ReturnType, S, M> & Pick>>; diff --git a/packages/rest/package.json b/packages/rest/package.json index 75e43fab0db7..83d5e37313b7 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -9,7 +9,7 @@ "unpkg": "dist/index.umd.min.js", "types": "lib/index.d.ts", "typesVersions": { - ">=4.3": { + ">=4.1": { "": [ "lib/index.d.ts" ], @@ -17,12 +17,12 @@ "lib/index.d.ts" ] }, - ">=4.1": { + ">=4.0": { "": [ - "ts4.1/index.d.ts" + "ts4.0/index.d.ts" ], "*": [ - "ts4.1/index.d.ts" + "ts4.0/index.d.ts" ] } }, @@ -45,7 +45,7 @@ "lib", "node.mjs", "legacy", - "ts4.1", + "ts4.0", "LICENSE", "README.md" ], @@ -55,8 +55,8 @@ "build:js:node": "BROWSERSLIST_ENV=node12 rollup -c", "build:js:browser": "BROWSERSLIST_ENV=legacy rollup -c", "build:bundle": "run-s build:js:\\* && echo '{\"type\":\"commonjs\"}' > dist/package.json", - "build:clean": "rimraf lib ts4.1 legacy dist *.tsbuildinfo", - "build:legacy-types": "yarn run downlevel-dts lib ts4.1 --to=4.1 && copyfiles --up 1 ./src-4.1-types/**/*.d.ts ./ts4.1", + "build:clean": "rimraf lib ts4.0 legacy dist *.tsbuildinfo", + "build:legacy-types": "yarn run downlevel-dts lib ts4.0 --to=4.0 && copyfiles --up 1 ./src-4.0-types/**/*.d.ts ./ts4.0", "build": "yarn run build:lib && yarn run build:legacy:lib && yarn run build:bundle", "dev": "yarn run build:lib -w", "prepare": "yarn run build:lib", diff --git a/packages/rest/src-4.0-types/pathTypes.d.ts b/packages/rest/src-4.0-types/pathTypes.d.ts new file mode 100644 index 000000000000..f4b8546a3588 --- /dev/null +++ b/packages/rest/src-4.0-types/pathTypes.d.ts @@ -0,0 +1,20 @@ +/** Computes the union of keys for a path string */ +export declare type PathKeys = string; + +/** Parameters for a given path */ +export declare type PathArgs = PathKeys extends never + ? unknown + : KeysToArgs>; + +export declare type KeysToArgs = { + [K in Key]?: string | number; +}; +export declare type PathArgsAndSearch = + PathKeys extends never + ? Record | undefined + : { + [K in PathKeys]: string | number; + } & Record; + +/** Removes the last :token */ +export declare type ShortenPath = S; diff --git a/packages/rest/src-4.1-types/pathTypes.d.ts b/packages/rest/src-4.1-types/pathTypes.d.ts deleted file mode 100644 index c879e864d112..000000000000 --- a/packages/rest/src-4.1-types/pathTypes.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -declare type OnlyOptional = S extends `${infer K}?` - ? K - : never; -declare type OnlyRequired = S extends `${string}?` - ? never - : S; -/** Computes the union of keys for a path string */ -export declare type PathKeys = string; - -/** Parameters for a given path */ -export declare type PathArgs = PathKeys extends never - ? unknown - : KeysToArgs>; -export declare type KeysToArgs = { - [K in Key as OnlyOptional]?: string | number; -} & { - [K in Key as OnlyRequired]: string | number; -}; -export declare type PathArgsAndSearch = OnlyRequired< - PathKeys -> extends never - ? Record | undefined - : { - [K in PathKeys as OnlyRequired]: string | number; - } & Record; -/** Removes the last :token */ -export declare type ShortenPath = S; -export {}; -//# sourceMappingURL=pathTypes.d.ts.map diff --git a/packages/rest/src/RestEndpoint.d.ts b/packages/rest/src/RestEndpoint.d.ts index 33b6c2c4b72e..606291d29a19 100644 --- a/packages/rest/src/RestEndpoint.d.ts +++ b/packages/rest/src/RestEndpoint.d.ts @@ -8,6 +8,7 @@ import type { } from '@rest-hooks/endpoint'; import { PathArgs } from './pathTypes.js'; +import { RequiredKeys } from './utiltypes.js'; export interface RestInstance< F extends FetchFunction = FetchFunction, @@ -393,6 +394,10 @@ export type ParamFetchWithBody = IfTypeScriptLooseNull< ? (this: EndpointInstanceInterface, body: B) => Promise : undefined extends P ? (this: EndpointInstanceInterface, body: B) => Promise + : RequiredKeys

extends never + ? + | ((this: EndpointInstanceInterface, body: B) => Promise) + | ((this: EndpointInstanceInterface, params: P, body: B) => Promise) : (this: EndpointInstanceInterface, params: P, body: B) => Promise >; @@ -406,6 +411,8 @@ export type ParamFetchNoBody = IfTypeScriptLooseNull< ? (this: EndpointInstanceInterface) => Promise : undefined extends P ? (this: EndpointInstanceInterface) => Promise + : RequiredKeys

extends never + ? (this: EndpointInstanceInterface, params?: P) => Promise : (this: EndpointInstanceInterface, params: P) => Promise >; diff --git a/packages/rest/src/__tests__/RestEndpoint.ts b/packages/rest/src/__tests__/RestEndpoint.ts index 68b43fadbbbd..b61326b79559 100644 --- a/packages/rest/src/__tests__/RestEndpoint.ts +++ b/packages/rest/src/__tests__/RestEndpoint.ts @@ -160,6 +160,24 @@ describe('RestEndpoint', () => { const y: undefined = updateUser.sideEffect; }); + it('only optional path means the arg is not required', () => { + const ep = new RestEndpoint({ path: '/users/:id?/:group?' }); + const epbody = new RestEndpoint({ + path: '/users/:id?/:group?', + body: { title: '' }, + }); + () => ep(); + () => ep({ id: 5 }); + () => ep({ group: 5 }); + () => ep({ id: 5, group: 5 }); + () => epbody({ title: 'hi' }); + () => epbody({ id: 5 }, { title: 'hi' }); + () => epbody({ group: 5 }, { title: 'hi' }); + () => epbody({ id: 5, group: 5 }, { title: 'hi' }); + // @ts-expect-error + () => epbody({ title: 'hi' }, { title: 'hi' }); + }); + /* TODO: it('should allow sideEffect overrides', () => { const weirdGetUser = new RestEndpoint({ path: 'http\\://test.com/user/:id', diff --git a/packages/rest/src/utiltypes.ts b/packages/rest/src/utiltypes.ts new file mode 100644 index 000000000000..609b31c37e1e --- /dev/null +++ b/packages/rest/src/utiltypes.ts @@ -0,0 +1,3 @@ +export type RequiredKeys = { + [K in keyof T]-?: {} extends Pick ? never : K; +}[keyof T]; diff --git a/website/src/components/Playground/editor-types/@rest-hooks/rest.d.ts b/website/src/components/Playground/editor-types/@rest-hooks/rest.d.ts index dbb1200a35aa..2ba364251387 100644 --- a/website/src/components/Playground/editor-types/@rest-hooks/rest.d.ts +++ b/website/src/components/Playground/editor-types/@rest-hooks/rest.d.ts @@ -1189,7 +1189,9 @@ type PaginationEndpoint< ParamFetchNoBody>, E['schema'], E['sideEffect'], - Pick + Pick & { + searchParams: Omit>; + } >; type BodyDefault = 'body' extends keyof O @@ -1284,8 +1286,26 @@ type RestType< } = { path: string }, // eslint-disable-next-line @typescript-eslint/ban-types > = IfTypeScriptLooseNull< - | RestTypeWithBody - | RestTypeNoBody, + RestInstance< + keyof UrlParams extends never + ? (this: EndpointInstanceInterface, body?: Body) => Promise + : string extends keyof UrlParams + ? + | ((this: EndpointInstanceInterface, body?: Body) => Promise) + | (( + this: EndpointInstanceInterface, + params: UrlParams, + body?: Body, + ) => Promise) + : ( + this: EndpointInstanceInterface, + params: UrlParams, + body?: Body, + ) => Promise, + S, + M, + O + >, Body extends {} ? RestTypeWithBody : RestTypeNoBody @@ -1331,8 +1351,13 @@ type RestFetch< >; type ParamFetchWithBody = IfTypeScriptLooseNull< - | ((this: EndpointInstanceInterface, body: B) => Promise) - | ((this: EndpointInstanceInterface, params: P, body: B) => Promise), + keyof P extends never + ? (this: EndpointInstanceInterface, body: B) => Promise + : string extends keyof P + ? + | ((this: EndpointInstanceInterface, body: B) => Promise) + | ((this: EndpointInstanceInterface, params: P, body: B) => Promise) + : (this: EndpointInstanceInterface, params: P, body: B) => Promise, P extends undefined ? (this: EndpointInstanceInterface, body: B) => Promise : undefined extends P @@ -1341,8 +1366,11 @@ type ParamFetchWithBody = IfTypeScriptLooseNull< >; type ParamFetchNoBody = IfTypeScriptLooseNull< - | ((this: EndpointInstanceInterface, params: P) => Promise) - | ((this: EndpointInstanceInterface) => Promise), + keyof P extends never + ? (this: EndpointInstanceInterface) => Promise + : string extends keyof P + ? (this: EndpointInstanceInterface, params?: P) => Promise + : (this: EndpointInstanceInterface, params: P) => Promise, P extends undefined ? (this: EndpointInstanceInterface) => Promise : undefined extends P