@lodestar/utils 1.35.0-dev.f80d2d52da → 1.35.0-dev.fcf8d024ea

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.
Files changed (81) hide show
  1. package/lib/assert.d.ts.map +1 -0
  2. package/lib/assert.js +1 -1
  3. package/lib/assert.js.map +1 -1
  4. package/lib/base64.d.ts.map +1 -0
  5. package/lib/bytes/browser.d.ts.map +1 -0
  6. package/lib/bytes/index.d.ts.map +1 -0
  7. package/lib/bytes/nodejs.d.ts.map +1 -0
  8. package/lib/bytes.d.ts.map +1 -0
  9. package/lib/command.d.ts.map +1 -0
  10. package/lib/diff.d.ts.map +1 -0
  11. package/lib/err.d.ts.map +1 -0
  12. package/lib/errors.d.ts.map +1 -0
  13. package/lib/errors.js +1 -0
  14. package/lib/errors.js.map +1 -1
  15. package/lib/ethConversion.d.ts.map +1 -0
  16. package/lib/fetch.d.ts.map +1 -0
  17. package/lib/fetch.js +3 -0
  18. package/lib/fetch.js.map +1 -1
  19. package/lib/format.d.ts.map +1 -0
  20. package/lib/index.d.ts +8 -8
  21. package/lib/index.d.ts.map +1 -0
  22. package/lib/index.js +6 -6
  23. package/lib/index.js.map +1 -1
  24. package/lib/iterator.d.ts.map +1 -0
  25. package/lib/logger.d.ts.map +1 -0
  26. package/lib/map.d.ts.map +1 -0
  27. package/lib/map.js +6 -7
  28. package/lib/map.js.map +1 -1
  29. package/lib/math.d.ts.map +1 -0
  30. package/lib/metrics.d.ts.map +1 -0
  31. package/lib/notNullish.d.ts.map +1 -0
  32. package/lib/objects.d.ts.map +1 -0
  33. package/lib/promise.d.ts.map +1 -0
  34. package/lib/retry.d.ts.map +1 -0
  35. package/lib/sleep.d.ts.map +1 -0
  36. package/lib/sort.d.ts.map +1 -0
  37. package/lib/timeout.d.ts.map +1 -0
  38. package/lib/types.d.ts +6 -4
  39. package/lib/types.d.ts.map +1 -0
  40. package/lib/types.js.map +1 -1
  41. package/lib/url.d.ts.map +1 -0
  42. package/lib/verifyMerkleBranch.d.ts.map +1 -0
  43. package/lib/waitFor.d.ts.map +1 -0
  44. package/lib/yaml/index.d.ts.map +1 -0
  45. package/lib/yaml/int.d.ts.map +1 -0
  46. package/lib/yaml/schema.d.ts.map +1 -0
  47. package/lib/yaml/schema.js.map +1 -1
  48. package/package.json +11 -8
  49. package/src/assert.ts +86 -0
  50. package/src/base64.ts +9 -0
  51. package/src/bytes/browser.ts +123 -0
  52. package/src/bytes/index.ts +29 -0
  53. package/src/bytes/nodejs.ts +63 -0
  54. package/src/bytes.ts +84 -0
  55. package/src/command.ts +74 -0
  56. package/src/diff.ts +234 -0
  57. package/src/err.ts +105 -0
  58. package/src/errors.ts +73 -0
  59. package/src/ethConversion.ts +12 -0
  60. package/src/fetch.ts +188 -0
  61. package/src/format.ts +119 -0
  62. package/src/index.ts +28 -0
  63. package/src/iterator.ts +10 -0
  64. package/src/logger.ts +20 -0
  65. package/src/map.ts +108 -0
  66. package/src/math.ts +55 -0
  67. package/src/metrics.ts +73 -0
  68. package/src/notNullish.ts +11 -0
  69. package/src/objects.ts +102 -0
  70. package/src/promise.ts +163 -0
  71. package/src/retry.ts +75 -0
  72. package/src/sleep.ts +32 -0
  73. package/src/sort.ts +9 -0
  74. package/src/timeout.ts +27 -0
  75. package/src/types.ts +48 -0
  76. package/src/url.ts +29 -0
  77. package/src/verifyMerkleBranch.ts +27 -0
  78. package/src/waitFor.ts +87 -0
  79. package/src/yaml/index.ts +12 -0
  80. package/src/yaml/int.ts +190 -0
  81. package/src/yaml/schema.ts +8 -0
package/src/err.ts ADDED
@@ -0,0 +1,105 @@
1
+ const symErr = Symbol("err");
2
+
3
+ export type Err<T> = {[symErr]: true; error: T};
4
+
5
+ export type Result<T, E> = T | Err<E>;
6
+
7
+ export function Err<T>(error: T): Err<T> {
8
+ return {[symErr]: true, error};
9
+ }
10
+
11
+ /**
12
+ * Typeguard for Err<T>. Allows the pattern
13
+ * ```ts
14
+ * function getNumSquare(): Result<number, Error> {
15
+ * const value = getNum();
16
+ * if (isErr(value)) {
17
+ * return value; // return as error
18
+ * }
19
+ * return value ** 2;
20
+ * }
21
+ * function getNum(): Result<number, Error>
22
+ * ```
23
+ * Since the non-error is not wrapped, it uses a symbol to prevent collisions
24
+ */
25
+ export function isErr<T, E>(result: Result<T, E>): result is Err<E> {
26
+ return result !== null && typeof result === "object" && (result as Err<E>)[symErr] === true;
27
+ }
28
+
29
+ /**
30
+ * Given an array of results, run a function only on an array of ok results.
31
+ * Returns a new array of results with same length as `results` where the ok
32
+ * value may be Err or T2.
33
+ */
34
+ export function mapOkResults<T1, T2, E>(
35
+ results: Result<T1, E>[],
36
+ fn: (items: T1[]) => Result<T2, E>[]
37
+ ): Result<T2, E>[] {
38
+ const oks: T1[] = [];
39
+
40
+ for (let i = 0; i < results.length; i++) {
41
+ const result = results[i];
42
+
43
+ if (!isErr(result)) {
44
+ oks.push(result);
45
+ }
46
+ }
47
+
48
+ const outOksResults = fn(oks);
49
+ if (outOksResults.length !== oks.length) {
50
+ throw Error("mapOkResults fn must return same length");
51
+ }
52
+
53
+ const outResults: Result<T2, E>[] = [];
54
+
55
+ for (let i = 0, j = 0; i < results.length; i++) {
56
+ const result = results[i];
57
+
58
+ if (isErr(result)) {
59
+ outResults.push(result);
60
+ } else {
61
+ outResults.push(outOksResults[j]);
62
+ j++;
63
+ }
64
+ }
65
+
66
+ return outResults;
67
+ }
68
+
69
+ /**
70
+ * See {@link mapOkResults} but `fn` is async
71
+ */
72
+ export async function mapOkResultsAsync<T1, T2, E>(
73
+ results: Result<T1, E>[],
74
+ fn: (items: T1[]) => Promise<Result<T2, E>[]>
75
+ ): Promise<Result<T2, E>[]> {
76
+ const oks: T1[] = [];
77
+
78
+ for (let i = 0; i < results.length; i++) {
79
+ const result = results[i];
80
+
81
+ if (!isErr(result)) {
82
+ oks.push(result);
83
+ }
84
+ }
85
+
86
+ const outOksResults = await fn(oks);
87
+ if (outOksResults.length !== oks.length) {
88
+ throw Error("mapOkResults fn must return same length");
89
+ }
90
+
91
+ const outResults: Result<T2, E>[] = [];
92
+
93
+ for (let i = 0, j = 0; i < results.length; i++) {
94
+ const result = results[i];
95
+
96
+ if (isErr(result)) {
97
+ outResults.push(result);
98
+ } else {
99
+ outResults.push(outOksResults[j]);
100
+ j++;
101
+ }
102
+ }
103
+
104
+ return outResults;
105
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,73 @@
1
+ export type LodestarErrorMetaData = Record<string, string | number | null>;
2
+ export type LodestarErrorObject = {
3
+ message: string;
4
+ stack: string;
5
+ className: string;
6
+ type: LodestarErrorMetaData;
7
+ };
8
+ export type FromObjectFn = (object: LodestarErrorObject) => Error;
9
+
10
+ /**
11
+ * Generic Lodestar error with attached metadata
12
+ */
13
+ export class LodestarError<T extends {code: string}> extends Error {
14
+ type: T;
15
+ constructor(type: T, message?: string, stack?: string) {
16
+ super(message || type.code);
17
+ this.type = type;
18
+ if (stack) this.stack = stack;
19
+ }
20
+
21
+ getMetadata(): LodestarErrorMetaData {
22
+ return this.type;
23
+ }
24
+
25
+ /**
26
+ * Get the metadata and the stacktrace for the error.
27
+ */
28
+ toObject(): LodestarErrorObject {
29
+ return {
30
+ type: this.getMetadata(),
31
+ message: this.message ?? "",
32
+ stack: this.stack ?? "",
33
+ className: this.constructor.name,
34
+ };
35
+ }
36
+
37
+ static fromObject(obj: LodestarErrorObject): LodestarError<{code: string}> {
38
+ return new LodestarError(obj.type as {code: string}, obj.message, obj.stack);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Throw this error when an upstream abort signal aborts
44
+ */
45
+ export class ErrorAborted extends Error {
46
+ constructor(message?: string) {
47
+ super(`Aborted ${message || ""}`);
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Throw this error when wrapped timeout expires
53
+ */
54
+ export class TimeoutError extends Error {
55
+ constructor(message?: string) {
56
+ super(`Timeout ${message || ""}`);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Returns true if arg `e` is an instance of `ErrorAborted`
62
+ */
63
+ export function isErrorAborted(e: unknown): e is ErrorAborted {
64
+ return e instanceof ErrorAborted;
65
+ }
66
+
67
+ /**
68
+ * Extend an existing error by appending a string to its `e.message`
69
+ */
70
+ export function extendError(e: Error, appendMessage: string): Error {
71
+ e.message = `${e.message} - ${appendMessage}`;
72
+ return e;
73
+ }
@@ -0,0 +1,12 @@
1
+ export const ETH_TO_GWEI = BigInt(10 ** 9);
2
+ export const GWEI_TO_WEI = BigInt(10 ** 9);
3
+ export const ETH_TO_WEI = ETH_TO_GWEI * GWEI_TO_WEI;
4
+
5
+ type EthNumeric = bigint;
6
+
7
+ /**
8
+ * Convert gwei to wei.
9
+ */
10
+ export function gweiToWei(gwei: EthNumeric): EthNumeric {
11
+ return gwei * GWEI_TO_WEI;
12
+ }
package/src/fetch.ts ADDED
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Native fetch with transparent and consistent error handling
3
+ *
4
+ * [MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch)
5
+ */
6
+ async function wrappedFetch(url: string | URL, init?: RequestInit): Promise<Response> {
7
+ try {
8
+ // This function wraps global `fetch` which should only be directly called here
9
+ // biome-ignore lint/style/noRestrictedGlobals: We need to use global `fetch`
10
+ return await fetch(url, init);
11
+ } catch (e) {
12
+ throw new FetchError(url, e);
13
+ }
14
+ }
15
+
16
+ export {wrappedFetch as fetch};
17
+
18
+ export function isFetchError(e: unknown): e is FetchError {
19
+ return e instanceof FetchError;
20
+ }
21
+
22
+ export type FetchErrorType = "failed" | "input" | "aborted" | "timeout" | "unknown";
23
+
24
+ type FetchErrorCause = NativeFetchFailedError["cause"] | NativeFetchInputError["cause"];
25
+
26
+ export class FetchError extends Error {
27
+ type: FetchErrorType;
28
+ code: string;
29
+ cause?: FetchErrorCause;
30
+
31
+ constructor(url: string | URL, e: unknown) {
32
+ if (isNativeFetchFailedError(e)) {
33
+ super(`Request to ${url.toString()} failed, reason: ${e.cause.message}`);
34
+ this.type = "failed";
35
+ this.code = e.cause.code || "ERR_FETCH_FAILED";
36
+ this.cause = e.cause;
37
+ } else if (isNativeFetchInputError(e)) {
38
+ // For input errors the e.message is more detailed
39
+ super(e.message);
40
+ this.type = "input";
41
+ this.code = e.cause.code || "ERR_INVALID_INPUT";
42
+ this.cause = e.cause;
43
+ } else if (isNativeFetchAbortError(e)) {
44
+ super(`Request to ${url.toString()} was aborted`);
45
+ this.type = "aborted";
46
+ this.code = "ERR_ABORTED";
47
+ } else if (isNativeFetchTimeoutError(e)) {
48
+ super(`Request to ${url.toString()} timed out`);
49
+ this.type = "timeout";
50
+ this.code = "ERR_TIMEOUT";
51
+ }
52
+ // There are few incompatibilities related to `fetch` with NodeJS
53
+ // So we have to wrap those cases here explicitly
54
+ // https://github.com/oven-sh/bun/issues/20486
55
+ else if (isBunError(e) && e.code === "ConnectionRefused") {
56
+ super("TypeError: fetch failed");
57
+ this.type = "failed";
58
+ this.code = "ENOTFOUND";
59
+ this.cause = e as unknown as FetchErrorCause;
60
+ } else if (isBunError(e) && e.code === "ECONNRESET") {
61
+ super("TypeError: fetch failed");
62
+ this.type = "failed";
63
+ this.code = "UND_ERR_SOCKET";
64
+ this.cause = e as unknown as FetchErrorCause;
65
+ } else if (isBun && (e as Error).message.includes("protocol must be")) {
66
+ super("fetch failed");
67
+ this.type = "failed";
68
+ this.code = "ERR_FETCH_FAILED";
69
+ this.cause = e as unknown as FetchErrorCause;
70
+ } else if ((e as Error).message.includes("URL is invalid")) {
71
+ super("Failed to parse URL from invalid-url");
72
+ this.type = "input";
73
+ this.code = "ERR_INVALID_URL";
74
+ this.cause = e as unknown as FetchErrorCause;
75
+ } else {
76
+ super((e as Error).message);
77
+ this.type = "unknown";
78
+ this.code = "ERR_UNKNOWN";
79
+ }
80
+ this.name = this.constructor.name;
81
+ }
82
+ }
83
+
84
+ type NativeFetchError = TypeError & {
85
+ cause: Error & {
86
+ code?: string;
87
+ };
88
+ };
89
+
90
+ /**
91
+ * ```
92
+ * TypeError: fetch failed
93
+ * cause: Error: connect ECONNREFUSED 127.0.0.1:9596
94
+ * errno: -111,
95
+ * code: 'ECONNREFUSED',
96
+ * syscall: 'connect',
97
+ * address: '127.0.0.1',
98
+ * port: 9596
99
+ * ---------------------------
100
+ * TypeError: fetch failed
101
+ * cause: Error: getaddrinfo ENOTFOUND non-existent-domain
102
+ * errno: -3008,
103
+ * code: 'ENOTFOUND',
104
+ * syscall: 'getaddrinfo',
105
+ * hostname: 'non-existent-domain'
106
+ * ---------------------------
107
+ * TypeError: fetch failed
108
+ * cause: SocketError: other side closed
109
+ * code: 'UND_ERR_SOCKET',
110
+ * socket: {}
111
+ * ---------------------------
112
+ * TypeError: fetch failed
113
+ * cause: Error: unknown scheme
114
+ * [cause]: undefined
115
+ * ```
116
+ */
117
+ type NativeFetchFailedError = NativeFetchError & {
118
+ message: "fetch failed";
119
+ cause: {
120
+ errno?: string;
121
+ syscall?: string;
122
+ address?: string;
123
+ port?: string;
124
+ hostname?: string;
125
+ socket?: object;
126
+ [prop: string]: unknown;
127
+ };
128
+ };
129
+
130
+ /**
131
+ * ```
132
+ * TypeError: Failed to parse URL from invalid-url
133
+ * [cause]: TypeError [ERR_INVALID_URL]: Invalid URL
134
+ * input: 'invalid-url',
135
+ * code: 'ERR_INVALID_URL'
136
+ * ```
137
+ */
138
+ type NativeFetchInputError = NativeFetchError & {
139
+ cause: {
140
+ input: unknown;
141
+ };
142
+ };
143
+
144
+ /**
145
+ * ```
146
+ * DOMException [AbortError]: This operation was aborted
147
+ * ```
148
+ */
149
+ type NativeFetchAbortError = DOMException & {
150
+ name: "AbortError";
151
+ };
152
+
153
+ /**
154
+ * ```
155
+ * DOMException [TimeoutError]: The operation was aborted due to timeout
156
+ * ```
157
+ */
158
+ type NativeFetchTimeoutError = DOMException & {
159
+ name: "TimeoutError";
160
+ };
161
+
162
+ function isNativeFetchError(e: unknown): e is NativeFetchError {
163
+ return e instanceof TypeError && (e as NativeFetchError).cause instanceof Error;
164
+ }
165
+
166
+ function isNativeFetchFailedError(e: unknown): e is NativeFetchFailedError {
167
+ return isNativeFetchError(e) && (e as NativeFetchFailedError).message === "fetch failed";
168
+ }
169
+
170
+ function isNativeFetchInputError(e: unknown): e is NativeFetchInputError {
171
+ return isNativeFetchError(e) && (e as NativeFetchInputError).cause.input !== undefined;
172
+ }
173
+
174
+ function isNativeFetchAbortError(e: unknown): e is NativeFetchAbortError {
175
+ return e instanceof DOMException && (e as NativeFetchAbortError).name === "AbortError";
176
+ }
177
+
178
+ function isNativeFetchTimeoutError(e: unknown): e is NativeFetchTimeoutError {
179
+ return e instanceof DOMException && (e as NativeFetchTimeoutError).name === "TimeoutError";
180
+ }
181
+
182
+ const isBun = "bun" in process.versions;
183
+
184
+ type BunError = {code: string; path: string; errno: number; message: string};
185
+
186
+ function isBunError(e: unknown): e is BunError {
187
+ return isBun && typeof e === "object" && e !== null && "code" in e && "path" in e && "errno" in e && "message" in e;
188
+ }
package/src/format.ts ADDED
@@ -0,0 +1,119 @@
1
+ import {toRootHex} from "./bytes/index.js";
2
+ import {ETH_TO_WEI} from "./ethConversion.js";
3
+
4
+ /**
5
+ * Format bytes as `0x1234…1234`
6
+ * 4 bytes can represent 4294967296 values, so the chance of collision is low
7
+ */
8
+ export function prettyBytes(root: Uint8Array | string): string {
9
+ const str = typeof root === "string" ? root : toRootHex(root);
10
+ return `${str.slice(0, 6)}…${str.slice(-4)}`;
11
+ }
12
+
13
+ /**
14
+ * Format bytes as `0x1234…`
15
+ * Paired with block numbers or slots, it can still act as a decent identify-able format
16
+ */
17
+ export function prettyBytesShort(root: Uint8Array | string): string {
18
+ const str = typeof root === "string" ? root : toRootHex(root);
19
+ return `${str.slice(0, 6)}…`;
20
+ }
21
+
22
+ /**
23
+ * Truncate and format bytes as `0x123456789abc`
24
+ * 6 bytes is sufficient to avoid collisions and it allows to easily look up
25
+ * values on explorers like beaconcha.in while improving readability of logs
26
+ */
27
+ export function truncBytes(root: Uint8Array | string): string {
28
+ const str = typeof root === "string" ? root : toRootHex(root);
29
+ return str.slice(0, 14);
30
+ }
31
+
32
+ /**
33
+ * Format a bigint value as a decimal string
34
+ */
35
+ export function formatBigDecimal(numerator: bigint, denominator: bigint, maxDecimalFactor: bigint): string {
36
+ const full = numerator / denominator;
37
+ const fraction = ((numerator - full * denominator) * maxDecimalFactor) / denominator;
38
+
39
+ // zeros to be added post decimal are number of zeros in maxDecimalFactor - number of digits in fraction
40
+ const zerosPostDecimal = String(maxDecimalFactor).length - 1 - String(fraction).length;
41
+ return `${full}.${"0".repeat(zerosPostDecimal)}${fraction}`;
42
+ }
43
+
44
+ // display upto 5 decimal places
45
+ const MAX_DECIMAL_FACTOR = BigInt("100000");
46
+
47
+ /**
48
+ * Format wei as ETH, with up to 5 decimals
49
+ */
50
+ export function formatWeiToEth(wei: bigint): string {
51
+ return formatBigDecimal(wei, ETH_TO_WEI, MAX_DECIMAL_FACTOR);
52
+ }
53
+
54
+ /**
55
+ * Format wei as ETH, with up to 5 decimals and append ' ETH'
56
+ */
57
+ export function prettyWeiToEth(wei: bigint): string {
58
+ return `${formatWeiToEth(wei)} ETH`;
59
+ }
60
+
61
+ /**
62
+ * Format milliseconds to time format HH:MM:SS.ms
63
+ */
64
+ export function prettyMsToTime(timeMs: number): string {
65
+ const date = new Date(0, 0, 0, 0, 0, 0, timeMs);
66
+ return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}`;
67
+ }
68
+
69
+ /**
70
+ * Remove 0x prefix from a string
71
+ */
72
+ export function strip0xPrefix(hex: string): string {
73
+ return hex.startsWith("0x") ? hex.slice(2) : hex;
74
+ }
75
+
76
+ export function groupSequentialIndices(indices: number[]): string[] {
77
+ if (indices.length === 0) {
78
+ return [];
79
+ }
80
+
81
+ // Get unique values and sort them
82
+ const uniqueValues = Array.from(new Set(indices)).sort((a, b) => a - b);
83
+
84
+ const result: string[] = [];
85
+ let start = uniqueValues[0];
86
+ let end = uniqueValues[0];
87
+
88
+ for (let i = 1; i < uniqueValues.length; i++) {
89
+ const current = uniqueValues[i];
90
+
91
+ if (current === end + 1) {
92
+ end = current; // extend the range
93
+ } else {
94
+ result.push(start === end ? `${start}` : `${start}-${end}`);
95
+ start = current;
96
+ end = current;
97
+ }
98
+ }
99
+
100
+ // Push the last range
101
+ result.push(start === end ? `${start}` : `${start}-${end}`);
102
+
103
+ return result;
104
+ }
105
+
106
+ /**
107
+ * Pretty print indices from an array of numbers.
108
+ *
109
+ * example:
110
+ * ```ts
111
+ * const indices = [1, 3, 109, 110, 111, 112, 113, 127];
112
+ * console.log(prettyPrintIndices(indices));
113
+ * // `1,3,110-113,127`
114
+ * ```
115
+ */
116
+ export function prettyPrintIndices(indices: number[]): string {
117
+ const increments = groupSequentialIndices(indices);
118
+ return `[${increments.join(", ")}]`;
119
+ }
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ export * from "./assert.js";
2
+ export * from "./base64.js";
3
+ export * from "./bytes/index.js";
4
+ export * from "./bytes.js";
5
+ export * from "./command.js";
6
+ export * from "./diff.js";
7
+ export * from "./err.js";
8
+ export * from "./errors.js";
9
+ export * from "./ethConversion.js";
10
+ export * from "./fetch.js";
11
+ export * from "./format.js";
12
+ export * from "./iterator.js";
13
+ export * from "./logger.js";
14
+ export * from "./map.js";
15
+ export * from "./math.js";
16
+ export * from "./metrics.js";
17
+ export * from "./notNullish.js";
18
+ export * from "./objects.js";
19
+ export * from "./promise.js";
20
+ export {type RetryOptions, retry} from "./retry.js";
21
+ export * from "./sleep.js";
22
+ export * from "./sort.js";
23
+ export * from "./timeout.js";
24
+ export {type RecursivePartial, type RequiredSelective, bnToNum} from "./types.js";
25
+ export * from "./url.js";
26
+ export * from "./verifyMerkleBranch.js";
27
+ export * from "./waitFor.js";
28
+ export * from "./yaml/index.js";
@@ -0,0 +1,10 @@
1
+ // ES2024 onward we have Array.fromAsync which does exactly this
2
+ // This function is here as wrapper to be deleted later when we upgrade
3
+ // minimum nodejs requirement to > 22
4
+ export async function fromAsync<T>(iter: AsyncIterable<T>): Promise<T[]> {
5
+ const arr: T[] = [];
6
+ for await (const v of iter) {
7
+ arr.push(v);
8
+ }
9
+ return arr;
10
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Interface of a generic Lodestar logger. For implementations, see `@lodestar/logger`
3
+ */
4
+ export type Logger = Record<Exclude<LogLevel, LogLevel.trace>, LogHandler>;
5
+
6
+ export enum LogLevel {
7
+ error = "error",
8
+ warn = "warn",
9
+ info = "info",
10
+ verbose = "verbose",
11
+ debug = "debug",
12
+ trace = "trace",
13
+ }
14
+
15
+ export const LogLevels = Object.values(LogLevel);
16
+
17
+ export type LogHandler = (message: string, context?: LogData, error?: Error) => void;
18
+
19
+ export type LogDataBasic = string | number | bigint | boolean | null | undefined;
20
+ export type LogData = LogDataBasic | Record<string, LogDataBasic> | LogDataBasic[] | Record<string, LogDataBasic>[];
package/src/map.ts ADDED
@@ -0,0 +1,108 @@
1
+ export class MapDef<K, V> extends Map<K, V> {
2
+ constructor(private readonly getDefault: () => V) {
3
+ super();
4
+ }
5
+
6
+ getOrDefault(key: K): V {
7
+ let value = super.get(key);
8
+ if (value === undefined) {
9
+ value = this.getDefault();
10
+ this.set(key, value);
11
+ }
12
+ return value;
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Extends MapDef but ensures that there always a max of `maxKeys` keys
18
+ */
19
+ export class MapDefMax<K, V> {
20
+ private readonly map = new Map<K, V>();
21
+
22
+ constructor(
23
+ private readonly getDefault: () => V,
24
+ private readonly maxKeys: number
25
+ ) {}
26
+
27
+ getOrDefault(key: K): V {
28
+ let value = this.map.get(key);
29
+ if (value === undefined) {
30
+ value = this.getDefault();
31
+ this.map.set(key, value);
32
+ pruneSetToMax(this.map, this.maxKeys);
33
+ }
34
+ return value;
35
+ }
36
+
37
+ get(key: K): V | undefined {
38
+ return this.map.get(key);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * 2 dimensions Es6 Map
44
+ */
45
+ export class Map2d<K1, K2, V> {
46
+ readonly map = new Map<K1, Map<K2, V>>();
47
+
48
+ get(k1: K1, k2: K2): V | undefined {
49
+ return this.map.get(k1)?.get(k2);
50
+ }
51
+
52
+ set(k1: K1, k2: K2, v: V): void {
53
+ let map2 = this.map.get(k1);
54
+ if (!map2) {
55
+ map2 = new Map<K2, V>();
56
+ this.map.set(k1, map2);
57
+ }
58
+ map2.set(k2, v);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * 2 dimensions Es6 Map + regular array
64
+ */
65
+ export class Map2dArr<K1, V> {
66
+ readonly map = new Map<K1, V[]>();
67
+
68
+ get(k1: K1, idx: number): V | undefined {
69
+ return this.map.get(k1)?.[idx];
70
+ }
71
+
72
+ set(k1: K1, idx: number, v: V): void {
73
+ let arr = this.map.get(k1);
74
+ if (!arr) {
75
+ arr = [];
76
+ this.map.set(k1, arr);
77
+ }
78
+ arr[idx] = v;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Prune an arbitrary set removing the first keys to have a set.size === maxItems.
84
+ * Returns the count of deleted items.
85
+ *
86
+ * Keys can be sorted by `compareFn` to get more control over which items to prune first
87
+ */
88
+ export function pruneSetToMax<T>(
89
+ set: Set<T> | Map<T, unknown>,
90
+ maxItems: number,
91
+ compareFn?: (a: T, b: T) => number
92
+ ): number {
93
+ let itemsToDelete = set.size - maxItems;
94
+ const deletedItems = Math.max(0, itemsToDelete);
95
+
96
+ if (itemsToDelete > 0) {
97
+ const keys = compareFn ? Array.from(set.keys()).sort(compareFn) : set.keys();
98
+ for (const key of keys) {
99
+ set.delete(key);
100
+ itemsToDelete--;
101
+ if (itemsToDelete <= 0) {
102
+ break;
103
+ }
104
+ }
105
+ }
106
+
107
+ return deletedItems;
108
+ }