@tramvai/module-metrics 6.64.1 → 6.65.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.
@@ -41,6 +41,16 @@ class MetricsServicesRegistry {
41
41
  if (!url) {
42
42
  return undefined;
43
43
  }
44
+ // In undici diagnostic_channel headers is flat string list
45
+ // ['header', 'value', 'header2', 'value2']
46
+ if (Array.isArray(options?.headers)) {
47
+ const headerIndex = options.headers.findIndex((item) => item === SERVICE_NAME_HEADER);
48
+ if (headerIndex !== -1) {
49
+ const serviceName = options.headers[headerIndex + 1];
50
+ options.headers.splice(headerIndex, 2);
51
+ return serviceName;
52
+ }
53
+ }
44
54
  const serviceFromHeaders = typeof options?.headers?.get === 'function'
45
55
  ? options.headers.get(SERVICE_NAME_HEADER)
46
56
  : options?.headers?.[SERVICE_NAME_HEADER];
@@ -59,7 +69,7 @@ class MetricsServicesRegistry {
59
69
  if (!treeValue) {
60
70
  return undefined;
61
71
  }
62
- // Когда урл запросен из регистри, мы не знаем был ли он добавлен с протоколом, поэтому
72
+ // Когда урл запрошен из регистри, мы не знаем был ли он добавлен с протоколом, поэтому
63
73
  // проверяем оба варианта
64
74
  return treeValue[protocol] || treeValue[NO_PROTOCOL];
65
75
  }
@@ -49,6 +49,16 @@ class MetricsServicesRegistry {
49
49
  if (!url) {
50
50
  return undefined;
51
51
  }
52
+ // In undici diagnostic_channel headers is flat string list
53
+ // ['header', 'value', 'header2', 'value2']
54
+ if (Array.isArray(options?.headers)) {
55
+ const headerIndex = options.headers.findIndex((item) => item === SERVICE_NAME_HEADER);
56
+ if (headerIndex !== -1) {
57
+ const serviceName = options.headers[headerIndex + 1];
58
+ options.headers.splice(headerIndex, 2);
59
+ return serviceName;
60
+ }
61
+ }
52
62
  const serviceFromHeaders = typeof options?.headers?.get === 'function'
53
63
  ? options.headers.get(SERVICE_NAME_HEADER)
54
64
  : options?.headers?.[SERVICE_NAME_HEADER];
@@ -67,7 +77,7 @@ class MetricsServicesRegistry {
67
77
  if (!treeValue) {
68
78
  return undefined;
69
79
  }
70
- // Когда урл запросен из регистри, мы не знаем был ли он добавлен с протоколом, поэтому
80
+ // Когда урл запрошен из регистри, мы не знаем был ли он добавлен с протоколом, поэтому
71
81
  // проверяем оба варианта
72
82
  return treeValue[protocol] || treeValue[NO_PROTOCOL];
73
83
  }
@@ -1,4 +1,19 @@
1
1
  import type { Args, CreateRequestWithMetrics } from './types';
2
2
  export declare const getUrlAndOptions: (args: Args) => any[];
3
3
  export declare const createRequestWithMetrics: CreateRequestWithMetrics;
4
+ export declare function initConnectionResolveMetrics({ metricsInstances: { dnsResolveDuration, tcpConnectDuration, tlsHandshakeDuration }, }: {
5
+ metricsInstances: {
6
+ dnsResolveDuration: any;
7
+ tcpConnectDuration: any;
8
+ tlsHandshakeDuration: any;
9
+ };
10
+ }): void;
11
+ export declare const addMetricsForFetch: ({ metricsInstances: { requestsTotal, requestsErrors, requestsDuration }, getServiceName, }: {
12
+ metricsInstances: {
13
+ requestsTotal: any;
14
+ requestsErrors: any;
15
+ requestsDuration: any;
16
+ };
17
+ getServiceName: any;
18
+ }) => void;
4
19
  //# sourceMappingURL=createRequestWithMetrics.d.ts.map
@@ -1,4 +1,7 @@
1
1
  import { format } from '@tinkoff/url';
2
+ import { subscribe } from 'diagnostics_channel';
3
+ import { UndiciError } from 'undici/lib/core/errors';
4
+ import { TLSSocket } from 'tls';
2
5
 
3
6
  // https://nodejs.org/api/errors.html#nodejs-error-codes - Common system errors possible for net/http/dns
4
7
  const POSSIBLE_ERRORS = [
@@ -57,8 +60,7 @@ const getUrlAndOptions = (args) => {
57
60
  const getDuration = (current, prev) =>
58
61
  // max to avoid negative values and turn that into zero
59
62
  prev === 0 ? 0 : Math.max((current - prev) / 1000, 0);
60
- const createRequestWithMetrics = ({ metricsInstances: { requestsTotal, requestsErrors, requestsDuration, dnsResolveDuration, tcpConnectDuration, tlsHandshakeDuration, }, getServiceName, config, }) => {
61
- const socketSet = new WeakSet();
63
+ const createRequestWithMetrics = ({ metricsInstances: { requestsTotal, requestsErrors, requestsDuration }, getServiceName, }) => {
62
64
  return function requestWithMetrics(originalRequest, ...args) {
63
65
  const [url, options] = getUrlAndOptions(args);
64
66
  const serviceName = getServiceName(url, options);
@@ -85,40 +87,87 @@ const createRequestWithMetrics = ({ metricsInstances: { requestsTotal, requestsE
85
87
  requestsErrors.inc(labelsValues);
86
88
  timerDone(labelsValues);
87
89
  });
88
- if (config.enableConnectionResolveMetrics) {
89
- req.on('socket', (socket) => {
90
- // due to keep-alive tcp option sockets might be reused
91
- // ignore them because they have already emitted events we are interested in
92
- if (socketSet.has(socket)) {
93
- return;
94
- }
95
- socketSet.add(socket);
96
- const timings = {
97
- start: Date.now(),
98
- lookupEnd: 0,
99
- connectEnd: 0,
100
- secureConnectEnd: 0,
101
- };
102
- const { service } = labelsValues;
103
- socket.on('lookup', () => {
104
- timings.lookupEnd = Date.now();
105
- dnsResolveDuration.observe({ service }, getDuration(timings.lookupEnd, timings.start));
106
- });
107
- socket.on('connect', () => {
108
- timings.connectEnd = Date.now();
109
- tcpConnectDuration.observe({ service }, getDuration(timings.connectEnd, timings.lookupEnd));
110
- });
111
- socket.on('secureConnect', () => {
112
- timings.secureConnectEnd = Date.now();
113
- tlsHandshakeDuration.observe({ service }, getDuration(timings.secureConnectEnd, timings.connectEnd));
114
- });
115
- socket.on('close', () => {
116
- socketSet.delete(socket);
117
- });
118
- });
119
- }
120
90
  return req;
121
91
  };
122
92
  };
93
+ function initConnectionResolveMetrics({ metricsInstances: { dnsResolveDuration, tcpConnectDuration, tlsHandshakeDuration }, }) {
94
+ subscribe('net.client.socket', ({ socket }) => {
95
+ const socketInfo = {
96
+ start: Date.now(),
97
+ lookupEnd: 0,
98
+ connectEnd: 0,
99
+ secureConnectEnd: 0,
100
+ host: 'unknown',
101
+ };
102
+ const protocol = socket instanceof TLSSocket ? 'https' : 'http';
103
+ socket.once('lookup', (_err, _address, _family, host) => {
104
+ socketInfo.lookupEnd = Date.now();
105
+ dnsResolveDuration.observe({ service: host }, getDuration(socketInfo.lookupEnd, socketInfo.start));
106
+ });
107
+ socket.on('connect', () => {
108
+ socketInfo.connectEnd = Date.now();
109
+ let service;
110
+ if (protocol === 'http') {
111
+ // _host is internal field - https://github.com/nodejs/node/blob/main/lib/net.js#L1383
112
+ const { remotePort: port, _host: host } = socket;
113
+ service = `${protocol}://${host}:${port}`;
114
+ socketInfo.host = host;
115
+ }
116
+ else {
117
+ // connect-options also internal - https://github.com/nodejs/node/blob/main/lib/internal/tls/wrap.js#L1749
118
+ const connectOptionsSymbol = Object.getOwnPropertySymbols(socket).find((smb) => smb.toString() === 'Symbol(connect-options)');
119
+ const connectOptions = socket[connectOptionsSymbol];
120
+ const { port, host } = connectOptions;
121
+ service = `${protocol}://${host}:${port}`;
122
+ socketInfo.host = host;
123
+ }
124
+ tcpConnectDuration.observe({ service }, getDuration(socketInfo.connectEnd, socketInfo.lookupEnd));
125
+ });
126
+ socket.on('secureConnect', () => {
127
+ socketInfo.secureConnectEnd = Date.now();
128
+ tlsHandshakeDuration.observe({ service: socketInfo.host }, getDuration(socketInfo.secureConnectEnd, socketInfo.connectEnd));
129
+ });
130
+ });
131
+ }
132
+ const requestMetricsSymbol = Symbol('request-metrics');
133
+ const addMetricsForFetch = ({ metricsInstances: { requestsTotal, requestsErrors, requestsDuration }, getServiceName, }) => {
134
+ subscribe('undici:request:create', ({ request }) => {
135
+ const { method, origin, path } = request;
136
+ const url = origin + path;
137
+ const serviceName = getServiceName(url, request);
138
+ const timerDone = requestsDuration.startTimer();
139
+ const labelsValues = {
140
+ method: method ?? 'unknown',
141
+ service: serviceName || origin || 'unknown',
142
+ status: 'unknown',
143
+ };
144
+ request[requestMetricsSymbol] = {
145
+ labelsValues,
146
+ timerDone,
147
+ };
148
+ });
149
+ subscribe('undici:request:headers', ({ request, response, }) => {
150
+ const { labelsValues } = request[requestMetricsSymbol];
151
+ const { statusCode } = response;
152
+ labelsValues.status = statusCode;
153
+ if (statusCode >= 400) {
154
+ requestsErrors.inc(labelsValues);
155
+ }
156
+ requestsTotal.inc(labelsValues);
157
+ });
158
+ subscribe('undici:request:trailers', ({ request }) => {
159
+ const { timerDone, labelsValues } = request[requestMetricsSymbol];
160
+ timerDone(labelsValues);
161
+ });
162
+ subscribe('undici:request:error', ({ request, error }) => {
163
+ const { timerDone, labelsValues } = request[requestMetricsSymbol];
164
+ if (error instanceof UndiciError) {
165
+ labelsValues.status = request.aborted ? 'aborted' : error.code;
166
+ }
167
+ requestsTotal.inc(labelsValues);
168
+ requestsErrors.inc(labelsValues);
169
+ timerDone(labelsValues);
170
+ });
171
+ };
123
172
 
124
- export { createRequestWithMetrics, getUrlAndOptions };
173
+ export { addMetricsForFetch, createRequestWithMetrics, getUrlAndOptions, initConnectionResolveMetrics };
@@ -3,6 +3,9 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var url = require('@tinkoff/url');
6
+ var diagnostics_channel = require('diagnostics_channel');
7
+ var errors = require('undici/lib/core/errors');
8
+ var tls = require('tls');
6
9
 
7
10
  // https://nodejs.org/api/errors.html#nodejs-error-codes - Common system errors possible for net/http/dns
8
11
  const POSSIBLE_ERRORS = [
@@ -61,8 +64,7 @@ const getUrlAndOptions = (args) => {
61
64
  const getDuration = (current, prev) =>
62
65
  // max to avoid negative values and turn that into zero
63
66
  prev === 0 ? 0 : Math.max((current - prev) / 1000, 0);
64
- const createRequestWithMetrics = ({ metricsInstances: { requestsTotal, requestsErrors, requestsDuration, dnsResolveDuration, tcpConnectDuration, tlsHandshakeDuration, }, getServiceName, config, }) => {
65
- const socketSet = new WeakSet();
67
+ const createRequestWithMetrics = ({ metricsInstances: { requestsTotal, requestsErrors, requestsDuration }, getServiceName, }) => {
66
68
  return function requestWithMetrics(originalRequest, ...args) {
67
69
  const [url, options] = getUrlAndOptions(args);
68
70
  const serviceName = getServiceName(url, options);
@@ -89,41 +91,90 @@ const createRequestWithMetrics = ({ metricsInstances: { requestsTotal, requestsE
89
91
  requestsErrors.inc(labelsValues);
90
92
  timerDone(labelsValues);
91
93
  });
92
- if (config.enableConnectionResolveMetrics) {
93
- req.on('socket', (socket) => {
94
- // due to keep-alive tcp option sockets might be reused
95
- // ignore them because they have already emitted events we are interested in
96
- if (socketSet.has(socket)) {
97
- return;
98
- }
99
- socketSet.add(socket);
100
- const timings = {
101
- start: Date.now(),
102
- lookupEnd: 0,
103
- connectEnd: 0,
104
- secureConnectEnd: 0,
105
- };
106
- const { service } = labelsValues;
107
- socket.on('lookup', () => {
108
- timings.lookupEnd = Date.now();
109
- dnsResolveDuration.observe({ service }, getDuration(timings.lookupEnd, timings.start));
110
- });
111
- socket.on('connect', () => {
112
- timings.connectEnd = Date.now();
113
- tcpConnectDuration.observe({ service }, getDuration(timings.connectEnd, timings.lookupEnd));
114
- });
115
- socket.on('secureConnect', () => {
116
- timings.secureConnectEnd = Date.now();
117
- tlsHandshakeDuration.observe({ service }, getDuration(timings.secureConnectEnd, timings.connectEnd));
118
- });
119
- socket.on('close', () => {
120
- socketSet.delete(socket);
121
- });
122
- });
123
- }
124
94
  return req;
125
95
  };
126
96
  };
97
+ function initConnectionResolveMetrics({ metricsInstances: { dnsResolveDuration, tcpConnectDuration, tlsHandshakeDuration }, }) {
98
+ diagnostics_channel.subscribe('net.client.socket', ({ socket }) => {
99
+ const socketInfo = {
100
+ start: Date.now(),
101
+ lookupEnd: 0,
102
+ connectEnd: 0,
103
+ secureConnectEnd: 0,
104
+ host: 'unknown',
105
+ };
106
+ const protocol = socket instanceof tls.TLSSocket ? 'https' : 'http';
107
+ socket.once('lookup', (_err, _address, _family, host) => {
108
+ socketInfo.lookupEnd = Date.now();
109
+ dnsResolveDuration.observe({ service: host }, getDuration(socketInfo.lookupEnd, socketInfo.start));
110
+ });
111
+ socket.on('connect', () => {
112
+ socketInfo.connectEnd = Date.now();
113
+ let service;
114
+ if (protocol === 'http') {
115
+ // _host is internal field - https://github.com/nodejs/node/blob/main/lib/net.js#L1383
116
+ const { remotePort: port, _host: host } = socket;
117
+ service = `${protocol}://${host}:${port}`;
118
+ socketInfo.host = host;
119
+ }
120
+ else {
121
+ // connect-options also internal - https://github.com/nodejs/node/blob/main/lib/internal/tls/wrap.js#L1749
122
+ const connectOptionsSymbol = Object.getOwnPropertySymbols(socket).find((smb) => smb.toString() === 'Symbol(connect-options)');
123
+ const connectOptions = socket[connectOptionsSymbol];
124
+ const { port, host } = connectOptions;
125
+ service = `${protocol}://${host}:${port}`;
126
+ socketInfo.host = host;
127
+ }
128
+ tcpConnectDuration.observe({ service }, getDuration(socketInfo.connectEnd, socketInfo.lookupEnd));
129
+ });
130
+ socket.on('secureConnect', () => {
131
+ socketInfo.secureConnectEnd = Date.now();
132
+ tlsHandshakeDuration.observe({ service: socketInfo.host }, getDuration(socketInfo.secureConnectEnd, socketInfo.connectEnd));
133
+ });
134
+ });
135
+ }
136
+ const requestMetricsSymbol = Symbol('request-metrics');
137
+ const addMetricsForFetch = ({ metricsInstances: { requestsTotal, requestsErrors, requestsDuration }, getServiceName, }) => {
138
+ diagnostics_channel.subscribe('undici:request:create', ({ request }) => {
139
+ const { method, origin, path } = request;
140
+ const url = origin + path;
141
+ const serviceName = getServiceName(url, request);
142
+ const timerDone = requestsDuration.startTimer();
143
+ const labelsValues = {
144
+ method: method ?? 'unknown',
145
+ service: serviceName || origin || 'unknown',
146
+ status: 'unknown',
147
+ };
148
+ request[requestMetricsSymbol] = {
149
+ labelsValues,
150
+ timerDone,
151
+ };
152
+ });
153
+ diagnostics_channel.subscribe('undici:request:headers', ({ request, response, }) => {
154
+ const { labelsValues } = request[requestMetricsSymbol];
155
+ const { statusCode } = response;
156
+ labelsValues.status = statusCode;
157
+ if (statusCode >= 400) {
158
+ requestsErrors.inc(labelsValues);
159
+ }
160
+ requestsTotal.inc(labelsValues);
161
+ });
162
+ diagnostics_channel.subscribe('undici:request:trailers', ({ request }) => {
163
+ const { timerDone, labelsValues } = request[requestMetricsSymbol];
164
+ timerDone(labelsValues);
165
+ });
166
+ diagnostics_channel.subscribe('undici:request:error', ({ request, error }) => {
167
+ const { timerDone, labelsValues } = request[requestMetricsSymbol];
168
+ if (error instanceof errors.UndiciError) {
169
+ labelsValues.status = request.aborted ? 'aborted' : error.code;
170
+ }
171
+ requestsTotal.inc(labelsValues);
172
+ requestsErrors.inc(labelsValues);
173
+ timerDone(labelsValues);
174
+ });
175
+ };
127
176
 
177
+ exports.addMetricsForFetch = addMetricsForFetch;
128
178
  exports.createRequestWithMetrics = createRequestWithMetrics;
129
179
  exports.getUrlAndOptions = getUrlAndOptions;
180
+ exports.initConnectionResolveMetrics = initConnectionResolveMetrics;
@@ -1,5 +1,6 @@
1
1
  import monkeypatch from '@tinkoff/monkeypatch';
2
2
  import { DEFAULT_BUCKETS } from '../constants.es.js';
3
+ import { addMetricsForFetch, initConnectionResolveMetrics } from './createRequestWithMetrics.es.js';
3
4
 
4
5
  const initRequestsMetrics = ({ metrics, getServiceName, http, https, createRequestWithMetrics, config, }) => {
5
6
  const metricsInstances = {
@@ -38,15 +39,19 @@ const initRequestsMetrics = ({ metrics, getServiceName, http, https, createReque
38
39
  buckets: DEFAULT_BUCKETS,
39
40
  }),
40
41
  };
42
+ addMetricsForFetch({ metricsInstances, getServiceName });
43
+ if (config.enableConnectionResolveMetrics) {
44
+ initConnectionResolveMetrics({ metricsInstances });
45
+ }
41
46
  monkeypatch({
42
47
  obj: https,
43
48
  method: 'request',
44
- handler: createRequestWithMetrics({ metricsInstances, getServiceName, config }),
49
+ handler: createRequestWithMetrics({ metricsInstances, getServiceName }),
45
50
  });
46
51
  monkeypatch({
47
52
  obj: http,
48
53
  method: 'request',
49
- handler: createRequestWithMetrics({ metricsInstances, getServiceName, config }),
54
+ handler: createRequestWithMetrics({ metricsInstances, getServiceName }),
50
55
  });
51
56
  };
52
57
 
@@ -4,12 +4,13 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var monkeypatch = require('@tinkoff/monkeypatch');
6
6
  var constants = require('../constants.js');
7
+ var createRequestWithMetrics = require('./createRequestWithMetrics.js');
7
8
 
8
9
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
9
10
 
10
11
  var monkeypatch__default = /*#__PURE__*/_interopDefaultLegacy(monkeypatch);
11
12
 
12
- const initRequestsMetrics = ({ metrics, getServiceName, http, https, createRequestWithMetrics, config, }) => {
13
+ const initRequestsMetrics = ({ metrics, getServiceName, http, https, createRequestWithMetrics: createRequestWithMetrics$1, config, }) => {
13
14
  const metricsInstances = {
14
15
  requestsTotal: metrics.counter({
15
16
  name: 'http_sent_requests_total',
@@ -46,15 +47,19 @@ const initRequestsMetrics = ({ metrics, getServiceName, http, https, createReque
46
47
  buckets: constants.DEFAULT_BUCKETS,
47
48
  }),
48
49
  };
50
+ createRequestWithMetrics.addMetricsForFetch({ metricsInstances, getServiceName });
51
+ if (config.enableConnectionResolveMetrics) {
52
+ createRequestWithMetrics.initConnectionResolveMetrics({ metricsInstances });
53
+ }
49
54
  monkeypatch__default["default"]({
50
55
  obj: https,
51
56
  method: 'request',
52
- handler: createRequestWithMetrics({ metricsInstances, getServiceName, config }),
57
+ handler: createRequestWithMetrics$1({ metricsInstances, getServiceName }),
53
58
  });
54
59
  monkeypatch__default["default"]({
55
60
  obj: http,
56
61
  method: 'request',
57
- handler: createRequestWithMetrics({ metricsInstances, getServiceName, config }),
62
+ handler: createRequestWithMetrics$1({ metricsInstances, getServiceName }),
58
63
  });
59
64
  };
60
65
 
@@ -4,7 +4,7 @@ import type { RequestOptions, IncomingMessage, ClientRequest } from 'http';
4
4
  import type httpType from 'http';
5
5
  import type httpsType from 'https';
6
6
  import type { Counter, Histogram } from 'prom-client';
7
- import type { METRICS_MODULE_TOKEN, METRICS_SERVICES_REGISTRY_TOKEN, ModuleConfig } from '@tramvai/tokens-metrics';
7
+ import type { METRICS_MODULE_TOKEN, METRICS_SERVICES_REGISTRY_TOKEN } from '@tramvai/tokens-metrics';
8
8
  export type MetricsModule = typeof METRICS_MODULE_TOKEN;
9
9
  export type HttpModule = typeof httpType;
10
10
  export type HttpsModule = typeof httpsType;
@@ -22,6 +22,5 @@ export type Args = [RequestOptions | string | URL, (res: IncomingMessage) => voi
22
22
  export type CreateRequestWithMetrics = (args: {
23
23
  metricsInstances: MetricsInstances;
24
24
  getServiceName: GetServiceName;
25
- config: ModuleConfig;
26
25
  }) => (originalRequest: HttpModule['request'], ...requestArgs: Args) => ClientRequest;
27
26
  //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tramvai/module-metrics",
3
- "version": "6.64.1",
3
+ "version": "6.65.0",
4
4
  "description": "",
5
5
  "browser": "lib/browser.js",
6
6
  "main": "lib/server.js",
@@ -25,17 +25,18 @@
25
25
  "@tinkoff/monkeypatch": "6.0.0",
26
26
  "@tinkoff/url": "0.12.1",
27
27
  "@tinkoff/utils": "^2.1.2",
28
- "@tramvai/core": "6.64.1",
29
- "@tramvai/papi": "6.64.1",
30
- "@tramvai/state": "6.64.1",
31
- "@tramvai/tokens-common": "6.64.1",
32
- "@tramvai/tokens-core-private": "6.64.1",
33
- "@tramvai/tokens-http-client": "6.64.1",
34
- "@tramvai/tokens-metrics": "6.64.1",
35
- "@tramvai/tokens-router": "6.64.1",
36
- "@tramvai/tokens-server": "6.64.1",
37
- "@tramvai/tokens-server-private": "6.64.1",
38
- "prom-client": "^14.2.0"
28
+ "@tramvai/core": "6.65.0",
29
+ "@tramvai/papi": "6.65.0",
30
+ "@tramvai/state": "6.65.0",
31
+ "@tramvai/tokens-common": "6.65.0",
32
+ "@tramvai/tokens-core-private": "6.65.0",
33
+ "@tramvai/tokens-http-client": "6.65.0",
34
+ "@tramvai/tokens-metrics": "6.65.0",
35
+ "@tramvai/tokens-router": "6.65.0",
36
+ "@tramvai/tokens-server": "6.65.0",
37
+ "@tramvai/tokens-server-private": "6.65.0",
38
+ "prom-client": "^14.2.0",
39
+ "undici": "^7.16.0"
39
40
  },
40
41
  "peerDependencies": {
41
42
  "tslib": "^2.4.0"