FRE-709: Document duplicate recovery wake - FRE-635 already recovered via FRE-708
This commit is contained in:
244
node_modules/svix/src/request.ts
generated
vendored
Normal file
244
node_modules/svix/src/request.ts
generated
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
import { ApiException, type XOR } from "./util";
|
||||
import type { HttpErrorOut, HTTPValidationError } from "./HttpErrors";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
export const LIB_VERSION = "1.90.0";
|
||||
const USER_AGENT = `svix-libs/${LIB_VERSION}/javascript`;
|
||||
|
||||
export enum HttpMethod {
|
||||
GET = "GET",
|
||||
HEAD = "HEAD",
|
||||
POST = "POST",
|
||||
PUT = "PUT",
|
||||
DELETE = "DELETE",
|
||||
CONNECT = "CONNECT",
|
||||
OPTIONS = "OPTIONS",
|
||||
TRACE = "TRACE",
|
||||
PATCH = "PATCH",
|
||||
}
|
||||
|
||||
export type SvixRequestContext = {
|
||||
/** The API base URL, like "https://api.svix.com" */
|
||||
baseUrl: string;
|
||||
/** The 'bearer' scheme access token */
|
||||
token: string;
|
||||
/** Time in milliseconds to wait for requests to get a response. */
|
||||
timeout?: number;
|
||||
/**
|
||||
* Custom fetch implementation to use for HTTP requests.
|
||||
* Useful for testing, adding custom middleware, or running in non-standard environments.
|
||||
*/
|
||||
fetch?: typeof fetch;
|
||||
} & XOR<
|
||||
{
|
||||
/** List of delays (in milliseconds) to wait before each retry attempt.*/
|
||||
retryScheduleInMs?: number[];
|
||||
},
|
||||
{
|
||||
/** The number of times the client will retry if a server-side error
|
||||
* or timeout is received.
|
||||
* Default: 2
|
||||
*/
|
||||
numRetries?: number;
|
||||
}
|
||||
>;
|
||||
|
||||
type QueryParameter = string | boolean | number | Date | string[] | null | undefined;
|
||||
|
||||
export class SvixRequest {
|
||||
constructor(
|
||||
private readonly method: HttpMethod,
|
||||
private path: string
|
||||
) {}
|
||||
|
||||
private body?: string;
|
||||
private queryParams: Record<string, string> = {};
|
||||
private headerParams: Record<string, string> = {};
|
||||
|
||||
public setPathParam(name: string, value: string) {
|
||||
const newPath = this.path.replace(`{${name}}`, encodeURIComponent(value));
|
||||
if (this.path === newPath) {
|
||||
throw new Error(`path parameter ${name} not found`);
|
||||
}
|
||||
this.path = newPath;
|
||||
}
|
||||
|
||||
public setQueryParams(params: { [name: string]: QueryParameter }) {
|
||||
for (const [name, value] of Object.entries(params)) {
|
||||
this.setQueryParam(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
public setQueryParam(name: string, value: QueryParameter) {
|
||||
if (value === undefined || value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
this.queryParams[name] = value;
|
||||
} else if (typeof value === "boolean" || typeof value === "number") {
|
||||
this.queryParams[name] = value.toString();
|
||||
} else if (value instanceof Date) {
|
||||
this.queryParams[name] = value.toISOString();
|
||||
} else if (Array.isArray(value)) {
|
||||
if (value.length > 0) {
|
||||
this.queryParams[name] = value.join(",");
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const _assert_unreachable: never = value;
|
||||
throw new Error(`query parameter ${name} has unsupported type`);
|
||||
}
|
||||
}
|
||||
|
||||
public setHeaderParam(name: string, value?: string) {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.headerParams[name] = value;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public setBody(value: any) {
|
||||
this.body = JSON.stringify(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send this request, returning the request body as a caller-specified type.
|
||||
*
|
||||
* If the server returns a 422 error, an `ApiException<HTTPValidationError>` is thrown.
|
||||
* If the server returns another 4xx error, an `ApiException<HttpErrorOut>` is thrown.
|
||||
*
|
||||
* If the server returns a 5xx error, the request is retried up to two times with exponential backoff.
|
||||
* If retries are exhausted, an `ApiException<HttpErrorOut>` is thrown.
|
||||
*/
|
||||
public async send<R>(
|
||||
ctx: SvixRequestContext,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
parseResponseBody: (jsonObject: any) => R
|
||||
): Promise<R> {
|
||||
const response = await this.sendInner(ctx);
|
||||
if (response.status === 204) {
|
||||
return <R>null;
|
||||
}
|
||||
const responseBody = await response.text();
|
||||
return parseResponseBody(JSON.parse(responseBody));
|
||||
}
|
||||
|
||||
/** Same as `send`, but the response body is discarded, not parsed. */
|
||||
public async sendNoResponseBody(ctx: SvixRequestContext): Promise<void> {
|
||||
await this.sendInner(ctx);
|
||||
}
|
||||
|
||||
private async sendInner(ctx: SvixRequestContext): Promise<Response> {
|
||||
const url = new URL(ctx.baseUrl + this.path);
|
||||
for (const [name, value] of Object.entries(this.queryParams)) {
|
||||
url.searchParams.set(name, value);
|
||||
}
|
||||
|
||||
if (
|
||||
this.headerParams["idempotency-key"] === undefined &&
|
||||
this.method.toUpperCase() === "POST"
|
||||
) {
|
||||
this.headerParams["idempotency-key"] = `auto_${uuidv4()}`;
|
||||
}
|
||||
|
||||
const randomId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
|
||||
if (this.body != null) {
|
||||
this.headerParams["content-type"] = "application/json";
|
||||
}
|
||||
// Cloudflare Workers fail if the credentials option is used in a fetch call.
|
||||
// This work around that. Source:
|
||||
// https://github.com/cloudflare/workers-sdk/issues/2514#issuecomment-21.90.0014
|
||||
const isCredentialsSupported = "credentials" in Request.prototype;
|
||||
|
||||
const response = await sendWithRetry(
|
||||
url,
|
||||
{
|
||||
method: this.method.toString(),
|
||||
body: this.body,
|
||||
headers: {
|
||||
accept: "application/json, */*;q=0.8",
|
||||
authorization: `Bearer ${ctx.token}`,
|
||||
"user-agent": USER_AGENT,
|
||||
"svix-req-id": randomId.toString(),
|
||||
...this.headerParams,
|
||||
},
|
||||
credentials: isCredentialsSupported ? "same-origin" : undefined,
|
||||
signal: ctx.timeout !== undefined ? AbortSignal.timeout(ctx.timeout) : undefined,
|
||||
},
|
||||
ctx.retryScheduleInMs,
|
||||
ctx.retryScheduleInMs?.[0],
|
||||
ctx.retryScheduleInMs?.length || ctx.numRetries,
|
||||
ctx.fetch
|
||||
);
|
||||
return filterResponseForErrors(response);
|
||||
}
|
||||
}
|
||||
|
||||
async function filterResponseForErrors(response: Response): Promise<Response> {
|
||||
if (response.status < 300) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const responseBody = await response.text();
|
||||
|
||||
if (response.status === 422) {
|
||||
throw new ApiException<HTTPValidationError>(
|
||||
response.status,
|
||||
JSON.parse(responseBody) as HTTPValidationError,
|
||||
response.headers
|
||||
);
|
||||
}
|
||||
|
||||
if (response.status >= 400 && response.status <= 499) {
|
||||
throw new ApiException<HttpErrorOut>(
|
||||
response.status,
|
||||
JSON.parse(responseBody) as HttpErrorOut,
|
||||
response.headers
|
||||
);
|
||||
}
|
||||
throw new ApiException(response.status, responseBody, response.headers);
|
||||
}
|
||||
|
||||
type SvixRequestInit = RequestInit & {
|
||||
headers: Record<string, string>;
|
||||
};
|
||||
|
||||
async function sendWithRetry(
|
||||
url: URL,
|
||||
init: SvixRequestInit,
|
||||
retryScheduleInMs?: number[],
|
||||
nextInterval = 50,
|
||||
triesLeft = 2,
|
||||
fetchImpl: typeof fetch = fetch,
|
||||
retryCount = 1
|
||||
): Promise<Response> {
|
||||
const sleep = (interval: number) =>
|
||||
new Promise((resolve) => setTimeout(resolve, interval));
|
||||
|
||||
try {
|
||||
const response = await fetchImpl(url, init);
|
||||
if (triesLeft <= 0 || response.status < 500) {
|
||||
return response;
|
||||
}
|
||||
} catch (e) {
|
||||
if (triesLeft <= 0) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
await sleep(nextInterval);
|
||||
init.headers["svix-retry-count"] = retryCount.toString();
|
||||
nextInterval = retryScheduleInMs?.[retryCount] || nextInterval * 2;
|
||||
return await sendWithRetry(
|
||||
url,
|
||||
init,
|
||||
retryScheduleInMs,
|
||||
nextInterval,
|
||||
--triesLeft,
|
||||
fetchImpl,
|
||||
++retryCount
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user