UNPKG

@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
/// <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