@lodestar/utils 1.35.0-dev.e18102ed8c → 1.35.0-dev.f45a2be721
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/lib/assert.d.ts.map +1 -0
- package/lib/base64.d.ts.map +1 -0
- package/lib/bytes/browser.d.ts.map +1 -0
- package/lib/bytes/index.d.ts.map +1 -0
- package/lib/bytes/nodejs.d.ts.map +1 -0
- package/lib/bytes.d.ts.map +1 -0
- package/lib/command.d.ts.map +1 -0
- package/lib/diff.d.ts.map +1 -0
- package/lib/err.d.ts.map +1 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/ethConversion.d.ts.map +1 -0
- package/lib/fetch.d.ts.map +1 -0
- package/lib/format.d.ts.map +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/iterator.d.ts.map +1 -0
- package/lib/logger.d.ts.map +1 -0
- package/lib/map.d.ts.map +1 -0
- package/lib/math.d.ts.map +1 -0
- package/lib/metrics.d.ts.map +1 -0
- package/lib/notNullish.d.ts.map +1 -0
- package/lib/objects.d.ts.map +1 -0
- package/lib/promise.d.ts.map +1 -0
- package/lib/retry.d.ts.map +1 -0
- package/lib/sleep.d.ts.map +1 -0
- package/lib/sort.d.ts.map +1 -0
- package/lib/timeout.d.ts.map +1 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/url.d.ts.map +1 -0
- package/lib/verifyMerkleBranch.d.ts.map +1 -0
- package/lib/waitFor.d.ts.map +1 -0
- package/lib/yaml/index.d.ts.map +1 -0
- package/lib/yaml/int.d.ts.map +1 -0
- package/lib/yaml/schema.d.ts.map +1 -0
- package/package.json +11 -8
- package/src/assert.ts +86 -0
- package/src/base64.ts +9 -0
- package/src/bytes/browser.ts +123 -0
- package/src/bytes/index.ts +29 -0
- package/src/bytes/nodejs.ts +63 -0
- package/src/bytes.ts +84 -0
- package/src/command.ts +74 -0
- package/src/diff.ts +234 -0
- package/src/err.ts +105 -0
- package/src/errors.ts +73 -0
- package/src/ethConversion.ts +12 -0
- package/src/fetch.ts +188 -0
- package/src/format.ts +119 -0
- package/src/index.ts +28 -0
- package/src/iterator.ts +10 -0
- package/src/logger.ts +20 -0
- package/src/map.ts +108 -0
- package/src/math.ts +55 -0
- package/src/metrics.ts +73 -0
- package/src/notNullish.ts +11 -0
- package/src/objects.ts +102 -0
- package/src/promise.ts +163 -0
- package/src/retry.ts +75 -0
- package/src/sleep.ts +32 -0
- package/src/sort.ts +9 -0
- package/src/timeout.ts +27 -0
- package/src/types.ts +48 -0
- package/src/url.ts +29 -0
- package/src/verifyMerkleBranch.ts +27 -0
- package/src/waitFor.ts +87 -0
- package/src/yaml/index.ts +12 -0
- package/src/yaml/int.ts +190 -0
- package/src/yaml/schema.ts +8 -0
package/src/retry.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {ErrorAborted} from "./errors.js";
|
|
2
|
+
import {sleep} from "./sleep.js";
|
|
3
|
+
|
|
4
|
+
export type RetryOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* The maximum amount of times to retry the operation. Default is 5
|
|
7
|
+
*/
|
|
8
|
+
retries?: number;
|
|
9
|
+
/**
|
|
10
|
+
* An optional Function that is invoked after the provided callback throws.
|
|
11
|
+
* It expects a boolean to know if it should retry or not.
|
|
12
|
+
* Useful to make retrying conditional on the type of error thrown.
|
|
13
|
+
*/
|
|
14
|
+
shouldRetry?: (lastError: Error) => boolean;
|
|
15
|
+
/**
|
|
16
|
+
* An optional Function that is invoked right before a retry is performed.
|
|
17
|
+
* It's passed the Error that triggered it and a number identifying the attempt.
|
|
18
|
+
* Useful to track number of retries and errors in logs or metrics.
|
|
19
|
+
*/
|
|
20
|
+
onRetry?: (lastError: Error, attempt: number) => unknown;
|
|
21
|
+
/**
|
|
22
|
+
* Milliseconds to wait before retrying again
|
|
23
|
+
*/
|
|
24
|
+
retryDelay?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Abort signal to stop retrying
|
|
27
|
+
*/
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Retry a given function on error.
|
|
33
|
+
* @param fn Async callback to retry. Invoked with 1 parameter
|
|
34
|
+
* A Number identifying the attempt. The absolute first attempt (before any retries) is 1
|
|
35
|
+
* @param opts
|
|
36
|
+
*/
|
|
37
|
+
export async function retry<A>(fn: (attempt: number) => A | Promise<A>, opts?: RetryOptions): Promise<A> {
|
|
38
|
+
const maxRetries = opts?.retries ?? 5;
|
|
39
|
+
// Number of retries + the initial attempt
|
|
40
|
+
const maxAttempts = maxRetries + 1;
|
|
41
|
+
const shouldRetry = opts?.shouldRetry;
|
|
42
|
+
const onRetry = opts?.onRetry;
|
|
43
|
+
|
|
44
|
+
let lastError: Error = Error("RetryError");
|
|
45
|
+
for (let i = 1; i <= maxAttempts; i++) {
|
|
46
|
+
// If not the first attempt
|
|
47
|
+
if (i > 1) {
|
|
48
|
+
if (opts?.signal?.aborted) {
|
|
49
|
+
throw new ErrorAborted("retry");
|
|
50
|
+
}
|
|
51
|
+
// Invoke right before retrying
|
|
52
|
+
onRetry?.(lastError, i);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
return await fn(i);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
lastError = e as Error;
|
|
59
|
+
|
|
60
|
+
if (i === maxAttempts) {
|
|
61
|
+
// Reached maximum number of attempts, there's no need to check if we should retry
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (shouldRetry && !shouldRetry(lastError)) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (opts?.retryDelay !== undefined) {
|
|
70
|
+
await sleep(opts?.retryDelay, opts?.signal);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw lastError;
|
|
75
|
+
}
|
package/src/sleep.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {ErrorAborted} from "./errors.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Abortable sleep function. Cleans everything on all cases preventing leaks
|
|
5
|
+
* On abort throws ErrorAborted
|
|
6
|
+
*/
|
|
7
|
+
export async function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
8
|
+
if (ms < 0) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
if (signal?.aborted) return reject(new ErrorAborted());
|
|
14
|
+
|
|
15
|
+
let onDone: () => void = () => {};
|
|
16
|
+
|
|
17
|
+
const timeout = setTimeout(() => {
|
|
18
|
+
onDone();
|
|
19
|
+
resolve();
|
|
20
|
+
}, ms);
|
|
21
|
+
const onAbort = (): void => {
|
|
22
|
+
onDone();
|
|
23
|
+
reject(new ErrorAborted());
|
|
24
|
+
};
|
|
25
|
+
if (signal) signal.addEventListener("abort", onAbort);
|
|
26
|
+
|
|
27
|
+
onDone = () => {
|
|
28
|
+
clearTimeout(timeout);
|
|
29
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
}
|
package/src/sort.ts
ADDED
package/src/timeout.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {anySignal} from "any-signal";
|
|
2
|
+
import {ErrorAborted, TimeoutError} from "./errors.js";
|
|
3
|
+
import {sleep} from "./sleep.js";
|
|
4
|
+
|
|
5
|
+
export async function withTimeout<T>(
|
|
6
|
+
asyncFn: (timeoutAndParentSignal?: AbortSignal) => Promise<T>,
|
|
7
|
+
timeoutMs: number,
|
|
8
|
+
signal?: AbortSignal
|
|
9
|
+
): Promise<T> {
|
|
10
|
+
if (signal?.aborted) {
|
|
11
|
+
throw new ErrorAborted();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const timeoutAbortController = new AbortController();
|
|
15
|
+
const timeoutAndParentSignal = anySignal([timeoutAbortController.signal, ...(signal ? [signal] : [])]);
|
|
16
|
+
|
|
17
|
+
async function timeoutPromise(signal: AbortSignal): Promise<never> {
|
|
18
|
+
await sleep(timeoutMs, signal);
|
|
19
|
+
throw new TimeoutError();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
return await Promise.race([asyncFn(timeoutAndParentSignal), timeoutPromise(timeoutAndParentSignal)]);
|
|
24
|
+
} finally {
|
|
25
|
+
timeoutAbortController.abort();
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively make all properties optional
|
|
3
|
+
*/
|
|
4
|
+
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
5
|
+
type Builtin = Primitive | Date | Error | RegExp;
|
|
6
|
+
|
|
7
|
+
export type RecursivePartial<T> =
|
|
8
|
+
// stop on built-ins (incl. Error) and functions
|
|
9
|
+
T extends Builtin
|
|
10
|
+
? T
|
|
11
|
+
: // arrays and readonly arrays
|
|
12
|
+
T extends ReadonlyArray<infer U>
|
|
13
|
+
? ReadonlyArray<RecursivePartial<U>>
|
|
14
|
+
: T extends Array<infer U>
|
|
15
|
+
? Array<RecursivePartial<U>>
|
|
16
|
+
: // (optionally: Map/Set support)
|
|
17
|
+
T extends Map<infer K, infer V>
|
|
18
|
+
? Map<RecursivePartial<K>, RecursivePartial<V>>
|
|
19
|
+
: T extends Set<infer U>
|
|
20
|
+
? Set<RecursivePartial<U>>
|
|
21
|
+
: // plain objects
|
|
22
|
+
T extends object
|
|
23
|
+
? {[P in keyof T]?: RecursivePartial<T[P]>}
|
|
24
|
+
: // fallback (shouldn’t be hit often)
|
|
25
|
+
T;
|
|
26
|
+
|
|
27
|
+
/** Type safe wrapper for Number constructor that takes 'any' */
|
|
28
|
+
export function bnToNum(bn: bigint): number {
|
|
29
|
+
return Number(bn);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type NonEmptyArray<T> = [T, ...T[]];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* ArrayToTuple converts an `Array<T>` to `[T, ...T]`
|
|
36
|
+
*
|
|
37
|
+
* eg: `[1, 2, 3]` from type `number[]` to `[number, number, number]`
|
|
38
|
+
*/
|
|
39
|
+
export type ArrayToTuple<Tuple extends NonEmptyArray<unknown>> = {
|
|
40
|
+
[Index in keyof Tuple]: Tuple[Index];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Convert optional attributes of an object to required
|
|
45
|
+
*/
|
|
46
|
+
export type RequiredSelective<T, Keys extends keyof T> = T & {
|
|
47
|
+
[K in Keys]-?: T[K];
|
|
48
|
+
};
|
package/src/url.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function isValidHttpUrl(urlStr: string): boolean {
|
|
2
|
+
let url: URL;
|
|
3
|
+
try {
|
|
4
|
+
url = new URL(urlStr);
|
|
5
|
+
|
|
6
|
+
// `new URL` encodes the username/password with the userinfo percent-encode set.
|
|
7
|
+
// This means the `%` character is not encoded, but others are (such as `=`).
|
|
8
|
+
// If a username/password contain a `%`, they will not be able to be decoded.
|
|
9
|
+
//
|
|
10
|
+
// Make sure that we can successfully decode the username and password here.
|
|
11
|
+
//
|
|
12
|
+
// Unfortunately this means we don't accept every character supported by RFC-3986.
|
|
13
|
+
decodeURIComponent(url.username);
|
|
14
|
+
decodeURIComponent(url.password);
|
|
15
|
+
} catch (_) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sanitize URL to prevent leaking user credentials in logs or metrics
|
|
24
|
+
*
|
|
25
|
+
* Note: `urlStr` must be a valid URL
|
|
26
|
+
*/
|
|
27
|
+
export function toPrintableUrl(urlStr: string): string {
|
|
28
|
+
return new URL(urlStr).origin;
|
|
29
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {digest, digest64} from "@chainsafe/as-sha256";
|
|
2
|
+
|
|
3
|
+
export function hash(...inputs: Uint8Array[]): Uint8Array {
|
|
4
|
+
return digest(Buffer.concat(inputs));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Verify that the given ``leaf`` is on the merkle branch ``proof``
|
|
9
|
+
* starting with the given ``root``.
|
|
10
|
+
*/
|
|
11
|
+
export function verifyMerkleBranch(
|
|
12
|
+
leaf: Uint8Array,
|
|
13
|
+
proof: Uint8Array[],
|
|
14
|
+
depth: number,
|
|
15
|
+
index: number,
|
|
16
|
+
root: Uint8Array
|
|
17
|
+
): boolean {
|
|
18
|
+
let value = leaf;
|
|
19
|
+
for (let i = 0; i < depth; i++) {
|
|
20
|
+
if (Math.floor(index / 2 ** i) % 2) {
|
|
21
|
+
value = digest64(Buffer.concat([proof[i], value]));
|
|
22
|
+
} else {
|
|
23
|
+
value = digest64(Buffer.concat([value, proof[i]]));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return Buffer.from(value.buffer, value.byteOffset, value.byteLength).equals(root);
|
|
27
|
+
}
|
package/src/waitFor.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {ErrorAborted, TimeoutError} from "./errors.js";
|
|
2
|
+
|
|
3
|
+
export type WaitForOpts = {
|
|
4
|
+
/** Time in milliseconds between checking condition */
|
|
5
|
+
interval?: number;
|
|
6
|
+
/** Time in milliseconds to wait before throwing TimeoutError */
|
|
7
|
+
timeout?: number;
|
|
8
|
+
/** Abort signal to stop waiting for condition by throwing ErrorAborted */
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Wait for a condition to be true
|
|
14
|
+
*/
|
|
15
|
+
export function waitFor(condition: () => boolean, opts: WaitForOpts = {}): Promise<void> {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const {interval = 10, timeout = Infinity, signal} = opts;
|
|
18
|
+
|
|
19
|
+
if (signal?.aborted) {
|
|
20
|
+
return reject(new ErrorAborted());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (condition()) {
|
|
24
|
+
return resolve();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let onDone: () => void = () => {};
|
|
28
|
+
|
|
29
|
+
const timeoutId = setTimeout(() => {
|
|
30
|
+
onDone();
|
|
31
|
+
reject(new TimeoutError());
|
|
32
|
+
}, timeout);
|
|
33
|
+
|
|
34
|
+
const intervalId = setInterval(() => {
|
|
35
|
+
if (condition()) {
|
|
36
|
+
onDone();
|
|
37
|
+
resolve();
|
|
38
|
+
}
|
|
39
|
+
}, interval);
|
|
40
|
+
|
|
41
|
+
const onAbort = (): void => {
|
|
42
|
+
onDone();
|
|
43
|
+
reject(new ErrorAborted());
|
|
44
|
+
};
|
|
45
|
+
if (signal) signal.addEventListener("abort", onAbort);
|
|
46
|
+
|
|
47
|
+
onDone = () => {
|
|
48
|
+
clearTimeout(timeoutId);
|
|
49
|
+
clearInterval(intervalId);
|
|
50
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ElapsedTimeTracker {
|
|
56
|
+
(): boolean;
|
|
57
|
+
msSinceLastCall: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create a tracker which keeps track of the last time a function was called
|
|
62
|
+
*
|
|
63
|
+
* @param durationMs
|
|
64
|
+
* @returns
|
|
65
|
+
*/
|
|
66
|
+
export function createElapsedTimeTracker({minElapsedTime}: {minElapsedTime: number}): ElapsedTimeTracker {
|
|
67
|
+
// Initialized with undefined as the function has not been called yet
|
|
68
|
+
let lastTimeCalled: number | undefined = undefined;
|
|
69
|
+
|
|
70
|
+
function elapsedTimeTracker(): boolean {
|
|
71
|
+
const now = Date.now();
|
|
72
|
+
const msSinceLastCall = now - (lastTimeCalled ?? 0);
|
|
73
|
+
|
|
74
|
+
if (msSinceLastCall > minElapsedTime) {
|
|
75
|
+
// Do not reset timer if called before timer elapsed
|
|
76
|
+
lastTimeCalled = now;
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return Object.assign(elapsedTimeTracker, {
|
|
83
|
+
get msSinceLastCall() {
|
|
84
|
+
return Date.now() - (lastTimeCalled ?? 0);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import yaml from "js-yaml";
|
|
2
|
+
import {schema} from "./schema.js";
|
|
3
|
+
|
|
4
|
+
const {load, dump} = yaml;
|
|
5
|
+
|
|
6
|
+
export function loadYaml<T = Record<string, unknown>>(yaml: string): T {
|
|
7
|
+
return load(yaml, {schema}) as T;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function dumpYaml(yaml: unknown): string {
|
|
11
|
+
return dump(yaml, {schema});
|
|
12
|
+
}
|
package/src/yaml/int.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Forked from https://github.com/nodeca/js-yaml/blob/master/lib/type/int.js
|
|
2
|
+
// Currently only supports loading ints
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
|
|
5
|
+
const {Type} = yaml;
|
|
6
|
+
|
|
7
|
+
function isHexCode(c: number): boolean {
|
|
8
|
+
return (
|
|
9
|
+
// 0, 9
|
|
10
|
+
(0x30 <= c && c <= 0x39) ||
|
|
11
|
+
// A, F
|
|
12
|
+
(0x41 <= c && c <= 0x46) ||
|
|
13
|
+
// a, f
|
|
14
|
+
(0x61 <= c && c <= 0x66)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isOctCode(c: number): boolean {
|
|
19
|
+
return 0x30 /* 0 */ <= c && c <= 0x37 /* 7 */;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isDecCode(c: number): boolean {
|
|
23
|
+
return 0x30 /* 0 */ <= c && c <= 0x39 /* 9 */;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveYamlInteger(data: string): boolean {
|
|
27
|
+
if (data === null) return false;
|
|
28
|
+
|
|
29
|
+
const max = data.length;
|
|
30
|
+
let ch: string,
|
|
31
|
+
index = 0,
|
|
32
|
+
hasDigits = false;
|
|
33
|
+
|
|
34
|
+
if (!max) return false;
|
|
35
|
+
|
|
36
|
+
ch = data[index];
|
|
37
|
+
|
|
38
|
+
// sign
|
|
39
|
+
if (ch === "-" || ch === "+") {
|
|
40
|
+
ch = data[++index];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (ch === "0") {
|
|
44
|
+
// 0
|
|
45
|
+
if (index + 1 === max) return true;
|
|
46
|
+
ch = data[++index];
|
|
47
|
+
|
|
48
|
+
// base 2, base 8, base 16
|
|
49
|
+
|
|
50
|
+
if (ch === "b") {
|
|
51
|
+
// base 2
|
|
52
|
+
index++;
|
|
53
|
+
|
|
54
|
+
for (; index < max; index++) {
|
|
55
|
+
ch = data[index];
|
|
56
|
+
if (ch === "_") continue;
|
|
57
|
+
if (ch !== "0" && ch !== "1") return false;
|
|
58
|
+
hasDigits = true;
|
|
59
|
+
}
|
|
60
|
+
return hasDigits && ch !== "_";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (ch === "x") {
|
|
64
|
+
// base 16
|
|
65
|
+
index++;
|
|
66
|
+
|
|
67
|
+
for (; index < max; index++) {
|
|
68
|
+
ch = data[index];
|
|
69
|
+
if (ch === "_") continue;
|
|
70
|
+
if (!isHexCode(data.charCodeAt(index))) return false;
|
|
71
|
+
hasDigits = true;
|
|
72
|
+
}
|
|
73
|
+
return hasDigits && ch !== "_";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// base 8
|
|
77
|
+
for (; index < max; index++) {
|
|
78
|
+
ch = data[index];
|
|
79
|
+
if (ch === "_") continue;
|
|
80
|
+
if (!isOctCode(data.charCodeAt(index))) return false;
|
|
81
|
+
hasDigits = true;
|
|
82
|
+
}
|
|
83
|
+
return hasDigits && ch !== "_";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// base 10 (except 0) or base 60
|
|
87
|
+
|
|
88
|
+
// value should not start with `_`;
|
|
89
|
+
if (ch === "_") return false;
|
|
90
|
+
|
|
91
|
+
for (; index < max; index++) {
|
|
92
|
+
ch = data[index];
|
|
93
|
+
if (ch === "_") continue;
|
|
94
|
+
if (ch === ":") break;
|
|
95
|
+
if (!isDecCode(data.charCodeAt(index))) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
hasDigits = true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Should have digits and should not end with `_`
|
|
102
|
+
if (!hasDigits || ch === "_") return false;
|
|
103
|
+
|
|
104
|
+
// if !base60 - done;
|
|
105
|
+
if (ch !== ":") return true;
|
|
106
|
+
|
|
107
|
+
// base60 almost not used, no needs to optimize
|
|
108
|
+
return /^(:[0-5]?[0-9])+$/.test(data.slice(index));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function constructYamlInteger(data: string): bigint {
|
|
112
|
+
let value: string | bigint = data,
|
|
113
|
+
sign = 1,
|
|
114
|
+
ch: string,
|
|
115
|
+
base: number | bigint;
|
|
116
|
+
const digits: number[] = [];
|
|
117
|
+
|
|
118
|
+
if (value.indexOf("_") !== -1) {
|
|
119
|
+
value = value.replace(/_/g, "");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ch = value[0];
|
|
123
|
+
|
|
124
|
+
if (ch === "-" || ch === "+") {
|
|
125
|
+
if (ch === "-") sign = -1;
|
|
126
|
+
value = value.slice(1);
|
|
127
|
+
ch = value[0];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (value === "0") return BigInt(0);
|
|
131
|
+
|
|
132
|
+
if (ch === "0") {
|
|
133
|
+
if (value[1] === "b" || value[1] === "x") return BigInt(value) * BigInt(sign);
|
|
134
|
+
return BigInt("0o" + value) * BigInt(sign);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (value.indexOf(":") !== -1) {
|
|
138
|
+
for (const v of value.split(":")) {
|
|
139
|
+
digits.unshift(parseInt(v, 10));
|
|
140
|
+
}
|
|
141
|
+
value = BigInt(0);
|
|
142
|
+
base = BigInt(1);
|
|
143
|
+
|
|
144
|
+
for (const d of digits) {
|
|
145
|
+
value = (BigInt(value) + BigInt(base)) * BigInt(d);
|
|
146
|
+
base = BigInt(base) * BigInt(60);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return value * BigInt(sign);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return BigInt(value) * BigInt(sign);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isInteger(object: unknown): boolean {
|
|
156
|
+
return typeof object === "bigint" || typeof object === "number";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export const intType = new Type("tag:yaml.org,2002:int", {
|
|
160
|
+
kind: "scalar",
|
|
161
|
+
resolve: resolveYamlInteger,
|
|
162
|
+
construct: constructYamlInteger,
|
|
163
|
+
predicate: isInteger,
|
|
164
|
+
instanceOf: BigInt,
|
|
165
|
+
represent: {
|
|
166
|
+
// @ts-expect-error
|
|
167
|
+
binary: function binary(obj: number) {
|
|
168
|
+
return obj >= 0 ? "0b" + obj.toString(2) : "-0b" + obj.toString(2).slice(1);
|
|
169
|
+
},
|
|
170
|
+
// @ts-expect-error
|
|
171
|
+
octal: function octal(obj: number) {
|
|
172
|
+
return obj >= 0 ? "0" + obj.toString(8) : "-0" + obj.toString(8).slice(1);
|
|
173
|
+
},
|
|
174
|
+
// @ts-expect-error
|
|
175
|
+
decimal: function decimal(obj: number) {
|
|
176
|
+
return obj.toString(10);
|
|
177
|
+
},
|
|
178
|
+
// @ts-expect-error
|
|
179
|
+
hexadecimal: function hexadecimal(obj: number) {
|
|
180
|
+
return obj >= 0 ? "0x" + obj.toString(16).toUpperCase() : "-0x" + obj.toString(16).toUpperCase().slice(1);
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
defaultStyle: "decimal",
|
|
184
|
+
styleAliases: {
|
|
185
|
+
binary: [2, "bin"],
|
|
186
|
+
octal: [8, "oct"],
|
|
187
|
+
decimal: [10, "dec"],
|
|
188
|
+
hexadecimal: [16, "hex"],
|
|
189
|
+
},
|
|
190
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import yml, {FAILSAFE_SCHEMA, Type} from "js-yaml";
|
|
2
|
+
import {intType} from "./int.js";
|
|
3
|
+
|
|
4
|
+
export const schema = FAILSAFE_SCHEMA.extend({
|
|
5
|
+
// @ts-expect-error
|
|
6
|
+
implicit: [yml.types.null as Type, yml.types.bool as Type, intType, yml.types.float as Type],
|
|
7
|
+
explicit: [],
|
|
8
|
+
});
|