Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/shared-fetch-utils/common/advancedFetcher.ts
13397 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import type { FetchMiddleware, HttpFetchFn, HttpRequest, HttpResponse } from './fetchTypes';
7
8
/**
9
* Composes an array of middlewares into a single middleware. Middlewares
10
* execute left-to-right: the first element wraps the outermost layer.
11
*
12
* ```
13
* composeFetchMiddleware(a, b, c)(httpFetch)
14
* // equivalent to: a(b(c(httpFetch)))
15
* ```
16
*/
17
export function composeFetchMiddleware(...middlewares: readonly FetchMiddleware[]): FetchMiddleware {
18
return (baseFetch) => middlewares.reduceRight<HttpFetchFn>((next, mw) => mw(next), baseFetch);
19
}
20
21
// ── Factory ─────────────────────────────────────────────────────────────
22
23
export interface AdvancedFetchOptions<T> {
24
/**
25
* The HTTP request to send. May be a static object, a synchronous
26
* factory, or an async factory (useful when headers depend on runtime
27
* state such as an auth token).
28
*/
29
readonly request: HttpRequest | (() => HttpRequest | Promise<HttpRequest>);
30
31
/**
32
* The underlying HTTP transport. Callers typically bridge their own
33
* `IFetcherService` here.
34
*/
35
readonly httpFetch: HttpFetchFn;
36
37
/**
38
* Extracts the domain value `T` from a successful HTTP response.
39
*/
40
readonly parseResponse: (response: HttpResponse) => Promise<T>;
41
42
/**
43
* Middleware applied around the HTTP fetch in the order listed (first
44
* middleware is outermost).
45
*/
46
readonly middleware?: readonly FetchMiddleware[];
47
}
48
49
/**
50
* Creates a `() => Promise<T>` suitable for passing as the `fetch` option
51
* of {@link FetchedValue}.
52
*
53
* @example
54
* ```ts
55
* const fetchConfig = createAdvancedFetch<ConfigType>({
56
* request: { url: 'https://api.example.com/config', headers: {} },
57
* httpFetch: async (req) => {
58
* const res = await fetcherService.fetch(req.url, {
59
* callSite: 'configFetch',
60
* headers: req.headers,
61
* });
62
* return { status: res.status, headers: res.headers, body: res.body, json: () => res.json(), text: () => res.text() };
63
* },
64
* parseResponse: async (res) => await res.json() as ConfigType,
65
* middleware: [
66
* windowActiveMiddleware(envService),
67
* etagMiddleware(),
68
* authBlockedMiddleware(),
69
* serverErrorBackoffMiddleware(),
70
* ],
71
* });
72
*
73
* const config = new FetchedValue({
74
* fetch: fetchConfig,
75
* isStale: (c) => c.lastUpdated < Date.now() - 60_000,
76
* });
77
* ```
78
*/
79
export function createAdvancedFetch<T>(options: AdvancedFetchOptions<T>): () => Promise<T> {
80
const { httpFetch, parseResponse, middleware = [] } = options;
81
const composedFetch = composeFetchMiddleware(...middleware)(httpFetch);
82
83
return async () => {
84
const request = typeof options.request === 'function' ? await options.request() : options.request;
85
const response = await composedFetch(request);
86
return parseResponse(response);
87
};
88
}
89
90