@clickup/rest-client
Version:
A syntax sugar tool around Node fetch() API, tailored to work with TypeScript and response validators
154 lines • 7.17 kB
TypeScript
/// <reference types="node" />
/// <reference types="node" />
import type RestOptions from "./RestOptions";
import RestRequest from "./RestRequest";
import type RestResponse from "./RestResponse";
/**
* A callback which returns access token, possibly after refreshing it, and also
* possibly before a retry on "invalid token" condition. I.e. it can be called
* once or twice (the 2nd time after the previous request error, and that error
* will be passed as a parameter).
*/
export interface TokenGetter<TData = string> {
(prevError: Error | null): Promise<TData>;
}
/**
* RestClient is an immutable object which allows to:
* 1. Send remote requests in different formats, in a caller-friendly manner.
* 2. Create a new RestClient objects deriving the current set of options and
* adding new ones.
*/
export default class RestClient {
private readonly _options;
constructor(options?: Partial<RestOptions>);
/**
* Returns a new RestClient with some options updated with the passed ones.
*/
withOptions(options: Partial<RestOptions>): RestClient;
/**
* Returns a new RestClient with added middleware.
*/
withMiddleware(middleware: RestOptions["middlewares"][0], method?: "unshift" | "push"): RestClient;
/**
* Returns a new RestClient with the base URL which will be prepended to all
* relative paths in get(), writeForm() etc. Allows to defer resolution of
* this base URL to the very late per-request moment. The complicated piece
* here is that, if we want base URL to be resolved asynchronously, we often
* times want to reuse the same RestClient object (to e.g. fetch some part of
* the base URL using already authenticated client). And a re-enterable call
* appears here which we must protect against in the code below.
*/
withBase(base: string | (() => Promise<string>)): RestClient;
/**
* Returns a new RestClient with a custom header.
*/
withHeader(name: string, value: string | (() => Promise<string>)): RestClient;
/**
* Returns a new RestClient with a bearer token authentication workflow.
* - RestClient supports interception of options.isTokenInvalid() signal and
* conversion it into RestTokenInvalidError exception.
* - If a token() is a lambda with 1 argument, it may be called the 2nd time
* when we get an isTokenInvalid() signal. In this case, the request is
* retried.
* - If token() is a lambda with 0 arguments, that means it doesn't want to
* watch for the isTokenInvalid() signal, so there is no sense in retrying
* the request either.
*
* From the first sight, it looks like options.isTokenInvalid() signal is
* coupled to setBearer() auth method only. But it's not true:
* isTokenInvalid() makes sense for ALL authentication methods actually (even
* for basic auth), and setBearer() is just one of "clients" which implements
* refreshing/retries on top of isTokenInvalid().
*
* Passing the token as lambda allows the caller to implement some complex
* logic, e.g.:
* - oauth2 tokens refreshing
* - marking the token as "revoked" in the database in case the refresh fails
* - marking the token as "revoked" after a failed request if refresh-token is
* not supported
*/
withBearer(token: TokenGetter, bearerPrefix?: string): RestClient;
/**
* Returns a new RestClient with oauth1 authentication workflow.
* - In case we get an options.isTokenInvalid() signal, the token() lambda is
* called the 2nd time with the error object, then the request is retries.
* This gives the lambda a chance to recover or update something in the
* database.
*
* We use a separate and small oauth-1.0a node library here, because the more
* popular one (https://www.npmjs.com/package/oauth) doesn't support signing
* of arbitrary requests, it can only send its own requests.
*/
withOAuth1(consumer: {
consumerKey: string;
consumerSecret: string;
}, token: TokenGetter<{
token: string;
tokenSecret: string;
}>): RestClient;
/**
* Returns a new RestClient with basic authorization workflow.
*/
withBasic(token: TokenGetter<{
name: string;
password: string;
}>): RestClient;
/**
* Sends a plain GET request without body.
*
* NOTE, all args will be passed through `encodeURIComponent`.
*/
get(path: string, args?: Partial<Record<string, string | number | string[]>>, accept?: string): RestRequest<any>;
/**
* Writes some raw string, buffer or a stream.
*/
writeRaw(path: string, body: string | Buffer | NodeJS.ReadableStream, contentType: string, method?: "POST" | "PUT" | "PATCH", accept?: string): RestRequest<any>;
/**
* A shortcut method to write JSON body.
*/
writeJson(path: string, body: any, method?: "POST" | "PUT" | "PATCH" | "DELETE", accept?: string): RestRequest<any>;
/**
* A shortcut method to write "application/x-www-form-urlencoded" data.
*/
writeForm(path: string, body: Partial<Record<string, string>> | string, method?: "POST" | "PUT" | "PATCH", accept?: string): RestRequest<any>;
/**
* A shortcut method to write DELETE request.
*/
writeDelete(path: string, args?: Partial<Record<string, string>>, accept?: string): RestRequest<any>;
/**
* Returns a RestRequest prepared for sending GraphQL operation.
* - Expects the response to contain no errors; throws otherwise.
* - In case of success, returns just the content of `data` field (this is
* different with writeGraphQLNullable() which returns `data` as a separate
* fields along with `error` and `errors`).
*/
writeGraphQLX(query: string, variables?: any): RestRequest<any>;
/**
* Same as writeGraphQLX(), but doesn't throw if GraphQL response contains
* non-empty `error` or `errors` fields and instead returns the full response.
* I.e. allows the caller to process these errors.
*/
writeGraphQLNullable(query: string, variables?: any): RestRequest<{
data?: any;
error?: any;
errors?: any[] | undefined;
} | null | undefined>;
/**
* Performs a series of Content-Range requests with content from a sequence of
* Buffers.
*/
rangeUpload(path: string, mimeType: string, stream: AsyncIterable<Buffer>, method: "POST" | "PUT" | undefined, chunkSize: number): Promise<string | null>;
private _writeGraphQLImpl;
/**
* Sends a plain request (with no body, like GET or DELETE).
*/
private _noBodyRequest;
}
/**
* @ignore
* Calls token(null), then runs body() passing the result there. If we get a
* RestTokenInvalidError exception, call token() with this error as a parameter
* and then passes the response to body() again (kinda retry with a new token).
*/
export declare function tokenRetryStrategy<TData>(token: TokenGetter<TData>, body: (tokenData: TData) => Promise<RestResponse>): Promise<RestResponse>;
//# sourceMappingURL=RestClient.d.ts.map