UNPKG

@prismicio/client

Version:

The official JavaScript + TypeScript client library for Prismic

1 lines 11.7 kB
{"version":3,"file":"BaseClient.cjs","sources":["../../src/BaseClient.ts"],"sourcesContent":["import { type LimitFunction, pLimit } from \"./lib/pLimit\"\n\nimport { PrismicError } from \"./errors/PrismicError\"\n\n/**\n * The default delay used with APIs not providing rate limit headers.\n *\n * @internal\n */\nexport const UNKNOWN_RATE_LIMIT_DELAY = 1500\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// eslint-disable-next-line @typescript-eslint/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// eslint-disable-next-line @typescript-eslint/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\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tjson(): Promise<any>\n\ttext(): Promise<string>\n\tblob(): Promise<Blob>\n}\n\n/**\n * The minimum required properties from Headers.\n */\nexport interface HeadersLike {\n\tget(name: string): string | null\n}\n\n/**\n * The minimum required properties to treat as an HTTP Request for automatic\n * Prismic preview support.\n */\nexport type HttpRequestLike =\n\t| /**\n\t * Web API Request\n\t *\n\t * @see http://developer.mozilla.org/en-US/docs/Web/API/Request\n\t */\n\t{\n\t\t\theaders?: {\n\t\t\t\tget(name: string): string | null\n\t\t\t}\n\t\t\turl?: string\n\t }\n\n\t/**\n\t * Express-style Request\n\t */\n\t| {\n\t\t\theaders?: {\n\t\t\t\tcookie?: string\n\t\t\t}\n\t\t\tquery?: Record<string, unknown>\n\t }\n\n/**\n * Configuration for clients that determine how APIs are queried.\n */\nexport type BaseClientConfig = {\n\t/**\n\t * The function used to make network requests to the Prismic REST API. In\n\t * environments where a global `fetch` function does not exist, such as\n\t * Node.js, this function must be provided.\n\t */\n\tfetch?: FetchLike\n\n\t/**\n\t * Options provided to the client's `fetch()` on all network requests. These\n\t * options will be merged with internally required options. They can also be\n\t * overriden on a per-query basis using the query's `fetchOptions` parameter.\n\t */\n\tfetchOptions?: RequestInitLike\n}\n\n/**\n * Parameters for any client method that use `fetch()`.\n */\nexport type FetchParams = {\n\t/**\n\t * Options provided to the client's `fetch()` on all network requests. These\n\t * options will be merged with internally required options. They can also be\n\t * overriden on a per-query basis using the query's `fetchOptions` parameter.\n\t */\n\tfetchOptions?: RequestInitLike\n\n\t/**\n\t * An `AbortSignal` provided by an `AbortController`. This allows the network\n\t * request to be cancelled if necessary.\n\t *\n\t * @deprecated Move the `signal` parameter into `fetchOptions.signal`:\n\t *\n\t * @see \\<https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\\>\n\t */\n\tsignal?: AbortSignalLike\n}\n\n/**\n * The result of a `fetch()` job.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype FetchJobResult<TJSON = any> = {\n\tstatus: number\n\theaders: HeadersLike\n\tjson: TJSON\n\ttext?: string\n}\n\nexport abstract class BaseClient {\n\t/**\n\t * The function used to make network requests to the Prismic REST API. In\n\t * environments where a global `fetch` function does not exist, such as\n\t * Node.js, this function must be provided.\n\t */\n\tfetchFn: FetchLike\n\n\tfetchOptions?: RequestInitLike\n\n\t/**\n\t * Active queued `fetch()` jobs keyed by URL and AbortSignal (if it exists).\n\t */\n\tprivate queuedFetchJobs: Record<string, LimitFunction> = {}\n\n\t/**\n\t * Active deduped `fetch()` jobs keyed by URL and AbortSignal (if it exists).\n\t */\n\tprivate dedupedFetchJobs: Record<\n\t\tstring,\n\t\tMap<AbortSignalLike | undefined, Promise<FetchJobResult>>\n\t> = {}\n\n\tconstructor(options: BaseClientConfig) {\n\t\tthis.fetchOptions = options.fetchOptions\n\n\t\tif (typeof options.fetch === \"function\") {\n\t\t\tthis.fetchFn = options.fetch\n\t\t} else if (typeof globalThis.fetch === \"function\") {\n\t\t\tthis.fetchFn = globalThis.fetch as FetchLike\n\t\t} else {\n\t\t\tthrow new PrismicError(\n\t\t\t\t\"A valid fetch implementation was not provided. In environments where fetch is not available (including Node.js), a fetch implementation must be provided via a polyfill or the `fetch` option.\",\n\t\t\t\tundefined,\n\t\t\t\tundefined,\n\t\t\t)\n\t\t}\n\n\t\t// If the global fetch function is used, we must bind it to the global scope.\n\t\tif (this.fetchFn === globalThis.fetch) {\n\t\t\tthis.fetchFn = this.fetchFn.bind(globalThis)\n\t\t}\n\t}\n\n\tprotected async fetch(\n\t\turl: string,\n\t\tparams: FetchParams = {},\n\t): Promise<FetchJobResult> {\n\t\tconst requestInit: RequestInitLike = {\n\t\t\t...this.fetchOptions,\n\t\t\t...params.fetchOptions,\n\t\t\theaders: {\n\t\t\t\t...this.fetchOptions?.headers,\n\t\t\t\t...params.fetchOptions?.headers,\n\t\t\t},\n\t\t\tsignal:\n\t\t\t\tparams.fetchOptions?.signal ||\n\t\t\t\tparams.signal ||\n\t\t\t\tthis.fetchOptions?.signal,\n\t\t}\n\n\t\t// Request with a `body` are throttled, others are deduped.\n\t\tif (params.fetchOptions?.body) {\n\t\t\treturn this.queueFetch(url, requestInit)\n\t\t} else {\n\t\t\treturn this.dedupeFetch(url, requestInit)\n\t\t}\n\t}\n\n\tprivate queueFetch(\n\t\turl: string,\n\t\trequestInit: RequestInitLike = {},\n\t): Promise<FetchJobResult> {\n\t\t// Rate limiting is done per hostname.\n\t\tconst hostname = new URL(url).hostname\n\n\t\tif (!this.queuedFetchJobs[hostname]) {\n\t\t\tthis.queuedFetchJobs[hostname] = pLimit({\n\t\t\t\tinterval: UNKNOWN_RATE_LIMIT_DELAY,\n\t\t\t})\n\t\t}\n\n\t\treturn this.queuedFetchJobs[hostname](() =>\n\t\t\tthis.createFetchJob(url, requestInit),\n\t\t)\n\t}\n\n\tprivate dedupeFetch(\n\t\turl: string,\n\t\trequestInit: RequestInitLike = {},\n\t): Promise<FetchJobResult> {\n\t\tlet job: Promise<FetchJobResult>\n\n\t\t// `fetchJobs` is keyed twice: first by the URL and again by is\n\t\t// signal, if one exists.\n\t\t//\n\t\t// Using two keys allows us to reuse fetch requests for\n\t\t// equivalent URLs, but eject when we detect unique signals.\n\t\tif (\n\t\t\tthis.dedupedFetchJobs[url] &&\n\t\t\tthis.dedupedFetchJobs[url].has(requestInit.signal)\n\t\t) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tjob = this.dedupedFetchJobs[url].get(requestInit.signal)!\n\t\t} else {\n\t\t\tthis.dedupedFetchJobs[url] = this.dedupedFetchJobs[url] || new Map()\n\n\t\t\tjob = this.createFetchJob(url, requestInit).finally(() => {\n\t\t\t\tthis.dedupedFetchJobs[url]?.delete(requestInit.signal)\n\n\t\t\t\tif (this.dedupedFetchJobs[url]?.size === 0) {\n\t\t\t\t\tdelete this.dedupedFetchJobs[url]\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tthis.dedupedFetchJobs[url].set(requestInit.signal, job)\n\t\t}\n\n\t\treturn job\n\t}\n\n\tprivate createFetchJob(\n\t\turl: string,\n\t\trequestInit: RequestInitLike = {},\n\t): Promise<FetchJobResult> {\n\t\treturn this.fetchFn(url, requestInit).then(async (res) => {\n\t\t\t// We can assume Prismic REST API responses\n\t\t\t// will have a `application/json`\n\t\t\t// Content Type. If not, this will\n\t\t\t// throw, signaling an invalid\n\t\t\t// response.\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\t\tlet json: any = undefined\n\t\t\tlet text: string | undefined = undefined\n\t\t\tif (res.ok) {\n\t\t\t\ttry {\n\t\t\t\t\tjson = await res.json()\n\t\t\t\t} catch {\n\t\t\t\t\t// noop\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\ttext = await res.text()\n\t\t\t\t\tjson = JSON.parse(text)\n\t\t\t\t} catch {\n\t\t\t\t\t// noop\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tstatus: res.status,\n\t\t\t\theaders: res.headers,\n\t\t\t\tjson,\n\t\t\t\ttext,\n\t\t\t}\n\t\t})\n\t}\n}\n"],"names":["PrismicError","pLimit"],"mappings":";;;;;;;AASO,MAAM,2BAA2B;MAmKlB,WAAU;AAAA,EAuB/B,YAAY,SAAyB;AAjBrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA;AAKQ;AAAA;AAAA;AAAA,2CAAiD;AAKjD;AAAA;AAAA;AAAA,4CAGJ;AAGH,SAAK,eAAe,QAAQ;AAExB,QAAA,OAAO,QAAQ,UAAU,YAAY;AACxC,WAAK,UAAU,QAAQ;AAAA,IACb,WAAA,OAAO,WAAW,UAAU,YAAY;AAClD,WAAK,UAAU,WAAW;AAAA,IAAA,OACpB;AACN,YAAM,IAAIA,aAAA,aACT,kMACA,QACA,MAAS;AAAA,IAAA;AAKP,QAAA,KAAK,YAAY,WAAW,OAAO;AACtC,WAAK,UAAU,KAAK,QAAQ,KAAK,UAAU;AAAA,IAAA;AAAA,EAC5C;AAAA,EAGS,MAAM,MACf,KACA,SAAsB,IAAE;;AAExB,UAAM,cAA+B;AAAA,MACpC,GAAG,KAAK;AAAA,MACR,GAAG,OAAO;AAAA,MACV,SAAS;AAAA,QACR,IAAG,UAAK,iBAAL,mBAAmB;AAAA,QACtB,IAAG,YAAO,iBAAP,mBAAqB;AAAA,MACxB;AAAA,MACD,UACC,YAAO,iBAAP,mBAAqB,WACrB,OAAO,YACP,UAAK,iBAAL,mBAAmB;AAAA;AAIjB,SAAA,YAAO,iBAAP,mBAAqB,MAAM;AACvB,aAAA,KAAK,WAAW,KAAK,WAAW;AAAA,IAAA,OACjC;AACC,aAAA,KAAK,YAAY,KAAK,WAAW;AAAA,IAAA;AAAA,EACzC;AAAA,EAGO,WACP,KACA,cAA+B,IAAE;AAGjC,UAAM,WAAW,IAAI,IAAI,GAAG,EAAE;AAE9B,QAAI,CAAC,KAAK,gBAAgB,QAAQ,GAAG;AAC/B,WAAA,gBAAgB,QAAQ,IAAIC,cAAO;AAAA,QACvC,UAAU;AAAA,MAAA,CACV;AAAA,IAAA;AAGK,WAAA,KAAK,gBAAgB,QAAQ,EAAE,MACrC,KAAK,eAAe,KAAK,WAAW,CAAC;AAAA,EAAA;AAAA,EAI/B,YACP,KACA,cAA+B,IAAE;AAE7B,QAAA;AAQH,QAAA,KAAK,iBAAiB,GAAG,KACzB,KAAK,iBAAiB,GAAG,EAAE,IAAI,YAAY,MAAM,GAChD;AAED,YAAM,KAAK,iBAAiB,GAAG,EAAE,IAAI,YAAY,MAAM;AAAA,IAAA,OACjD;AACD,WAAA,iBAAiB,GAAG,IAAI,KAAK,iBAAiB,GAAG,yBAAS;AAE/D,YAAM,KAAK,eAAe,KAAK,WAAW,EAAE,QAAQ,MAAK;;AACxD,mBAAK,iBAAiB,GAAG,MAAzB,mBAA4B,OAAO,YAAY;AAE/C,cAAI,UAAK,iBAAiB,GAAG,MAAzB,mBAA4B,UAAS,GAAG;AACpC,iBAAA,KAAK,iBAAiB,GAAG;AAAA,QAAA;AAAA,MACjC,CACA;AAED,WAAK,iBAAiB,GAAG,EAAE,IAAI,YAAY,QAAQ,GAAG;AAAA,IAAA;AAGhD,WAAA;AAAA,EAAA;AAAA,EAGA,eACP,KACA,cAA+B,IAAE;AAEjC,WAAO,KAAK,QAAQ,KAAK,WAAW,EAAE,KAAK,OAAO,QAAO;AAOxD,UAAI,OAAY;AAChB,UAAI,OAA2B;AAC/B,UAAI,IAAI,IAAI;AACP,YAAA;AACI,iBAAA,MAAM,IAAI;gBACV;AAAA,QAAA;AAAA,MAER,OACM;AACF,YAAA;AACI,iBAAA,MAAM,IAAI;AACV,iBAAA,KAAK,MAAM,IAAI;AAAA,QAAA,QACf;AAAA,QAAA;AAAA,MAER;AAGM,aAAA;AAAA,QACN,QAAQ,IAAI;AAAA,QACZ,SAAS,IAAI;AAAA,QACb;AAAA,QACA;AAAA;KAED;AAAA,EAAA;AAEF;;;"}