UNPKG

@prismicio/client

Version:

The official JavaScript + TypeScript client library for Prismic

1 lines 7.12 kB
{"version":3,"file":"request.cjs","names":["THROTTLED_RUNNERS: Partial<Record<string, LimitFunction>>","DEDUPLICATED_JOBS: Partial<\n\tRecord<string, Map<AbortSignalLike | undefined, Promise<ResponseLike>>>\n>","memoized: ResponseLike","job: Promise<ResponseLike>","pLimit"],"sources":["../../src/lib/request.ts"],"sourcesContent":["import { type LimitFunction, pLimit } from \"./pLimit\"\n\n/**\n * The default number of milliseconds to wait before retrying a rate-limited\n * `fetch()` request (429 response code). The default value is only used if the\n * response does not include a `retry-after` header.\n */\nexport const DEFAULT_RETRY_AFTER = 1500 // ms\n\n/**\n * A record of URLs mapped to throttled task runners.\n */\nconst THROTTLED_RUNNERS: Partial<Record<string, LimitFunction>> = {}\n\n/**\n * A record of URLs mapped to active deduplicated jobs. Jobs are keyed by their\n * optional signal.\n */\nconst DEDUPLICATED_JOBS: Partial<\n\tRecord<string, Map<AbortSignalLike | undefined, Promise<ResponseLike>>>\n> = {}\n\n/**\n * A universal API to make network requests. A subset of the `fetch()` API.\n *\n * {@link https://developer.mozilla.org/en-US/docs/Web/API/fetch}\n */\nexport type FetchLike = (\n\tinput: string,\n\tinit?: RequestInitLike,\n) => Promise<ResponseLike>\n\n/**\n * An object that allows you to abort a `fetch()` request if needed via an\n * `AbortController` object\n *\n * {@link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal}\n */\n// `any` is used often here to ensure this type is universally valid among\n// different AbortSignal implementations. The types of each property are not\n// important to validate since it is blindly passed to a given `fetch()`\n// function.\n// oxlint-disable-next-line no-explicit-any\nexport type AbortSignalLike = any\n\n/**\n * A subset of RequestInit properties to configure a `fetch()` request.\n */\n// Only options relevant to the client are included. Extending from the full\n// RequestInit would cause issues, such as accepting Header objects.\n//\n// An interface is used to allow other libraries to augment the type with\n// environment-specific types.\nexport interface RequestInitLike extends Pick<RequestInit, \"cache\"> {\n\t/**\n\t * The HTTP method to use for the request.\n\t */\n\tmethod?: string\n\n\t/**\n\t * The request body to send with the request.\n\t */\n\t// We want to keep the body type as compatible as possible, so\n\t// we only declare the type we need and accept anything else.\n\t// oxlint-disable-next-line no-explicit-any\n\tbody?: any | FormData | string\n\n\t/**\n\t * An object literal to set the `fetch()` request's headers.\n\t */\n\theaders?: Record<string, string>\n\n\t/**\n\t * An AbortSignal to set the `fetch()` request's signal.\n\t *\n\t * See:\n\t * [https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)\n\t */\n\t// NOTE: `AbortSignalLike` is `any`! It is left as `AbortSignalLike`\n\t// for backwards compatibility (the type is exported) and to signal to\n\t// other readers that this should be an AbortSignal-like object.\n\tsignal?: AbortSignalLike\n}\n\n/**\n * The minimum required properties from Response.\n */\nexport interface ResponseLike {\n\tok: boolean\n\tstatus: number\n\theaders: HeadersLike\n\turl: string\n\t// oxlint-disable-next-line no-explicit-any\n\tjson(): Promise<any>\n\ttext(): Promise<string>\n\tblob(): Promise<Blob>\n\tclone(): ResponseLike\n}\n\n/**\n * The minimum required properties from Headers.\n */\nexport interface HeadersLike {\n\tget(name: string): string | null\n}\n\nasync function memoizeResponse(response: ResponseLike): Promise<ResponseLike> {\n\t// Deduplicated responses are shared across multiple callers. Calling\n\t// response.clone() on a shared response can cause backpressure hangs\n\t// in Node.js, so we buffer the body as a blob upfront instead.\n\tconst blob = await response.blob()\n\n\tconst memoized: ResponseLike = {\n\t\tok: response.ok,\n\t\tstatus: response.status,\n\t\theaders: response.headers,\n\t\turl: response.url,\n\t\ttext: async () => blob.text(),\n\t\tjson: async () => JSON.parse(await blob.text()),\n\t\tblob: async () => blob,\n\t\tclone: () => memoized,\n\t}\n\n\treturn memoized\n}\n\n/**\n * Makes an HTTP request with automatic retry for rate limits and request\n * deduplication.\n *\n * @param url - The URL to request.\n * @param init - Fetch options.\n * @param fetchFn - The fetch function to use.\n *\n * @returns The response from the fetch request.\n */\nexport async function request(\n\turl: URL,\n\tinit: RequestInitLike | undefined,\n\tfetchFn: FetchLike,\n): Promise<ResponseLike> {\n\tconst stringURL = url.toString()\n\n\tlet job: Promise<ResponseLike>\n\n\t// Throttle requests with a body.\n\tif (init?.body) {\n\t\t// Rate limiting is done per hostname.\n\t\tconst runner = (THROTTLED_RUNNERS[url.hostname] ||= pLimit({\n\t\t\tinterval: DEFAULT_RETRY_AFTER,\n\t\t}))\n\n\t\tjob = runner(() => fetchFn(stringURL, init))\n\t} else {\n\t\t// Deduplicate all other requests.\n\t\tconst existingJob = DEDUPLICATED_JOBS[stringURL]?.get(init?.signal)\n\t\tif (existingJob) {\n\t\t\tjob = existingJob\n\t\t} else {\n\t\t\tjob = fetchFn(stringURL, init)\n\t\t\t\t.then(memoizeResponse)\n\t\t\t\t.finally(() => {\n\t\t\t\t\tDEDUPLICATED_JOBS[stringURL]?.delete(init?.signal)\n\t\t\t\t\tif (DEDUPLICATED_JOBS[stringURL]?.size === 0) {\n\t\t\t\t\t\tdelete DEDUPLICATED_JOBS[stringURL]\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tconst map = (DEDUPLICATED_JOBS[stringURL] ||= new Map())\n\t\t\tmap.set(init?.signal, job)\n\t\t}\n\t}\n\n\tconst response = await job\n\n\t// Retry rate limited requests.\n\tif (response.status === 429) {\n\t\tconst retryAfter = Number(response.headers.get(\"retry-after\"))\n\t\tconst resolvedRetryAfter = Number.isNaN(retryAfter)\n\t\t\t? DEFAULT_RETRY_AFTER\n\t\t\t: retryAfter * 1000\n\n\t\tawait new Promise((resolve) => setTimeout(resolve, resolvedRetryAfter))\n\n\t\treturn request(url, init, fetchFn)\n\t}\n\n\treturn response\n}\n"],"mappings":";;;;;;;;AAOA,MAAa,sBAAsB;;;;AAKnC,MAAMA,oBAA4D,EAAE;;;;;AAMpE,MAAMC,oBAEF,EAAE;AAsFN,eAAe,gBAAgB,UAA+C;CAI7E,MAAM,OAAO,MAAM,SAAS,MAAM;CAElC,MAAMC,WAAyB;EAC9B,IAAI,SAAS;EACb,QAAQ,SAAS;EACjB,SAAS,SAAS;EAClB,KAAK,SAAS;EACd,MAAM,YAAY,KAAK,MAAM;EAC7B,MAAM,YAAY,KAAK,MAAM,MAAM,KAAK,MAAM,CAAC;EAC/C,MAAM,YAAY;EAClB,aAAa;EACb;AAED,QAAO;;;;;;;;;;;;AAaR,eAAsB,QACrB,KACA,MACA,SACwB;CACxB,MAAM,YAAY,IAAI,UAAU;CAEhC,IAAIC;AAGJ,iDAAI,KAAM,MAAM;;AAMf,SAJgB,kCAAkB,IAAI,cAAtB,mCAAoCC,sBAAO,EAC1D,UAAU,qBACV,CAAC,SAEiB,QAAQ,WAAW,KAAK,CAAC;QACtC;;EAEN,MAAM,uCAAc,kBAAkB,0FAAY,gDAAI,KAAM,OAAO;AACnE,MAAI,YACH,OAAM;OACA;AACN,SAAM,QAAQ,WAAW,KAAK,CAC5B,KAAK,gBAAgB,CACrB,cAAc;;AACd,gDAAkB,oFAAY,mDAAO,KAAM,OAAO;AAClD,mCAAI,kBAAkB,4FAAY,UAAS,EAC1C,QAAO,kBAAkB;KAEzB;AAEH,IADa,kBAAkB,eAAlB,kBAAkB,6BAAe,IAAI,KAAK,GACnD,gDAAI,KAAM,QAAQ,IAAI;;;CAI5B,MAAM,WAAW,MAAM;AAGvB,KAAI,SAAS,WAAW,KAAK;EAC5B,MAAM,aAAa,OAAO,SAAS,QAAQ,IAAI,cAAc,CAAC;EAC9D,MAAM,qBAAqB,OAAO,MAAM,WAAW,GAChD,sBACA,aAAa;AAEhB,QAAM,IAAI,SAAS,YAAY,WAAW,SAAS,mBAAmB,CAAC;AAEvE,SAAO,QAAQ,KAAK,MAAM,QAAQ;;AAGnC,QAAO"}