@subsquid/http-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # @subsquid/util-internal-http-client
2
+
3
+ A small convenience layer on top of `fetch()` which adds
4
+
5
+ * `HttpClient` as a store for common request settings
6
+ * retries
7
+ * timeouts
8
+ * logging.
package/esm.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export declare function esm<T>(mod: string): Promise<T>
package/esm.js ADDED
@@ -0,0 +1,3 @@
1
+ exports.esm = function esm(mod) {
2
+ return import(mod)
3
+ }
package/lib/agent.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import * as http from 'http';
4
+ import * as https from 'https';
5
+ export interface AgentProvider {
6
+ getNativeAgent(url: string): http.Agent;
7
+ }
8
+ export declare const defaultAgentProvider: AgentProvider;
9
+ export declare class HttpAgent implements AgentProvider {
10
+ private options;
11
+ private http?;
12
+ private https?;
13
+ constructor(options: https.AgentOptions);
14
+ getNativeAgent(url: string): http.Agent;
15
+ close(): void;
16
+ }
17
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":";;AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,WAAW,aAAa;IAC1B,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,KAAK,CAAA;CAC1C;AAGD,eAAO,MAAM,oBAAoB,EAAE,aAQlC,CAAA;AAGD,qBAAa,SAAU,YAAW,aAAa;IAI/B,OAAO,CAAC,OAAO;IAH3B,OAAO,CAAC,IAAI,CAAC,CAAY;IACzB,OAAO,CAAC,KAAK,CAAC,CAAa;gBAEP,OAAO,EAAE,KAAK,CAAC,YAAY;IAE/C,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,KAAK;IAQvC,KAAK;CAIR"}
package/lib/agent.js ADDED
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.HttpAgent = exports.defaultAgentProvider = void 0;
27
+ const http = __importStar(require("http"));
28
+ const https = __importStar(require("https"));
29
+ exports.defaultAgentProvider = {
30
+ getNativeAgent(url) {
31
+ if (url.startsWith('https://')) {
32
+ return https.globalAgent;
33
+ }
34
+ else {
35
+ return http.globalAgent;
36
+ }
37
+ }
38
+ };
39
+ class HttpAgent {
40
+ constructor(options) {
41
+ this.options = options;
42
+ }
43
+ getNativeAgent(url) {
44
+ if (url.startsWith('https://')) {
45
+ return this.https || (this.https = new https.Agent(this.options));
46
+ }
47
+ else {
48
+ return this.http || (this.http = new http.Agent(this.options));
49
+ }
50
+ }
51
+ close() {
52
+ this.http?.destroy();
53
+ this.https?.destroy();
54
+ }
55
+ }
56
+ exports.HttpAgent = HttpAgent;
57
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA4B;AAC5B,6CAA8B;AAQjB,QAAA,oBAAoB,GAAkB;IAC/C,cAAc,CAAC,GAAW;QACtB,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE;YAC5B,OAAO,KAAK,CAAC,WAAW,CAAA;SAC3B;aAAM;YACH,OAAO,IAAI,CAAC,WAAW,CAAA;SAC1B;IACL,CAAC;CACJ,CAAA;AAGD,MAAa,SAAS;IAIlB,YAAoB,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;IAAG,CAAC;IAEnD,cAAc,CAAC,GAAW;QACtB,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE;YAC5B,OAAO,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAA;SACpE;aAAM;YACH,OAAO,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAA;SACjE;IACL,CAAC;IAED,KAAK;QACD,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,CAAA;QACpB,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,CAAA;IACzB,CAAC;CACJ;AAlBD,8BAkBC"}
package/lib/body.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export type HttpBody = Content | Json | Nothing;
2
+ interface Content {
3
+ content: string | Uint8Array;
4
+ json?: undefined;
5
+ }
6
+ interface Json {
7
+ content?: undefined;
8
+ json: object;
9
+ }
10
+ interface Nothing {
11
+ content?: undefined;
12
+ json?: undefined;
13
+ }
14
+ export {};
15
+ //# sourceMappingURL=body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,CAAA;AAG/C,UAAU,OAAO;IACb,OAAO,EAAE,MAAM,GAAG,UAAU,CAAA;IAC5B,IAAI,CAAC,EAAE,SAAS,CAAA;CACnB;AAGD,UAAU,IAAI;IACV,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACf;AAGD,UAAU,OAAO;IACb,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,IAAI,CAAC,EAAE,SAAS,CAAA;CACnB"}
package/lib/body.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body.js","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":""}
@@ -0,0 +1,101 @@
1
+ import type { Logger } from '@subsquid/logger';
2
+ import type { Headers, RequestInit, Response } from 'node-fetch';
3
+ import { AgentProvider } from './agent';
4
+ import { HttpBody } from './body';
5
+ export interface HttpClientOptions {
6
+ agent?: AgentProvider;
7
+ baseUrl?: string;
8
+ headers?: Record<string, string | number | bigint>;
9
+ /**
10
+ * Default request timeout in milliseconds.
11
+ *
12
+ * This timeout is only related to individual http requests.
13
+ * Overall request processing time might be much larger due to retries.
14
+ */
15
+ httpTimeout?: number;
16
+ retryAttempts?: number;
17
+ retrySchedule?: number[];
18
+ log?: Logger;
19
+ }
20
+ export interface RequestOptions {
21
+ query?: Record<string, string | number | bigint>;
22
+ headers?: HeadersInit;
23
+ retryAttempts?: number;
24
+ retrySchedule?: number[];
25
+ httpTimeout?: number;
26
+ abort?: AbortSignal;
27
+ }
28
+ export interface GraphqlRequestOptions extends RequestOptions {
29
+ variables?: Record<string, any>;
30
+ url?: string;
31
+ method?: 'GET' | 'POST';
32
+ }
33
+ export interface FetchRequest extends RequestInit {
34
+ id: number;
35
+ url: string;
36
+ headers: Headers;
37
+ timeout?: number;
38
+ signal?: AbortSignal;
39
+ }
40
+ export declare class HttpClient {
41
+ protected log?: Logger;
42
+ protected headers?: Record<string, string | number | bigint>;
43
+ private baseUrl?;
44
+ private agent;
45
+ private retrySchedule;
46
+ private retryAttempts;
47
+ private httpTimeout;
48
+ private requestCounter;
49
+ constructor(options?: HttpClientOptions);
50
+ get<T = any>(url: string, options?: RequestOptions): Promise<T>;
51
+ post<T = any>(url: string, options?: RequestOptions & HttpBody): Promise<T>;
52
+ request<T = any>(method: string, url: string, options?: RequestOptions & HttpBody): Promise<HttpResponse<T>>;
53
+ protected beforeRequest(req: FetchRequest): void;
54
+ protected beforeRetryPause(req: FetchRequest, reason: Error | HttpResponse, pause: number): void;
55
+ protected afterResponseHeaders(req: FetchRequest, url: string, status: number, headers: Headers): void;
56
+ protected afterResponse(req: FetchRequest, res: HttpResponse): void;
57
+ protected prepareRequest(method: string, url: string, options: RequestOptions & HttpBody): Promise<FetchRequest>;
58
+ private performRequestWithTimeout;
59
+ private performRequest;
60
+ protected handleResponseBody(req: FetchRequest, res: Response): Promise<any>;
61
+ isRetryableError(error: HttpResponse | Error, req?: FetchRequest): boolean;
62
+ getAbsUrl(url: string): string;
63
+ private setBaseUrl;
64
+ graphqlRequest<T = any>(gql: string, options?: GraphqlRequestOptions): Promise<T>;
65
+ }
66
+ export declare class HttpResponse<T = any> {
67
+ readonly requestId: number;
68
+ readonly url: string;
69
+ readonly status: number;
70
+ readonly headers: Headers;
71
+ readonly body: T;
72
+ constructor(requestId: number, url: string, status: number, headers: Headers, body: T);
73
+ get ok(): boolean;
74
+ assert(): void;
75
+ toJSON(): {
76
+ status: number;
77
+ headers: [string, string][];
78
+ body: T;
79
+ url: string;
80
+ };
81
+ }
82
+ export declare class HttpError extends Error {
83
+ readonly response: HttpResponse;
84
+ constructor(response: HttpResponse);
85
+ get name(): string;
86
+ }
87
+ export declare class HttpTimeoutError extends Error {
88
+ constructor(ms: number);
89
+ get name(): string;
90
+ }
91
+ export interface GraphqlMessage {
92
+ message: string;
93
+ path?: (string | number)[];
94
+ }
95
+ export declare class GraphqlError extends Error {
96
+ readonly messages: GraphqlMessage[];
97
+ constructor(messages: GraphqlMessage[]);
98
+ get name(): string;
99
+ }
100
+ export declare function isHttpConnectionError(err: unknown): boolean;
101
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/lib/client.js ADDED
@@ -0,0 +1,341 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isHttpConnectionError = exports.GraphqlError = exports.HttpTimeoutError = exports.HttpError = exports.HttpResponse = exports.HttpClient = void 0;
4
+ const util_internal_1 = require("@subsquid/util-internal");
5
+ const agent_1 = require("./agent");
6
+ const request_1 = require("./request");
7
+ class HttpClient {
8
+ constructor(options = {}) {
9
+ this.requestCounter = 0;
10
+ this.log = options.log;
11
+ this.headers = options.headers;
12
+ this.setBaseUrl(options.baseUrl);
13
+ this.agent = options.agent || agent_1.defaultAgentProvider;
14
+ this.retrySchedule = options.retrySchedule || [10, 100, 500, 2000, 10000, 20000];
15
+ this.retryAttempts = options.retryAttempts || 0;
16
+ this.httpTimeout = options.httpTimeout ?? 20000;
17
+ }
18
+ get(url, options) {
19
+ return this.request('GET', url, options).then(res => res.body);
20
+ }
21
+ post(url, options) {
22
+ return this.request('POST', url, options).then(res => res.body);
23
+ }
24
+ async request(method, url, options = {}) {
25
+ let req = await this.prepareRequest(method, url, options);
26
+ this.beforeRequest(req);
27
+ let retryAttempts = options.retryAttempts ?? this.retryAttempts;
28
+ let retrySchedule = options.retrySchedule ?? this.retrySchedule;
29
+ let retries = 0;
30
+ while (true) {
31
+ let res = await this.performRequestWithTimeout(req).catch(util_internal_1.ensureError);
32
+ if (res instanceof Error || !res.ok) {
33
+ if (retryAttempts > retries && this.isRetryableError(res, req)) {
34
+ let pause = retrySchedule.length
35
+ ? retrySchedule[Math.min(retries, retrySchedule.length - 1)]
36
+ : 1000;
37
+ retries += 1;
38
+ this.beforeRetryPause(req, res, pause);
39
+ await (0, util_internal_1.wait)(pause, req.signal);
40
+ }
41
+ else if (res instanceof Error) {
42
+ throw (0, util_internal_1.addErrorContext)(res, { httpRequestId: req.id });
43
+ }
44
+ else {
45
+ throw new HttpError(res);
46
+ }
47
+ }
48
+ else {
49
+ return res;
50
+ }
51
+ }
52
+ }
53
+ beforeRequest(req) {
54
+ if (this.log?.isDebug()) {
55
+ this.log.debug({
56
+ httpRequestId: req.id,
57
+ httpRequestUrl: req.url,
58
+ httpRequestMethod: req.method,
59
+ httpRequestHeaders: Array.from(req.headers),
60
+ httpRequestBody: req.body
61
+ }, 'http request');
62
+ }
63
+ }
64
+ beforeRetryPause(req, reason, pause) {
65
+ if (this.log?.isWarn()) {
66
+ let info = {
67
+ httpRequestId: req.id,
68
+ httpRequestUrl: req.url,
69
+ httpRequestMethod: req.method
70
+ };
71
+ if (reason instanceof Error) {
72
+ info.reason = reason.toString();
73
+ }
74
+ else {
75
+ info.reason = `got ${reason.status}`;
76
+ info.httpResponseUrl = reason.url;
77
+ info.httpResponseStatus = reason.status;
78
+ info.httpResponseHeaders = Array.from(reason.headers);
79
+ info.httpResponseBody = reason.body;
80
+ }
81
+ this.log.warn(info, `request will be retried in ${pause} ms`);
82
+ }
83
+ }
84
+ afterResponseHeaders(req, url, status, headers) {
85
+ if (this.log?.isDebug()) {
86
+ this.log.debug({
87
+ httpRequestId: req.id,
88
+ httpResponseUrl: url,
89
+ httpResponseStatus: status,
90
+ httpResponseHeaders: headers
91
+ }, 'http headers');
92
+ }
93
+ }
94
+ afterResponse(req, res) {
95
+ if (this.log?.isDebug()) {
96
+ let httpResponseBody = res.body;
97
+ if (typeof res.body == 'string' || res.body instanceof Uint8Array) {
98
+ if (res.body.length > 1024 * 1024) {
99
+ httpResponseBody = '...body is too long to be logged';
100
+ }
101
+ }
102
+ this.log.debug({
103
+ httpRequestId: req.id,
104
+ httpResponseBody
105
+ }, 'http body');
106
+ }
107
+ }
108
+ async prepareRequest(method, url, options) {
109
+ await request_1.nodeFetch.load();
110
+ let req = {
111
+ id: this.requestCounter++,
112
+ method,
113
+ headers: new request_1.nodeFetch.Headers(options.headers),
114
+ url: this.getAbsUrl(url),
115
+ signal: options.abort,
116
+ compress: true,
117
+ timeout: options.httpTimeout ?? this.httpTimeout
118
+ };
119
+ if (options.query) {
120
+ let qs = new URLSearchParams(options.query).toString();
121
+ if (req.url.includes('?')) {
122
+ req.url += '&' + qs;
123
+ }
124
+ else {
125
+ req.url += '?' + qs;
126
+ }
127
+ }
128
+ req.agent = this.agent.getNativeAgent(req.url);
129
+ if (options.content !== undefined) {
130
+ if (typeof options.content == 'string') {
131
+ req.body = options.content;
132
+ if (!req.headers.has('content-type')) {
133
+ req.headers.set('content-type', 'text/plain');
134
+ }
135
+ }
136
+ else if (Buffer.isBuffer(options.content)) {
137
+ req.body = options.content;
138
+ }
139
+ else {
140
+ req.body = Buffer.from(options.content.buffer, options.content.byteOffset, options.content.byteLength);
141
+ }
142
+ }
143
+ if (options.json !== undefined) {
144
+ req.body = JSON.stringify(options.json);
145
+ if (!req.headers.has('content-type')) {
146
+ req.headers.set('content-type', 'application/json');
147
+ }
148
+ }
149
+ for (let name in this.headers) {
150
+ if (!req.headers.has(name)) {
151
+ req.headers.set(name, '' + this.headers[name]);
152
+ }
153
+ }
154
+ return req;
155
+ }
156
+ async performRequestWithTimeout(req) {
157
+ if (!req.timeout)
158
+ return this.performRequest(req);
159
+ let ac = new AbortController();
160
+ function abort() {
161
+ ac.abort();
162
+ }
163
+ req.signal?.addEventListener('abort', abort);
164
+ let timer = setTimeout(() => {
165
+ timer = null;
166
+ abort();
167
+ }, req.timeout);
168
+ try {
169
+ return await this.performRequest({ ...req, signal: ac.signal });
170
+ }
171
+ catch (err) {
172
+ if (timer == null) {
173
+ throw new HttpTimeoutError(req.timeout);
174
+ }
175
+ else {
176
+ throw err;
177
+ }
178
+ }
179
+ finally {
180
+ if (timer != null) {
181
+ clearTimeout(timer);
182
+ }
183
+ req.signal?.removeEventListener('abort', abort);
184
+ }
185
+ }
186
+ async performRequest(req) {
187
+ let res = await request_1.nodeFetch.request(req.url, req);
188
+ this.afterResponseHeaders(req, res.url, res.status, res.headers);
189
+ let body = await this.handleResponseBody(req, res);
190
+ let httpResponse = new HttpResponse(req.id, res.url, res.status, res.headers, body);
191
+ this.afterResponse(req, httpResponse);
192
+ return httpResponse;
193
+ }
194
+ async handleResponseBody(req, res) {
195
+ let contentType = (res.headers.get('content-type') || '').split(';')[0];
196
+ if (contentType == 'application/json') {
197
+ return res.json();
198
+ }
199
+ if (contentType.startsWith('text/')) {
200
+ return res.text();
201
+ }
202
+ let arrayBuffer = await res.arrayBuffer();
203
+ let bytes = Buffer.from(arrayBuffer);
204
+ if (bytes.length == 0)
205
+ return undefined;
206
+ return bytes;
207
+ }
208
+ isRetryableError(error, req) {
209
+ if (isHttpConnectionError(error))
210
+ return true;
211
+ if (error instanceof HttpTimeoutError)
212
+ return true;
213
+ if (error instanceof HttpResponse) {
214
+ switch (error.status) {
215
+ case 429:
216
+ case 502:
217
+ case 503:
218
+ case 504:
219
+ return true;
220
+ default:
221
+ return false;
222
+ }
223
+ }
224
+ return false;
225
+ }
226
+ getAbsUrl(url) {
227
+ if (!this.baseUrl)
228
+ return url;
229
+ if (url.includes('://'))
230
+ return url;
231
+ if (url == '/')
232
+ return this.baseUrl;
233
+ if (url[0] == '/')
234
+ return this.baseUrl + url;
235
+ return this.baseUrl + '/' + url;
236
+ }
237
+ setBaseUrl(url) {
238
+ if (url) {
239
+ let u = new URL(url);
240
+ u.hash = '';
241
+ u.search = '';
242
+ url = u.toString();
243
+ if (url.endsWith('/')) {
244
+ url = url.slice(0, url.length - 1);
245
+ }
246
+ this.baseUrl = url;
247
+ }
248
+ else {
249
+ this.baseUrl = undefined;
250
+ }
251
+ }
252
+ async graphqlRequest(gql, options = {}) {
253
+ let { method = 'POST', url = '/', variables, ...reqOptions } = options;
254
+ let req = reqOptions;
255
+ if (method == 'GET') {
256
+ req.query = {
257
+ ...req.query,
258
+ query: gql,
259
+ };
260
+ if (variables) {
261
+ req.query.variables = JSON.stringify(variables);
262
+ }
263
+ }
264
+ else {
265
+ req.json = {
266
+ query: gql,
267
+ variables
268
+ };
269
+ }
270
+ let res = await this.request(method, url, req);
271
+ if (res.body.errors?.length) {
272
+ throw new GraphqlError(res.body.errors);
273
+ }
274
+ else {
275
+ return res.body.data;
276
+ }
277
+ }
278
+ }
279
+ exports.HttpClient = HttpClient;
280
+ class HttpResponse {
281
+ constructor(requestId, url, status, headers, body) {
282
+ this.requestId = requestId;
283
+ this.url = url;
284
+ this.status = status;
285
+ this.headers = headers;
286
+ this.body = body;
287
+ }
288
+ get ok() {
289
+ return this.status >= 200 && this.status < 300;
290
+ }
291
+ assert() {
292
+ if (this.ok)
293
+ return;
294
+ throw new HttpError(this);
295
+ }
296
+ toJSON() {
297
+ return {
298
+ status: this.status,
299
+ headers: Array.from(this.headers),
300
+ body: this.body,
301
+ url: this.url
302
+ };
303
+ }
304
+ }
305
+ exports.HttpResponse = HttpResponse;
306
+ class HttpError extends Error {
307
+ constructor(response) {
308
+ super(`Got ${response.status} from ${response.url}`);
309
+ this.response = response;
310
+ }
311
+ get name() {
312
+ return 'HttpError';
313
+ }
314
+ }
315
+ exports.HttpError = HttpError;
316
+ class HttpTimeoutError extends Error {
317
+ constructor(ms) {
318
+ super(`request timed out after ${ms} ms`);
319
+ }
320
+ get name() {
321
+ return 'HttpTimeoutError';
322
+ }
323
+ }
324
+ exports.HttpTimeoutError = HttpTimeoutError;
325
+ class GraphqlError extends Error {
326
+ constructor(messages) {
327
+ super(`GraphQL error: ${messages[0].message}`);
328
+ this.messages = messages;
329
+ }
330
+ get name() {
331
+ return 'GraphqlError';
332
+ }
333
+ }
334
+ exports.GraphqlError = GraphqlError;
335
+ function isHttpConnectionError(err) {
336
+ return err instanceof request_1.nodeFetch.FetchError
337
+ && err.type == 'system'
338
+ && (err.message.startsWith('request to') || err.code == 'ERR_STREAM_PREMATURE_CLOSE');
339
+ }
340
+ exports.isHttpConnectionError = isHttpConnectionError;
341
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './client';
2
+ export { AgentProvider, HttpAgent } from './agent';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,OAAO,EAAC,aAAa,EAAE,SAAS,EAAC,MAAM,SAAS,CAAA"}
package/lib/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.HttpAgent = void 0;
18
+ __exportStar(require("./client"), exports);
19
+ var agent_1 = require("./agent");
20
+ Object.defineProperty(exports, "HttpAgent", { enumerable: true, get: function () { return agent_1.HttpAgent; } });
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2CAAwB;AACxB,iCAAgD;AAAzB,kGAAA,SAAS,OAAA"}
@@ -0,0 +1,8 @@
1
+ import type { RequestInit, Response } from 'node-fetch';
2
+ export declare const nodeFetch: {
3
+ load(): Promise<typeof import('node-fetch')>;
4
+ readonly Headers: typeof import("node-fetch").Headers;
5
+ readonly FetchError: typeof import("node-fetch").FetchError;
6
+ request(url: string, init?: RequestInit): Promise<Response>;
7
+ };
8
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,WAAW,EAAE,QAAQ,EAAC,MAAM,YAAY,CAAA;AAOrD,eAAO,MAAM,SAAS;YACJ,QAAQ,cAAc,YAAY,CAAC,CAAC;;;iBAY/B,MAAM,SAAS,WAAW,GAAG,QAAQ,QAAQ,CAAC;CAIpE,CAAA"}
package/lib/request.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.nodeFetch = void 0;
7
+ const assert_1 = __importDefault(require("assert"));
8
+ const esm_1 = require("../esm");
9
+ let mod;
10
+ exports.nodeFetch = {
11
+ async load() {
12
+ if (mod)
13
+ return mod;
14
+ return mod = await (0, esm_1.esm)('node-fetch');
15
+ },
16
+ get Headers() {
17
+ (0, assert_1.default)(mod, 'node-fetch ESM is not loaded');
18
+ return mod.Headers;
19
+ },
20
+ get FetchError() {
21
+ (0, assert_1.default)(mod, 'node-fetch ESM is not loaded');
22
+ return mod.FetchError;
23
+ },
24
+ async request(url, init) {
25
+ let m = await exports.nodeFetch.load();
26
+ return m.default(url, init);
27
+ }
28
+ };
29
+ //# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA2B;AAE3B,gCAA0B;AAG1B,IAAI,GAA4C,CAAA;AAGnC,QAAA,SAAS,GAAG;IACrB,KAAK,CAAC,IAAI;QACN,IAAI,GAAG;YAAE,OAAO,GAAG,CAAA;QACnB,OAAO,GAAG,GAAG,MAAM,IAAA,SAAG,EAA+B,YAAY,CAAC,CAAA;IACtE,CAAC;IACD,IAAI,OAAO;QACP,IAAA,gBAAM,EAAC,GAAG,EAAE,8BAA8B,CAAC,CAAA;QAC3C,OAAO,GAAG,CAAC,OAAO,CAAA;IACtB,CAAC;IACD,IAAI,UAAU;QACV,IAAA,gBAAM,EAAC,GAAG,EAAE,8BAA8B,CAAC,CAAA;QAC3C,OAAO,GAAG,CAAC,UAAU,CAAA;IACzB,CAAC;IACD,KAAK,CAAC,OAAO,CAAC,GAAW,EAAE,IAAkB;QACzC,IAAI,CAAC,GAAG,MAAM,iBAAS,CAAC,IAAI,EAAE,CAAA;QAC9B,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IAC/B,CAAC;CACJ,CAAA"}
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@subsquid/http-client",
3
+ "version": "1.0.0",
4
+ "description": "Small convenience layer on top of fetch",
5
+ "license": "GPL-3.0-or-later",
6
+ "repository": "git@github.com:subsquid/squid.git",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "main": "lib/index.js",
11
+ "files": [
12
+ "lib",
13
+ "src",
14
+ "esm.d.ts",
15
+ "esm.js"
16
+ ],
17
+ "dependencies": {
18
+ "@subsquid/logger": "^1.0.0",
19
+ "@subsquid/util-internal": "^2.0.0",
20
+ "node-fetch": "^3.3.1"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^16.18.25",
24
+ "typescript": "~4.9.5"
25
+ },
26
+ "scripts": {
27
+ "build": "rm -rf lib && tsc"
28
+ },
29
+ "readme": "# @subsquid/util-internal-http-client\n\nA small convenience layer on top of `fetch()` which adds\n\n* `HttpClient` as a store for common request settings\n* retries\n* timeouts\n* logging.\n"
30
+ }
package/src/agent.ts ADDED
@@ -0,0 +1,39 @@
1
+ import * as http from 'http'
2
+ import * as https from 'https'
3
+
4
+
5
+ export interface AgentProvider {
6
+ getNativeAgent(url: string): http.Agent
7
+ }
8
+
9
+
10
+ export const defaultAgentProvider: AgentProvider = {
11
+ getNativeAgent(url: string): http.Agent {
12
+ if (url.startsWith('https://')) {
13
+ return https.globalAgent
14
+ } else {
15
+ return http.globalAgent
16
+ }
17
+ }
18
+ }
19
+
20
+
21
+ export class HttpAgent implements AgentProvider {
22
+ private http?: http.Agent
23
+ private https?: https.Agent
24
+
25
+ constructor(private options: https.AgentOptions) {}
26
+
27
+ getNativeAgent(url: string): http.Agent {
28
+ if (url.startsWith('https://')) {
29
+ return this.https || (this.https = new https.Agent(this.options))
30
+ } else {
31
+ return this.http || (this.http = new http.Agent(this.options))
32
+ }
33
+ }
34
+
35
+ close() {
36
+ this.http?.destroy()
37
+ this.https?.destroy()
38
+ }
39
+ }
package/src/body.ts ADDED
@@ -0,0 +1,20 @@
1
+
2
+ export type HttpBody = Content | Json | Nothing
3
+
4
+
5
+ interface Content {
6
+ content: string | Uint8Array
7
+ json?: undefined
8
+ }
9
+
10
+
11
+ interface Json {
12
+ content?: undefined
13
+ json: object
14
+ }
15
+
16
+
17
+ interface Nothing {
18
+ content?: undefined
19
+ json?: undefined
20
+ }
package/src/client.ts ADDED
@@ -0,0 +1,427 @@
1
+ import type {Logger} from '@subsquid/logger'
2
+ import {addErrorContext, ensureError, wait} from '@subsquid/util-internal'
3
+ import type {Headers, RequestInit, Response} from 'node-fetch'
4
+ import {AgentProvider, defaultAgentProvider} from './agent'
5
+ import {HttpBody} from './body'
6
+ import {nodeFetch} from './request'
7
+
8
+
9
+ export interface HttpClientOptions {
10
+ agent?: AgentProvider
11
+ baseUrl?: string
12
+ headers?: Record<string, string | number | bigint>
13
+ /**
14
+ * Default request timeout in milliseconds.
15
+ *
16
+ * This timeout is only related to individual http requests.
17
+ * Overall request processing time might be much larger due to retries.
18
+ */
19
+ httpTimeout?: number
20
+ retryAttempts?: number
21
+ retrySchedule?: number[]
22
+ log?: Logger
23
+ }
24
+
25
+
26
+ export interface RequestOptions {
27
+ query?: Record<string, string | number | bigint>
28
+ headers?: HeadersInit
29
+ retryAttempts?: number
30
+ retrySchedule?: number[]
31
+ httpTimeout?: number
32
+ abort?: AbortSignal
33
+ }
34
+
35
+
36
+ export interface GraphqlRequestOptions extends RequestOptions {
37
+ variables?: Record<string, any>
38
+ url?: string
39
+ method?: 'GET' | 'POST'
40
+ }
41
+
42
+
43
+ export interface FetchRequest extends RequestInit {
44
+ id: number
45
+ url: string
46
+ headers: Headers
47
+ timeout?: number
48
+ signal?: AbortSignal
49
+ }
50
+
51
+
52
+ export class HttpClient {
53
+ protected log?: Logger
54
+ protected headers?: Record<string, string | number | bigint>
55
+ private baseUrl?: string
56
+ private agent: AgentProvider
57
+ private retrySchedule: number[]
58
+ private retryAttempts: number
59
+ private httpTimeout: number
60
+ private requestCounter = 0
61
+
62
+ constructor(options: HttpClientOptions = {}) {
63
+ this.log = options.log
64
+ this.headers = options.headers
65
+ this.setBaseUrl(options.baseUrl)
66
+ this.agent = options.agent || defaultAgentProvider
67
+ this.retrySchedule = options.retrySchedule || [10, 100, 500, 2000, 10000, 20000]
68
+ this.retryAttempts = options.retryAttempts || 0
69
+ this.httpTimeout = options.httpTimeout ?? 20000
70
+ }
71
+
72
+ get<T=any>(url: string, options?: RequestOptions): Promise<T> {
73
+ return this.request('GET', url, options).then(res => res.body)
74
+ }
75
+
76
+ post<T=any>(url: string, options?: RequestOptions & HttpBody): Promise<T> {
77
+ return this.request('POST', url, options).then(res => res.body)
78
+ }
79
+
80
+ async request<T=any>(
81
+ method: string,
82
+ url: string,
83
+ options: RequestOptions & HttpBody = {},
84
+ ): Promise<HttpResponse<T>> {
85
+ let req = await this.prepareRequest(method, url, options)
86
+
87
+ this.beforeRequest(req)
88
+
89
+ let retryAttempts = options.retryAttempts ?? this.retryAttempts
90
+ let retrySchedule = options.retrySchedule ?? this.retrySchedule
91
+ let retries = 0
92
+
93
+ while (true) {
94
+ let res: HttpResponse | Error = await this.performRequestWithTimeout(req).catch(ensureError)
95
+ if (res instanceof Error || !res.ok) {
96
+ if (retryAttempts > retries && this.isRetryableError(res, req)) {
97
+ let pause = retrySchedule.length
98
+ ? retrySchedule[Math.min(retries, retrySchedule.length - 1)]
99
+ : 1000
100
+ retries += 1
101
+ this.beforeRetryPause(req, res, pause)
102
+ await wait(pause, req.signal)
103
+ } else if (res instanceof Error) {
104
+ throw addErrorContext(res, {httpRequestId: req.id})
105
+ } else {
106
+ throw new HttpError(res)
107
+ }
108
+ } else {
109
+ return res
110
+ }
111
+ }
112
+ }
113
+
114
+ protected beforeRequest(req: FetchRequest): void {
115
+ if (this.log?.isDebug()) {
116
+ this.log.debug({
117
+ httpRequestId: req.id,
118
+ httpRequestUrl: req.url,
119
+ httpRequestMethod: req.method,
120
+ httpRequestHeaders: Array.from(req.headers),
121
+ httpRequestBody: req.body
122
+ }, 'http request')
123
+ }
124
+ }
125
+
126
+ protected beforeRetryPause(req: FetchRequest, reason: Error | HttpResponse, pause: number): void {
127
+ if (this.log?.isWarn()) {
128
+ let info: any = {
129
+ httpRequestId: req.id,
130
+ httpRequestUrl: req.url,
131
+ httpRequestMethod: req.method
132
+ }
133
+ if (reason instanceof Error) {
134
+ info.reason = reason.toString()
135
+ } else {
136
+ info.reason = `got ${reason.status}`
137
+ info.httpResponseUrl = reason.url
138
+ info.httpResponseStatus = reason.status
139
+ info.httpResponseHeaders = Array.from(reason.headers)
140
+ info.httpResponseBody = reason.body
141
+ }
142
+ this.log.warn(info, `request will be retried in ${pause} ms`)
143
+ }
144
+ }
145
+
146
+ protected afterResponseHeaders(req: FetchRequest, url: string, status: number, headers: Headers): void {
147
+ if (this.log?.isDebug()) {
148
+ this.log.debug({
149
+ httpRequestId: req.id,
150
+ httpResponseUrl: url,
151
+ httpResponseStatus: status,
152
+ httpResponseHeaders: headers
153
+ }, 'http headers')
154
+ }
155
+ }
156
+
157
+ protected afterResponse(req: FetchRequest, res: HttpResponse): void {
158
+ if (this.log?.isDebug()) {
159
+ let httpResponseBody: any = res.body
160
+ if (typeof res.body == 'string' || res.body instanceof Uint8Array) {
161
+ if (res.body.length > 1024 * 1024) {
162
+ httpResponseBody = '...body is too long to be logged'
163
+ }
164
+ }
165
+ this.log.debug({
166
+ httpRequestId: req.id,
167
+ httpResponseBody
168
+ }, 'http body')
169
+ }
170
+ }
171
+
172
+ protected async prepareRequest(
173
+ method: string,
174
+ url: string,
175
+ options: RequestOptions & HttpBody
176
+ ): Promise<FetchRequest> {
177
+ await nodeFetch.load()
178
+
179
+ let req: FetchRequest = {
180
+ id: this.requestCounter++,
181
+ method,
182
+ headers: new nodeFetch.Headers(options.headers),
183
+ url: this.getAbsUrl(url),
184
+ signal: options.abort,
185
+ compress: true,
186
+ timeout: options.httpTimeout ?? this.httpTimeout
187
+ }
188
+
189
+ if (options.query) {
190
+ let qs = new URLSearchParams(options.query as any).toString()
191
+ if (req.url.includes('?')) {
192
+ req.url += '&' + qs
193
+ } else {
194
+ req.url += '?' + qs
195
+ }
196
+ }
197
+
198
+ req.agent = this.agent.getNativeAgent(req.url)
199
+
200
+ if (options.content !== undefined) {
201
+ if (typeof options.content == 'string') {
202
+ req.body = options.content
203
+ if (!req.headers.has('content-type')) {
204
+ req.headers.set('content-type', 'text/plain')
205
+ }
206
+ } else if (Buffer.isBuffer(options.content)) {
207
+ req.body = options.content
208
+ } else {
209
+ req.body = Buffer.from(options.content.buffer, options.content.byteOffset, options.content.byteLength)
210
+ }
211
+ }
212
+
213
+ if (options.json !== undefined) {
214
+ req.body = JSON.stringify(options.json)
215
+ if (!req.headers.has('content-type')) {
216
+ req.headers.set('content-type', 'application/json')
217
+ }
218
+ }
219
+
220
+ for (let name in this.headers) {
221
+ if (!req.headers.has(name)) {
222
+ req.headers.set(name, ''+this.headers[name])
223
+ }
224
+ }
225
+
226
+ return req
227
+ }
228
+
229
+ private async performRequestWithTimeout(req: FetchRequest): Promise<HttpResponse> {
230
+ if (!req.timeout) return this.performRequest(req)
231
+
232
+ let ac = new AbortController()
233
+
234
+ function abort() {
235
+ ac.abort()
236
+ }
237
+
238
+ req.signal?.addEventListener('abort', abort)
239
+
240
+ let timer: any | null = setTimeout(() => {
241
+ timer = null
242
+ abort()
243
+ }, req.timeout)
244
+
245
+ try {
246
+ return await this.performRequest({...req, signal: ac.signal})
247
+ } catch(err: any) {
248
+ if (timer == null) {
249
+ throw new HttpTimeoutError(req.timeout)
250
+ } else {
251
+ throw err
252
+ }
253
+ } finally {
254
+ if (timer != null) {
255
+ clearTimeout(timer)
256
+ }
257
+ req.signal?.removeEventListener('abort', abort)
258
+ }
259
+ }
260
+
261
+ private async performRequest(req: FetchRequest): Promise<HttpResponse> {
262
+ let res = await nodeFetch.request(req.url, req)
263
+ this.afterResponseHeaders(req, res.url, res.status, res.headers)
264
+ let body = await this.handleResponseBody(req, res)
265
+ let httpResponse = new HttpResponse(req.id, res.url, res.status, res.headers, body)
266
+ this.afterResponse(req, httpResponse)
267
+ return httpResponse
268
+ }
269
+
270
+ protected async handleResponseBody(req: FetchRequest, res: Response): Promise<any> {
271
+ let contentType = (res.headers.get('content-type') || '').split(';')[0]
272
+
273
+ if (contentType == 'application/json') {
274
+ return res.json()
275
+ }
276
+
277
+ if (contentType.startsWith('text/')) {
278
+ return res.text()
279
+ }
280
+
281
+ let arrayBuffer = await res.arrayBuffer()
282
+ let bytes = Buffer.from(arrayBuffer)
283
+ if (bytes.length == 0) return undefined
284
+ return bytes
285
+ }
286
+
287
+ isRetryableError(error: HttpResponse | Error, req?: FetchRequest): boolean {
288
+ if (isHttpConnectionError(error)) return true
289
+ if (error instanceof HttpTimeoutError) return true
290
+ if (error instanceof HttpResponse) {
291
+ switch(error.status) {
292
+ case 429:
293
+ case 502:
294
+ case 503:
295
+ case 504:
296
+ return true
297
+ default:
298
+ return false
299
+ }
300
+ }
301
+ return false
302
+ }
303
+
304
+ getAbsUrl(url: string): string {
305
+ if (!this.baseUrl) return url
306
+ if (url.includes('://')) return url
307
+ if (url == '/') return this.baseUrl
308
+ if (url[0] == '/') return this.baseUrl + url
309
+ return this.baseUrl + '/' + url
310
+ }
311
+
312
+ private setBaseUrl(url?: string): void {
313
+ if (url) {
314
+ let u = new URL(url)
315
+ u.hash = ''
316
+ u.search = ''
317
+ url = u.toString()
318
+ if (url.endsWith('/')) {
319
+ url = url.slice(0, url.length - 1)
320
+ }
321
+ this.baseUrl = url
322
+ } else {
323
+ this.baseUrl = undefined
324
+ }
325
+ }
326
+
327
+ async graphqlRequest<T=any>(gql: string, options: GraphqlRequestOptions = {}): Promise<T> {
328
+ let {method = 'POST', url = '/', variables, ...reqOptions} = options
329
+ let req: RequestOptions & {json?: any} = reqOptions
330
+ if (method == 'GET') {
331
+ req.query = {
332
+ ...req.query,
333
+ query: gql,
334
+ }
335
+ if (variables) {
336
+ req.query.variables = JSON.stringify(variables)
337
+ }
338
+ } else {
339
+ req.json = {
340
+ query: gql,
341
+ variables
342
+ }
343
+ }
344
+ let res = await this.request<{data: T, errors?: GraphqlMessage[]}>(method, url, req)
345
+ if (res.body.errors?.length) {
346
+ throw new GraphqlError(res.body.errors)
347
+ } else {
348
+ return res.body.data
349
+ }
350
+ }
351
+ }
352
+
353
+
354
+ export class HttpResponse<T=any> {
355
+ constructor(
356
+ public readonly requestId: number,
357
+ public readonly url: string,
358
+ public readonly status: number,
359
+ public readonly headers: Headers,
360
+ public readonly body: T
361
+ ) {
362
+ }
363
+
364
+ get ok(): boolean {
365
+ return this.status >= 200 && this.status < 300
366
+ }
367
+
368
+ assert(): void {
369
+ if (this.ok) return
370
+ throw new HttpError(this)
371
+ }
372
+
373
+ toJSON() {
374
+ return {
375
+ status: this.status,
376
+ headers: Array.from(this.headers),
377
+ body: this.body,
378
+ url: this.url
379
+ }
380
+ }
381
+ }
382
+
383
+
384
+ export class HttpError extends Error {
385
+ constructor(public readonly response: HttpResponse) {
386
+ super(`Got ${response.status} from ${response.url}`)
387
+ }
388
+
389
+ get name(): string {
390
+ return 'HttpError'
391
+ }
392
+ }
393
+
394
+
395
+ export class HttpTimeoutError extends Error {
396
+ constructor(ms: number) {
397
+ super(`request timed out after ${ms} ms`)
398
+ }
399
+
400
+ get name(): string {
401
+ return 'HttpTimeoutError'
402
+ }
403
+ }
404
+
405
+
406
+ export interface GraphqlMessage {
407
+ message: string
408
+ path?: (string | number)[]
409
+ }
410
+
411
+
412
+ export class GraphqlError extends Error {
413
+ constructor(public readonly messages: GraphqlMessage[]) {
414
+ super(`GraphQL error: ${messages[0].message}`)
415
+ }
416
+
417
+ get name(): string {
418
+ return 'GraphqlError'
419
+ }
420
+ }
421
+
422
+
423
+ export function isHttpConnectionError(err: unknown): boolean {
424
+ return err instanceof nodeFetch.FetchError
425
+ && err.type == 'system'
426
+ && (err.message.startsWith('request to') || err.code == 'ERR_STREAM_PREMATURE_CLOSE')
427
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './client'
2
+ export {AgentProvider, HttpAgent} from './agent'
package/src/request.ts ADDED
@@ -0,0 +1,26 @@
1
+ import assert from 'assert'
2
+ import type {RequestInit, Response} from 'node-fetch'
3
+ import {esm} from '../esm'
4
+
5
+
6
+ let mod: typeof import('node-fetch') | undefined
7
+
8
+
9
+ export const nodeFetch = {
10
+ async load(): Promise<typeof import('node-fetch')> {
11
+ if (mod) return mod
12
+ return mod = await esm<typeof import('node-fetch') >('node-fetch')
13
+ },
14
+ get Headers() {
15
+ assert(mod, 'node-fetch ESM is not loaded')
16
+ return mod.Headers
17
+ },
18
+ get FetchError() {
19
+ assert(mod, 'node-fetch ESM is not loaded')
20
+ return mod.FetchError
21
+ },
22
+ async request(url: string, init?: RequestInit): Promise<Response> {
23
+ let m = await nodeFetch.load()
24
+ return m.default(url, init)
25
+ }
26
+ }