@libp2p/utils 5.4.0-9d13a2f6a → 5.4.0-bfa7660d5

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.
@@ -0,0 +1,7 @@
1
+ import type { AbortOptions } from '@libp2p/interface';
2
+ import type { ClearableSignal } from 'any-signal';
3
+ export declare function createTimeoutOptions(timeout: number): AbortOptions;
4
+ export declare function createTimeoutOptions(timeout: number, ...existingSignals: AbortSignal[]): {
5
+ signal: ClearableSignal;
6
+ };
7
+ //# sourceMappingURL=abort-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abort-options.d.ts","sourceRoot":"","sources":["../../src/abort-options.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,wBAAgB,oBAAoB,CAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAAA;AACpE,wBAAgB,oBAAoB,CAAE,OAAO,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,WAAW,EAAE,GAAG;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,CAAA"}
@@ -0,0 +1,14 @@
1
+ import { setMaxListeners } from '@libp2p/interface';
2
+ import { anySignal } from 'any-signal';
3
+ export function createTimeoutOptions(timeout, ...existingSignals) {
4
+ let signal = AbortSignal.timeout(timeout);
5
+ setMaxListeners(Infinity, signal);
6
+ if (existingSignals.length > 0) {
7
+ signal = anySignal([signal, ...existingSignals]);
8
+ setMaxListeners(Infinity, signal);
9
+ }
10
+ return {
11
+ signal
12
+ };
13
+ }
14
+ //# sourceMappingURL=abort-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abort-options.js","sourceRoot":"","sources":["../../src/abort-options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAMtC,MAAM,UAAU,oBAAoB,CAAE,OAAe,EAAE,GAAG,eAA8B;IACtF,IAAI,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IACzC,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAEjC,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,MAAM,GAAG,SAAS,CAAC,CAAC,MAAM,EAAE,GAAG,eAAe,CAAC,CAAC,CAAA;QAChD,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IACnC,CAAC;IAED,OAAO;QACL,MAAM;KACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { type ClearableSignal } from 'any-signal';
2
+ import type { Metrics } from '@libp2p/interface';
3
+ export declare const DEFAULT_TIMEOUT_MULTIPLIER = 1.2;
4
+ export declare const DEFAULT_FAILURE_MULTIPLIER = 2;
5
+ export declare const DEFAULT_MIN_TIMEOUT = 2000;
6
+ export interface AdaptiveTimeoutSignal extends ClearableSignal {
7
+ start: number;
8
+ timeout: number;
9
+ }
10
+ export interface AdaptiveTimeoutInit {
11
+ metricName?: string;
12
+ metrics?: Metrics;
13
+ interval?: number;
14
+ initialValue?: number;
15
+ timeoutMultiplier?: number;
16
+ failureMultiplier?: number;
17
+ minTimeout?: number;
18
+ }
19
+ export interface GetTimeoutSignalOptions {
20
+ timeoutFactor?: number;
21
+ signal?: AbortSignal;
22
+ }
23
+ export declare class AdaptiveTimeout {
24
+ private readonly success;
25
+ private readonly failure;
26
+ private readonly next;
27
+ private readonly metric?;
28
+ private readonly timeoutMultiplier;
29
+ private readonly failureMultiplier;
30
+ private readonly minTimeout;
31
+ constructor(init?: AdaptiveTimeoutInit);
32
+ getTimeoutSignal(options?: GetTimeoutSignalOptions): AdaptiveTimeoutSignal;
33
+ cleanUp(signal: AdaptiveTimeoutSignal): void;
34
+ }
35
+ //# sourceMappingURL=adaptive-timeout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptive-timeout.d.ts","sourceRoot":"","sources":["../../src/adaptive-timeout.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAE5D,OAAO,KAAK,EAAe,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAE7D,eAAO,MAAM,0BAA0B,MAAM,CAAA;AAC7C,eAAO,MAAM,0BAA0B,IAAI,CAAA;AAC3C,eAAO,MAAM,mBAAmB,OAAO,CAAA;AAEvC,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAa;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAQ;IAC1C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAQ;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;gBAEtB,IAAI,GAAE,mBAAwB;IAa3C,gBAAgB,CAAE,OAAO,GAAE,uBAA4B,GAAG,qBAAqB;IAiB/E,OAAO,CAAE,MAAM,EAAE,qBAAqB,GAAG,IAAI;CAyB9C"}
@@ -0,0 +1,63 @@
1
+ import { setMaxListeners } from '@libp2p/interface';
2
+ import { anySignal } from 'any-signal';
3
+ import { MovingAverage } from './moving-average.js';
4
+ export const DEFAULT_TIMEOUT_MULTIPLIER = 1.2;
5
+ export const DEFAULT_FAILURE_MULTIPLIER = 2;
6
+ export const DEFAULT_MIN_TIMEOUT = 2000;
7
+ export class AdaptiveTimeout {
8
+ success;
9
+ failure;
10
+ next;
11
+ metric;
12
+ timeoutMultiplier;
13
+ failureMultiplier;
14
+ minTimeout;
15
+ constructor(init = {}) {
16
+ this.success = new MovingAverage(init.interval ?? 5000);
17
+ this.failure = new MovingAverage(init.interval ?? 5000);
18
+ this.next = new MovingAverage(init.interval ?? 5000);
19
+ this.failureMultiplier = init.failureMultiplier ?? DEFAULT_FAILURE_MULTIPLIER;
20
+ this.timeoutMultiplier = init.timeoutMultiplier ?? DEFAULT_TIMEOUT_MULTIPLIER;
21
+ this.minTimeout = init.minTimeout ?? DEFAULT_MIN_TIMEOUT;
22
+ if (init.metricName != null) {
23
+ this.metric = init.metrics?.registerMetricGroup(init.metricName);
24
+ }
25
+ }
26
+ getTimeoutSignal(options = {}) {
27
+ // calculate timeout for individual peers based on moving average of
28
+ // previous successful requests
29
+ const timeout = Math.max(Math.round(this.next.movingAverage * (options.timeoutFactor ?? this.timeoutMultiplier)), this.minTimeout);
30
+ const sendTimeout = AbortSignal.timeout(timeout);
31
+ const timeoutSignal = anySignal([options.signal, sendTimeout]);
32
+ setMaxListeners(Infinity, timeoutSignal, sendTimeout);
33
+ timeoutSignal.start = Date.now();
34
+ timeoutSignal.timeout = timeout;
35
+ return timeoutSignal;
36
+ }
37
+ cleanUp(signal) {
38
+ const time = Date.now() - signal.start;
39
+ if (signal.aborted) {
40
+ this.failure.push(time);
41
+ this.next.push(time * this.failureMultiplier);
42
+ this.metric?.update({
43
+ failureMovingAverage: this.failure.movingAverage,
44
+ failureDeviation: this.failure.deviation,
45
+ failureForecast: this.failure.forecast,
46
+ failureVariance: this.failure.variance,
47
+ failure: time
48
+ });
49
+ }
50
+ else {
51
+ this.success.push(time);
52
+ this.next.push(time);
53
+ this.metric?.update({
54
+ successMovingAverage: this.success.movingAverage,
55
+ successDeviation: this.success.deviation,
56
+ successForecast: this.success.forecast,
57
+ successVariance: this.success.variance,
58
+ success: time
59
+ });
60
+ }
61
+ }
62
+ }
63
+ //# sourceMappingURL=adaptive-timeout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptive-timeout.js","sourceRoot":"","sources":["../../src/adaptive-timeout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { Connection, Stream, AbortOptions } from '@libp2p/interface';
2
+ /**
3
+ * Close the passed stream, falling back to aborting the stream if closing
4
+ * cleanly fails.
5
+ */
6
+ export declare function safelyCloseStream(stream?: Stream, options?: AbortOptions): Promise<void>;
7
+ export interface SafelyCloseConnectionOptions extends AbortOptions {
8
+ /**
9
+ * Only close the stream if it either has no protocol streams open or only
10
+ * ones in this list.
11
+ *
12
+ * @default ['/ipfs/id/1.0.0']
13
+ */
14
+ closableProtocols?: string[];
15
+ }
16
+ /**
17
+ * Close the passed connection if it has no streams, or only closable protocol
18
+ * streams, falling back to aborting the connection if closing it cleanly fails.
19
+ */
20
+ export declare function safelyCloseConnectionIfUnused(connection?: Connection, options?: SafelyCloseConnectionOptions): Promise<void>;
21
+ //# sourceMappingURL=close.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"close.d.ts","sourceRoot":"","sources":["../../src/close.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEzE;;;GAGG;AACH,wBAAsB,iBAAiB,CAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/F;AAuBD,MAAM,WAAW,4BAA6B,SAAQ,YAAY;IAChE;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC7B;AAED;;;GAGG;AACH,wBAAsB,6BAA6B,CAAE,UAAU,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,4BAA4B,GAAG,OAAO,CAAC,IAAI,CAAC,CAenI"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Close the passed stream, falling back to aborting the stream if closing
3
+ * cleanly fails.
4
+ */
5
+ export async function safelyCloseStream(stream, options) {
6
+ try {
7
+ await stream?.close(options);
8
+ }
9
+ catch (err) {
10
+ stream?.abort(err);
11
+ }
12
+ }
13
+ /**
14
+ * These are speculative protocols that are run automatically on connection open
15
+ * so are usually not the reason the connection was opened.
16
+ *
17
+ * Consequently when requested it should be safe to close connections that only
18
+ * have these protocol streams open.
19
+ */
20
+ const DEFAULT_CLOSABLE_PROTOCOLS = [
21
+ // identify
22
+ '/ipfs/id/1.0.0',
23
+ // identify-push
24
+ '/ipfs/id/push/1.0.0',
25
+ // autonat
26
+ '/libp2p/autonat/1.0.0',
27
+ // dcutr
28
+ '/libp2p/dcutr'
29
+ ];
30
+ /**
31
+ * Close the passed connection if it has no streams, or only closable protocol
32
+ * streams, falling back to aborting the connection if closing it cleanly fails.
33
+ */
34
+ export async function safelyCloseConnectionIfUnused(connection, options) {
35
+ const streamProtocols = connection?.streams?.map(stream => stream.protocol) ?? [];
36
+ const closableProtocols = options?.closableProtocols ?? DEFAULT_CLOSABLE_PROTOCOLS;
37
+ // if the connection has protocols not in the closable protocols list, do not
38
+ // close the connection
39
+ if (streamProtocols.filter(proto => proto != null && !closableProtocols.includes(proto)).length > 0) {
40
+ return;
41
+ }
42
+ try {
43
+ await connection?.close(options);
44
+ }
45
+ catch (err) {
46
+ connection?.abort(err);
47
+ }
48
+ }
49
+ //# sourceMappingURL=close.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"close.js","sourceRoot":"","sources":["../../src/close.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAE,MAAe,EAAE,OAAsB;IAC9E,IAAI,CAAC;QACH,MAAM,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;IAC9B,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;IACpB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,0BAA0B,GAAG;IACjC,WAAW;IACX,gBAAgB;IAEhB,gBAAgB;IAChB,qBAAqB;IAErB,UAAU;IACV,uBAAuB;IAEvB,QAAQ;IACR,eAAe;CAChB,CAAA;AAYD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B,CAAE,UAAuB,EAAE,OAAsC;IAClH,MAAM,eAAe,GAAG,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;IACjF,MAAM,iBAAiB,GAAG,OAAO,EAAE,iBAAiB,IAAI,0BAA0B,CAAA;IAElF,6EAA6E;IAC7E,uBAAuB;IACvB,IAAI,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpG,OAAM;IACR,CAAC;IAED,IAAI,CAAC;QACH,MAAM,UAAU,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;IAClC,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,UAAU,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;IACxB,CAAC;AACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Implements exponential moving average. Ported from `moving-average`.
3
+ *
4
+ * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
5
+ * @see https://www.npmjs.com/package/moving-average
6
+ */
7
+ export declare class MovingAverage {
8
+ movingAverage: number;
9
+ variance: number;
10
+ deviation: number;
11
+ forecast: number;
12
+ private readonly timespan;
13
+ private previousTime?;
14
+ constructor(timespan: number);
15
+ alpha(t: number, pt: number): number;
16
+ push(value: number, time?: number): void;
17
+ }
18
+ //# sourceMappingURL=moving-average.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moving-average.d.ts","sourceRoot":"","sources":["../../src/moving-average.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,aAAa;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,YAAY,CAAC,CAAQ;gBAEhB,QAAQ,EAAE,MAAM;IAQ7B,KAAK,CAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM;IAIrC,IAAI,CAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,MAAmB,GAAG,IAAI;CAkBtD"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Implements exponential moving average. Ported from `moving-average`.
3
+ *
4
+ * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
5
+ * @see https://www.npmjs.com/package/moving-average
6
+ */
7
+ export class MovingAverage {
8
+ movingAverage;
9
+ variance;
10
+ deviation;
11
+ forecast;
12
+ timespan;
13
+ previousTime;
14
+ constructor(timespan) {
15
+ this.timespan = timespan;
16
+ this.movingAverage = 0;
17
+ this.variance = 0;
18
+ this.deviation = 0;
19
+ this.forecast = 0;
20
+ }
21
+ alpha(t, pt) {
22
+ return 1 - (Math.exp(-(t - pt) / this.timespan));
23
+ }
24
+ push(value, time = Date.now()) {
25
+ if (this.previousTime != null) {
26
+ // calculate moving average
27
+ const a = this.alpha(time, this.previousTime);
28
+ const diff = value - this.movingAverage;
29
+ const incr = a * diff;
30
+ this.movingAverage = a * value + (1 - a) * this.movingAverage;
31
+ // calculate variance & deviation
32
+ this.variance = (1 - a) * (this.variance + diff * incr);
33
+ this.deviation = Math.sqrt(this.variance);
34
+ // calculate forecast
35
+ this.forecast = this.movingAverage + a * diff;
36
+ }
37
+ else {
38
+ this.movingAverage = value;
39
+ }
40
+ this.previousTime = time;
41
+ }
42
+ }
43
+ //# sourceMappingURL=moving-average.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moving-average.js","sourceRoot":"","sources":["../../src/moving-average.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IACjB,aAAa,CAAQ;IACrB,QAAQ,CAAQ;IAChB,SAAS,CAAQ;IACjB,QAAQ,CAAQ;IACN,QAAQ,CAAQ;IACzB,YAAY,CAAS;IAE7B,YAAa,QAAgB;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACxB,IAAI,CAAC,aAAa,GAAG,CAAC,CAAA;QACtB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;QACjB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAA;QAClB,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAA;IACnB,CAAC;IAED,KAAK,CAAE,CAAS,EAAE,EAAU;QAC1B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;IAClD,CAAC;IAED,IAAI,CAAE,KAAa,EAAE,OAAe,IAAI,CAAC,GAAG,EAAE;QAC5C,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;YAC9B,2BAA2B;YAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,CAAA;YAC7C,MAAM,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC,aAAa,CAAA;YACvC,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAA;YACrB,IAAI,CAAC,aAAa,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,aAAa,CAAA;YAC7D,iCAAiC;YACjC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,CAAA;YACvD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACzC,qBAAqB;YACrB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,aAAa,GAAG,CAAC,GAAG,IAAI,CAAA;QAC/C,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,aAAa,GAAG,KAAK,CAAA;QAC5B,CAAC;QAED,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;IAC1B,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libp2p/utils",
3
- "version": "5.4.0-9d13a2f6a",
3
+ "version": "5.4.0-bfa7660d5",
4
4
  "description": "Package to aggregate shared logic and dependencies for the libp2p ecosystem",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/utils#readme",
@@ -44,6 +44,10 @@
44
44
  "types": "./src/index.d.ts",
45
45
  "import": "./dist/src/index.js"
46
46
  },
47
+ "./abort-options": {
48
+ "types": "./dist/src/abort-options.d.ts",
49
+ "import": "./dist/src/abort-options.js"
50
+ },
47
51
  "./abstract-stream": {
48
52
  "types": "./dist/src/abstract-stream.d.ts",
49
53
  "import": "./dist/src/abstract-stream.js"
@@ -52,6 +56,10 @@
52
56
  "types": "./dist/src/address-sort.d.ts",
53
57
  "import": "./dist/src/address-sort.js"
54
58
  },
59
+ "./adaptive-timeout": {
60
+ "types": "./dist/src/adaptive-timeout.d.ts",
61
+ "import": "./dist/src/adaptive-timeout.js"
62
+ },
55
63
  "./array-equals": {
56
64
  "types": "./dist/src/array-equals.d.ts",
57
65
  "import": "./dist/src/array-equals.js"
@@ -60,6 +68,10 @@
60
68
  "types": "./dist/src/close-source.d.ts",
61
69
  "import": "./dist/src/close-source.js"
62
70
  },
71
+ "./close": {
72
+ "types": "./dist/src/close.d.ts",
73
+ "import": "./dist/src/close.js"
74
+ },
63
75
  "./filters": {
64
76
  "types": "./dist/src/filters/index.d.ts",
65
77
  "import": "./dist/src/filters/index.js"
@@ -72,6 +84,10 @@
72
84
  "types": "./dist/src/is-promise.d.ts",
73
85
  "import": "./dist/src/is-promise.js"
74
86
  },
87
+ "./moving-average": {
88
+ "types": "./dist/src/moving-average.d.ts",
89
+ "import": "./dist/src/moving-average.js"
90
+ },
75
91
  "./multiaddr/is-loopback": {
76
92
  "types": "./dist/src/multiaddr/is-loopback.d.ts",
77
93
  "import": "./dist/src/multiaddr/is-loopback.js"
@@ -132,13 +148,14 @@
132
148
  },
133
149
  "dependencies": {
134
150
  "@chainsafe/is-ip": "^2.0.2",
135
- "@libp2p/crypto": "4.1.1-9d13a2f6a",
136
- "@libp2p/interface": "1.3.1-9d13a2f6a",
137
- "@libp2p/logger": "4.0.12-9d13a2f6a",
151
+ "@libp2p/crypto": "4.1.1-bfa7660d5",
152
+ "@libp2p/interface": "1.3.1-bfa7660d5",
153
+ "@libp2p/logger": "4.0.12-bfa7660d5",
138
154
  "@multiformats/multiaddr": "^12.2.1",
139
155
  "@multiformats/multiaddr-matcher": "^1.2.0",
140
156
  "@sindresorhus/fnv1a": "^3.1.0",
141
157
  "@types/murmurhash3js-revisited": "^3.0.3",
158
+ "any-signal": "^4.1.1",
142
159
  "delay": "^6.0.0",
143
160
  "get-iterator": "^2.0.1",
144
161
  "is-loopback-addr": "^2.0.2",
@@ -153,7 +170,7 @@
153
170
  "uint8arrays": "^5.0.3"
154
171
  },
155
172
  "devDependencies": {
156
- "@libp2p/peer-id-factory": "4.1.1-9d13a2f6a",
173
+ "@libp2p/peer-id-factory": "4.1.1-bfa7660d5",
157
174
  "@types/netmask": "^2.0.5",
158
175
  "aegir": "^42.2.5",
159
176
  "delay": "^6.0.0",
@@ -0,0 +1,20 @@
1
+ import { setMaxListeners } from '@libp2p/interface'
2
+ import { anySignal } from 'any-signal'
3
+ import type { AbortOptions } from '@libp2p/interface'
4
+ import type { ClearableSignal } from 'any-signal'
5
+
6
+ export function createTimeoutOptions (timeout: number): AbortOptions
7
+ export function createTimeoutOptions (timeout: number, ...existingSignals: AbortSignal[]): { signal: ClearableSignal }
8
+ export function createTimeoutOptions (timeout: number, ...existingSignals: AbortSignal[]): AbortOptions {
9
+ let signal = AbortSignal.timeout(timeout)
10
+ setMaxListeners(Infinity, signal)
11
+
12
+ if (existingSignals.length > 0) {
13
+ signal = anySignal([signal, ...existingSignals])
14
+ setMaxListeners(Infinity, signal)
15
+ }
16
+
17
+ return {
18
+ signal
19
+ }
20
+ }
@@ -0,0 +1,94 @@
1
+ import { setMaxListeners } from '@libp2p/interface'
2
+ import { anySignal, type ClearableSignal } from 'any-signal'
3
+ import { MovingAverage } from './moving-average.js'
4
+ import type { MetricGroup, Metrics } from '@libp2p/interface'
5
+
6
+ export const DEFAULT_TIMEOUT_MULTIPLIER = 1.2
7
+ export const DEFAULT_FAILURE_MULTIPLIER = 2
8
+ export const DEFAULT_MIN_TIMEOUT = 2000
9
+
10
+ export interface AdaptiveTimeoutSignal extends ClearableSignal {
11
+ start: number
12
+ timeout: number
13
+ }
14
+
15
+ export interface AdaptiveTimeoutInit {
16
+ metricName?: string
17
+ metrics?: Metrics
18
+ interval?: number
19
+ initialValue?: number
20
+ timeoutMultiplier?: number
21
+ failureMultiplier?: number
22
+ minTimeout?: number
23
+ }
24
+
25
+ export interface GetTimeoutSignalOptions {
26
+ timeoutFactor?: number
27
+ signal?: AbortSignal
28
+ }
29
+
30
+ export class AdaptiveTimeout {
31
+ private readonly success: MovingAverage
32
+ private readonly failure: MovingAverage
33
+ private readonly next: MovingAverage
34
+ private readonly metric?: MetricGroup
35
+ private readonly timeoutMultiplier: number
36
+ private readonly failureMultiplier: number
37
+ private readonly minTimeout: number
38
+
39
+ constructor (init: AdaptiveTimeoutInit = {}) {
40
+ this.success = new MovingAverage(init.interval ?? 5000)
41
+ this.failure = new MovingAverage(init.interval ?? 5000)
42
+ this.next = new MovingAverage(init.interval ?? 5000)
43
+ this.failureMultiplier = init.failureMultiplier ?? DEFAULT_FAILURE_MULTIPLIER
44
+ this.timeoutMultiplier = init.timeoutMultiplier ?? DEFAULT_TIMEOUT_MULTIPLIER
45
+ this.minTimeout = init.minTimeout ?? DEFAULT_MIN_TIMEOUT
46
+
47
+ if (init.metricName != null) {
48
+ this.metric = init.metrics?.registerMetricGroup(init.metricName)
49
+ }
50
+ }
51
+
52
+ getTimeoutSignal (options: GetTimeoutSignalOptions = {}): AdaptiveTimeoutSignal {
53
+ // calculate timeout for individual peers based on moving average of
54
+ // previous successful requests
55
+ const timeout = Math.max(
56
+ Math.round(this.next.movingAverage * (options.timeoutFactor ?? this.timeoutMultiplier)),
57
+ this.minTimeout
58
+ )
59
+ const sendTimeout = AbortSignal.timeout(timeout)
60
+ const timeoutSignal = anySignal([options.signal, sendTimeout]) as AdaptiveTimeoutSignal
61
+ setMaxListeners(Infinity, timeoutSignal, sendTimeout)
62
+
63
+ timeoutSignal.start = Date.now()
64
+ timeoutSignal.timeout = timeout
65
+
66
+ return timeoutSignal
67
+ }
68
+
69
+ cleanUp (signal: AdaptiveTimeoutSignal): void {
70
+ const time = Date.now() - signal.start
71
+
72
+ if (signal.aborted) {
73
+ this.failure.push(time)
74
+ this.next.push(time * this.failureMultiplier)
75
+ this.metric?.update({
76
+ failureMovingAverage: this.failure.movingAverage,
77
+ failureDeviation: this.failure.deviation,
78
+ failureForecast: this.failure.forecast,
79
+ failureVariance: this.failure.variance,
80
+ failure: time
81
+ })
82
+ } else {
83
+ this.success.push(time)
84
+ this.next.push(time)
85
+ this.metric?.update({
86
+ successMovingAverage: this.success.movingAverage,
87
+ successDeviation: this.success.deviation,
88
+ successForecast: this.success.forecast,
89
+ successVariance: this.success.variance,
90
+ success: time
91
+ })
92
+ }
93
+ }
94
+ }
package/src/close.ts ADDED
@@ -0,0 +1,65 @@
1
+ import type { Connection, Stream, AbortOptions } from '@libp2p/interface'
2
+
3
+ /**
4
+ * Close the passed stream, falling back to aborting the stream if closing
5
+ * cleanly fails.
6
+ */
7
+ export async function safelyCloseStream (stream?: Stream, options?: AbortOptions): Promise<void> {
8
+ try {
9
+ await stream?.close(options)
10
+ } catch (err: any) {
11
+ stream?.abort(err)
12
+ }
13
+ }
14
+
15
+ /**
16
+ * These are speculative protocols that are run automatically on connection open
17
+ * so are usually not the reason the connection was opened.
18
+ *
19
+ * Consequently when requested it should be safe to close connections that only
20
+ * have these protocol streams open.
21
+ */
22
+ const DEFAULT_CLOSABLE_PROTOCOLS = [
23
+ // identify
24
+ '/ipfs/id/1.0.0',
25
+
26
+ // identify-push
27
+ '/ipfs/id/push/1.0.0',
28
+
29
+ // autonat
30
+ '/libp2p/autonat/1.0.0',
31
+
32
+ // dcutr
33
+ '/libp2p/dcutr'
34
+ ]
35
+
36
+ export interface SafelyCloseConnectionOptions extends AbortOptions {
37
+ /**
38
+ * Only close the stream if it either has no protocol streams open or only
39
+ * ones in this list.
40
+ *
41
+ * @default ['/ipfs/id/1.0.0']
42
+ */
43
+ closableProtocols?: string[]
44
+ }
45
+
46
+ /**
47
+ * Close the passed connection if it has no streams, or only closable protocol
48
+ * streams, falling back to aborting the connection if closing it cleanly fails.
49
+ */
50
+ export async function safelyCloseConnectionIfUnused (connection?: Connection, options?: SafelyCloseConnectionOptions): Promise<void> {
51
+ const streamProtocols = connection?.streams?.map(stream => stream.protocol) ?? []
52
+ const closableProtocols = options?.closableProtocols ?? DEFAULT_CLOSABLE_PROTOCOLS
53
+
54
+ // if the connection has protocols not in the closable protocols list, do not
55
+ // close the connection
56
+ if (streamProtocols.filter(proto => proto != null && !closableProtocols.includes(proto)).length > 0) {
57
+ return
58
+ }
59
+
60
+ try {
61
+ await connection?.close(options)
62
+ } catch (err: any) {
63
+ connection?.abort(err)
64
+ }
65
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Implements exponential moving average. Ported from `moving-average`.
3
+ *
4
+ * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
5
+ * @see https://www.npmjs.com/package/moving-average
6
+ */
7
+ export class MovingAverage {
8
+ public movingAverage: number
9
+ public variance: number
10
+ public deviation: number
11
+ public forecast: number
12
+ private readonly timespan: number
13
+ private previousTime?: number
14
+
15
+ constructor (timespan: number) {
16
+ this.timespan = timespan
17
+ this.movingAverage = 0
18
+ this.variance = 0
19
+ this.deviation = 0
20
+ this.forecast = 0
21
+ }
22
+
23
+ alpha (t: number, pt: number): number {
24
+ return 1 - (Math.exp(-(t - pt) / this.timespan))
25
+ }
26
+
27
+ push (value: number, time: number = Date.now()): void {
28
+ if (this.previousTime != null) {
29
+ // calculate moving average
30
+ const a = this.alpha(time, this.previousTime)
31
+ const diff = value - this.movingAverage
32
+ const incr = a * diff
33
+ this.movingAverage = a * value + (1 - a) * this.movingAverage
34
+ // calculate variance & deviation
35
+ this.variance = (1 - a) * (this.variance + diff * incr)
36
+ this.deviation = Math.sqrt(this.variance)
37
+ // calculate forecast
38
+ this.forecast = this.movingAverage + a * diff
39
+ } else {
40
+ this.movingAverage = value
41
+ }
42
+
43
+ this.previousTime = time
44
+ }
45
+ }