Path: blob/main/extensions/copilot/src/shared-fetch-utils/common/advancedFetcher.ts
13397 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import type { FetchMiddleware, HttpFetchFn, HttpRequest, HttpResponse } from './fetchTypes';67/**8* Composes an array of middlewares into a single middleware. Middlewares9* execute left-to-right: the first element wraps the outermost layer.10*11* ```12* composeFetchMiddleware(a, b, c)(httpFetch)13* // equivalent to: a(b(c(httpFetch)))14* ```15*/16export function composeFetchMiddleware(...middlewares: readonly FetchMiddleware[]): FetchMiddleware {17return (baseFetch) => middlewares.reduceRight<HttpFetchFn>((next, mw) => mw(next), baseFetch);18}1920// ── Factory ─────────────────────────────────────────────────────────────2122export interface AdvancedFetchOptions<T> {23/**24* The HTTP request to send. May be a static object, a synchronous25* factory, or an async factory (useful when headers depend on runtime26* state such as an auth token).27*/28readonly request: HttpRequest | (() => HttpRequest | Promise<HttpRequest>);2930/**31* The underlying HTTP transport. Callers typically bridge their own32* `IFetcherService` here.33*/34readonly httpFetch: HttpFetchFn;3536/**37* Extracts the domain value `T` from a successful HTTP response.38*/39readonly parseResponse: (response: HttpResponse) => Promise<T>;4041/**42* Middleware applied around the HTTP fetch in the order listed (first43* middleware is outermost).44*/45readonly middleware?: readonly FetchMiddleware[];46}4748/**49* Creates a `() => Promise<T>` suitable for passing as the `fetch` option50* of {@link FetchedValue}.51*52* @example53* ```ts54* const fetchConfig = createAdvancedFetch<ConfigType>({55* request: { url: 'https://api.example.com/config', headers: {} },56* httpFetch: async (req) => {57* const res = await fetcherService.fetch(req.url, {58* callSite: 'configFetch',59* headers: req.headers,60* });61* return { status: res.status, headers: res.headers, body: res.body, json: () => res.json(), text: () => res.text() };62* },63* parseResponse: async (res) => await res.json() as ConfigType,64* middleware: [65* windowActiveMiddleware(envService),66* etagMiddleware(),67* authBlockedMiddleware(),68* serverErrorBackoffMiddleware(),69* ],70* });71*72* const config = new FetchedValue({73* fetch: fetchConfig,74* isStale: (c) => c.lastUpdated < Date.now() - 60_000,75* });76* ```77*/78export function createAdvancedFetch<T>(options: AdvancedFetchOptions<T>): () => Promise<T> {79const { httpFetch, parseResponse, middleware = [] } = options;80const composedFetch = composeFetchMiddleware(...middleware)(httpFetch);8182return async () => {83const request = typeof options.request === 'function' ? await options.request() : options.request;84const response = await composedFetch(request);85return parseResponse(response);86};87}888990