@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
@@ -0,0 +1,123 @@
1
+ // "0".charCodeAt(0) = 48
2
+ const CHAR_CODE_0 = 48;
3
+ // "x".charCodeAt(0) = 120
4
+ const CHAR_CODE_X = 120;
5
+
6
+ export function toHex(bytes: Uint8Array): string {
7
+ const charCodes = new Array<number>(bytes.length * 2 + 2);
8
+ charCodes[0] = CHAR_CODE_0;
9
+ charCodes[1] = CHAR_CODE_X;
10
+
11
+ bytesIntoCharCodes(bytes, charCodes);
12
+ return String.fromCharCode(...charCodes);
13
+ }
14
+
15
+ const rootCharCodes = new Array<number>(32 * 2 + 2);
16
+ rootCharCodes[0] = CHAR_CODE_0;
17
+ rootCharCodes[1] = CHAR_CODE_X;
18
+
19
+ /**
20
+ * Convert a Uint8Array, length 32, to 0x-prefixed hex string
21
+ */
22
+ export function toRootHex(root: Uint8Array): string {
23
+ if (root.length !== 32) {
24
+ throw Error(`Expect root to be 32 bytes, got ${root.length}`);
25
+ }
26
+
27
+ bytesIntoCharCodes(root, rootCharCodes);
28
+ return String.fromCharCode(...rootCharCodes);
29
+ }
30
+
31
+ const pubkeyCharCodes = new Array<number>(48 * 2 + 2);
32
+ pubkeyCharCodes[0] = CHAR_CODE_0;
33
+ pubkeyCharCodes[1] = CHAR_CODE_X;
34
+
35
+ /**
36
+ * Convert a Uint8Array, length 48, to 0x-prefixed hex string
37
+ */
38
+ export function toPubkeyHex(pubkey: Uint8Array): string {
39
+ if (pubkey.length !== CHAR_CODE_0) {
40
+ throw Error(`Expect pubkey to be 48 bytes, got ${pubkey.length}`);
41
+ }
42
+
43
+ bytesIntoCharCodes(pubkey, pubkeyCharCodes);
44
+ return String.fromCharCode(...pubkeyCharCodes);
45
+ }
46
+
47
+ export function fromHex(hex: string): Uint8Array {
48
+ if (typeof hex !== "string") {
49
+ throw new Error(`hex argument type ${typeof hex} must be of type string`);
50
+ }
51
+
52
+ if (hex.startsWith("0x")) {
53
+ hex = hex.slice(2);
54
+ }
55
+
56
+ if (hex.length % 2 !== 0) {
57
+ throw new Error(`hex string length ${hex.length} must be multiple of 2`);
58
+ }
59
+
60
+ const byteLen = hex.length / 2;
61
+ const bytes = new Uint8Array(byteLen);
62
+ for (let i = 0; i < byteLen; i++) {
63
+ const byte2i = charCodeToByte(hex.charCodeAt(i * 2));
64
+ const byte2i1 = charCodeToByte(hex.charCodeAt(i * 2 + 1));
65
+ bytes[i] = (byte2i << 4) | byte2i1;
66
+ }
67
+ return bytes;
68
+ }
69
+
70
+ export function fromHexInto(hex: string, buffer: Uint8Array): void {
71
+ if (hex.startsWith("0x")) {
72
+ hex = hex.slice(2);
73
+ }
74
+
75
+ if (hex.length !== buffer.length * 2) {
76
+ throw new Error(`hex string length ${hex.length} must be exactly double the buffer length ${buffer.length}`);
77
+ }
78
+
79
+ for (let i = 0; i < buffer.length; i++) {
80
+ const byte2i = charCodeToByte(hex.charCodeAt(i * 2));
81
+ const byte2i1 = charCodeToByte(hex.charCodeAt(i * 2 + 1));
82
+ buffer[i] = (byte2i << 4) | byte2i1;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Populate charCodes from bytes. Note that charCodes index 0 and 1 ("0x") are not populated.
88
+ */
89
+ function bytesIntoCharCodes(bytes: Uint8Array, charCodes: number[]): void {
90
+ if (bytes.length * 2 + 2 !== charCodes.length) {
91
+ throw Error(`Expect charCodes to be of length ${bytes.length * 2 + 2}, got ${charCodes.length}`);
92
+ }
93
+
94
+ for (let i = 0; i < bytes.length; i++) {
95
+ const byte = bytes[i];
96
+ const first = (byte & 0xf0) >> 4;
97
+ const second = byte & 0x0f;
98
+
99
+ // "0".charCodeAt(0) = 48
100
+ // "a".charCodeAt(0) = 97 => delta = 87
101
+ charCodes[2 + 2 * i] = first < 10 ? first + 48 : first + 87;
102
+ charCodes[2 + 2 * i + 1] = second < 10 ? second + 48 : second + 87;
103
+ }
104
+ }
105
+
106
+ function charCodeToByte(charCode: number): number {
107
+ // "a".charCodeAt(0) = 97, "f".charCodeAt(0) = 102 => delta = 87
108
+ if (charCode >= 97 && charCode <= 102) {
109
+ return charCode - 87;
110
+ }
111
+
112
+ // "A".charCodeAt(0) = 65, "F".charCodeAt(0) = 70 => delta = 55
113
+ if (charCode >= 65 && charCode <= 70) {
114
+ return charCode - 55;
115
+ }
116
+
117
+ // "0".charCodeAt(0) = 48, "9".charCodeAt(0) = 57 => delta = 48
118
+ if (charCode >= 48 && charCode <= 57) {
119
+ return charCode - 48;
120
+ }
121
+
122
+ throw new Error(`Invalid hex character code: ${charCode}`);
123
+ }
@@ -0,0 +1,29 @@
1
+ import {
2
+ fromHex as browserFromHex,
3
+ fromHexInto as browserFromHexInto,
4
+ toHex as browserToHex,
5
+ toPubkeyHex as browserToPubkeyHex,
6
+ toRootHex as browserToRootHex,
7
+ } from "./browser.js";
8
+ import {
9
+ fromHex as nodeFromHex,
10
+ toHex as nodeToHex,
11
+ toPubkeyHex as nodeToPubkeyHex,
12
+ toRootHex as nodeToRootHex,
13
+ } from "./nodejs.js";
14
+
15
+ let toHex = browserToHex;
16
+ let toRootHex = browserToRootHex;
17
+ let toPubkeyHex = browserToPubkeyHex;
18
+ let fromHex = browserFromHex;
19
+ // there is no fromHexInto for NodeJs as the performance of browserFromHexInto is >100x faster
20
+ const fromHexInto = browserFromHexInto;
21
+
22
+ if (typeof Buffer !== "undefined") {
23
+ toHex = nodeToHex;
24
+ toRootHex = nodeToRootHex;
25
+ toPubkeyHex = nodeToPubkeyHex;
26
+ fromHex = nodeFromHex;
27
+ }
28
+
29
+ export {toHex, toRootHex, toPubkeyHex, fromHex, fromHexInto};
@@ -0,0 +1,63 @@
1
+ export function toHex(buffer: Uint8Array | Parameters<typeof Buffer.from>[0]): string {
2
+ if (Buffer.isBuffer(buffer)) {
3
+ return "0x" + buffer.toString("hex");
4
+ }
5
+ if (buffer instanceof Uint8Array) {
6
+ return "0x" + Buffer.from(buffer.buffer, buffer.byteOffset, buffer.length).toString("hex");
7
+ }
8
+ return "0x" + Buffer.from(buffer).toString("hex");
9
+ }
10
+
11
+ // Shared buffer to convert root to hex
12
+ let rootBuf: Buffer | undefined;
13
+
14
+ /**
15
+ * Convert a Uint8Array, length 32, to 0x-prefixed hex string
16
+ */
17
+ export function toRootHex(root: Uint8Array): string {
18
+ if (root.length !== 32) {
19
+ throw Error(`Expect root to be 32 bytes, got ${root.length}`);
20
+ }
21
+
22
+ if (rootBuf === undefined) {
23
+ rootBuf = Buffer.alloc(32);
24
+ }
25
+
26
+ rootBuf.set(root);
27
+ return `0x${rootBuf.toString("hex")}`;
28
+ }
29
+
30
+ // Shared buffer to convert pubkey to hex
31
+ let pubkeyBuf: Buffer | undefined;
32
+
33
+ export function toPubkeyHex(pubkey: Uint8Array): string {
34
+ if (pubkey.length !== 48) {
35
+ throw Error(`Expect pubkey to be 48 bytes, got ${pubkey.length}`);
36
+ }
37
+
38
+ if (pubkeyBuf === undefined) {
39
+ pubkeyBuf = Buffer.alloc(48);
40
+ }
41
+
42
+ pubkeyBuf.set(pubkey);
43
+ return `0x${pubkeyBuf.toString("hex")}`;
44
+ }
45
+
46
+ export function fromHex(hex: string): Uint8Array {
47
+ if (typeof hex !== "string") {
48
+ throw new Error(`hex argument type ${typeof hex} must be of type string`);
49
+ }
50
+
51
+ if (hex.startsWith("0x")) {
52
+ hex = hex.slice(2);
53
+ }
54
+
55
+ if (hex.length % 2 !== 0) {
56
+ throw new Error(`hex string length ${hex.length} must be multiple of 2`);
57
+ }
58
+
59
+ const b = Buffer.from(hex, "hex");
60
+ return new Uint8Array(b.buffer, b.byteOffset, b.length);
61
+ }
62
+
63
+ /// the performance of fromHexInto using a preallocated buffer is very bad compared to browser so I moved it to the benchmark
package/src/bytes.ts ADDED
@@ -0,0 +1,84 @@
1
+ import {toBigIntBE, toBigIntLE, toBufferBE, toBufferLE} from "bigint-buffer";
2
+
3
+ type Endianness = "le" | "be";
4
+
5
+ const hexByByte: string[] = [];
6
+ /**
7
+ * @deprecated Use toHex() instead.
8
+ */
9
+ export function toHexString(bytes: Uint8Array): string {
10
+ let hex = "0x";
11
+ for (const byte of bytes) {
12
+ if (!hexByByte[byte]) {
13
+ hexByByte[byte] = byte < 16 ? "0" + byte.toString(16) : byte.toString(16);
14
+ }
15
+ hex += hexByByte[byte];
16
+ }
17
+ return hex;
18
+ }
19
+
20
+ /**
21
+ * Return a byte array from a number or BigInt
22
+ */
23
+ export function intToBytes(value: bigint | number, length: number, endianness: Endianness = "le"): Buffer {
24
+ return bigIntToBytes(BigInt(value), length, endianness);
25
+ }
26
+
27
+ /**
28
+ * Convert byte array in LE to integer.
29
+ */
30
+ export function bytesToInt(value: Uint8Array, endianness: Endianness = "le"): number {
31
+ return Number(bytesToBigInt(value, endianness));
32
+ }
33
+
34
+ export function bigIntToBytes(value: bigint, length: number, endianness: Endianness = "le"): Buffer {
35
+ if (endianness === "le") {
36
+ return toBufferLE(value, length);
37
+ }
38
+ if (endianness === "be") {
39
+ return toBufferBE(value, length);
40
+ }
41
+ throw new Error("endianness must be either 'le' or 'be'");
42
+ }
43
+
44
+ export function bytesToBigInt(value: Uint8Array, endianness: Endianness = "le"): bigint {
45
+ if (!(value instanceof Uint8Array)) {
46
+ throw new TypeError("expected a Uint8Array");
47
+ }
48
+
49
+ if (endianness === "le") {
50
+ return toBigIntLE(value as Buffer);
51
+ }
52
+ if (endianness === "be") {
53
+ return toBigIntBE(value as Buffer);
54
+ }
55
+ throw new Error("endianness must be either 'le' or 'be'");
56
+ }
57
+
58
+ export function formatBytes(bytes: number): string {
59
+ if (bytes < 0) {
60
+ throw new Error("bytes must be a positive number, got " + bytes);
61
+ }
62
+
63
+ if (bytes === 0) {
64
+ return "0 Bytes";
65
+ }
66
+
67
+ // size of a kb
68
+ const k = 1024;
69
+
70
+ // only support up to GB
71
+ const units = ["Bytes", "KB", "MB", "GB"];
72
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), units.length - 1);
73
+ const formattedSize = (bytes / Math.pow(k, i)).toFixed(2);
74
+
75
+ return `${formattedSize} ${units[i]}`;
76
+ }
77
+
78
+ export function xor(a: Uint8Array, b: Uint8Array): Uint8Array {
79
+ const length = Math.min(a.length, b.length);
80
+ for (let i = 0; i < length; i++) {
81
+ a[i] = a[i] ^ b[i];
82
+ }
83
+ return a;
84
+ }
package/src/command.ts ADDED
@@ -0,0 +1,74 @@
1
+ import type {Argv, Options} from "yargs";
2
+
3
+ export interface CliExample {
4
+ command: string;
5
+ title?: string;
6
+ description?: string;
7
+ }
8
+
9
+ // biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
10
+ export interface CliOptionDefinition<T = any> extends Options {
11
+ example?: Omit<CliExample, "title">;
12
+ // Ensure `type` property matches type of `T`
13
+ type: T extends string
14
+ ? "string"
15
+ : T extends number
16
+ ? "number"
17
+ : T extends boolean
18
+ ? "boolean"
19
+ : T extends Array<unknown>
20
+ ? "array"
21
+ : never;
22
+ }
23
+
24
+ export type CliCommandOptions<OwnArgs> = Required<{
25
+ [K in keyof OwnArgs]: undefined extends OwnArgs[K]
26
+ ? CliOptionDefinition<OwnArgs[K]>
27
+ : // If arg cannot be undefined it must specify a default value or be provided by the user
28
+ CliOptionDefinition<OwnArgs[K]> & (Required<Pick<Options, "default">> | {demandOption: true});
29
+ }>;
30
+
31
+ // biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
32
+ export interface CliCommand<OwnArgs = Record<never, never>, ParentArgs = Record<never, never>, R = any> {
33
+ command: string;
34
+ describe: string;
35
+ /**
36
+ * The folder in docs/pages that the cli.md should be placed in. If not provided no
37
+ * cli flags page will be generated for the command
38
+ */
39
+ docsFolder?: string;
40
+ examples?: CliExample[];
41
+ options?: CliCommandOptions<OwnArgs>;
42
+ // 1st arg: any = free own sub command options
43
+ // 2nd arg: subcommand parent options is = to this command options + parent options
44
+ // biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
45
+ subcommands?: CliCommand<any, OwnArgs & ParentArgs>[];
46
+ handler?: (args: OwnArgs & ParentArgs) => Promise<R>;
47
+ }
48
+
49
+ /**
50
+ * Register a CliCommand type to yargs. Recursively registers subcommands too.
51
+ * @param yargs
52
+ * @param cliCommand
53
+ */
54
+
55
+ // biome-ignore lint/suspicious/noExplicitAny: We need to use `any` type here
56
+ export function registerCommandToYargs(yargs: Argv, cliCommand: CliCommand<any, any>): void {
57
+ yargs.command({
58
+ command: cliCommand.command,
59
+ describe: cliCommand.describe,
60
+ builder: (yargsBuilder) => {
61
+ yargsBuilder.options(cliCommand.options ?? {});
62
+ for (const subcommand of cliCommand.subcommands ?? []) {
63
+ registerCommandToYargs(yargsBuilder, subcommand);
64
+ }
65
+ if (cliCommand.examples) {
66
+ for (const example of cliCommand.examples) {
67
+ yargsBuilder.example(`$0 ${example.command}`, example.description ?? "");
68
+ }
69
+ }
70
+ return yargs;
71
+ },
72
+ handler: cliCommand.handler ?? function emptyHandler(): void {},
73
+ });
74
+ }
package/src/diff.ts ADDED
@@ -0,0 +1,234 @@
1
+ import fs from "node:fs";
2
+
3
+ const primitiveTypeof = ["number", "string", "bigint", "boolean"];
4
+ export type BufferType = Uint8Array | Uint32Array;
5
+ export type PrimitiveType = number | string | bigint | boolean | BufferType;
6
+ export type DiffableCollection = Record<string | number, PrimitiveType>;
7
+ export type Diffable = PrimitiveType | Array<PrimitiveType> | DiffableCollection;
8
+
9
+ export interface Diff {
10
+ objectPath: string;
11
+ errorMessage?: string;
12
+ val1: Diffable;
13
+ val2: Diffable;
14
+ }
15
+
16
+ export function diffUint8Array(val1: Uint8Array, val2: PrimitiveType, objectPath: string): Diff[] {
17
+ if (!(val2 instanceof Uint8Array)) {
18
+ return [
19
+ {
20
+ objectPath,
21
+ errorMessage: `val1${objectPath} is a Uint8Array, but val2${objectPath} is not`,
22
+ val1,
23
+ val2,
24
+ },
25
+ ];
26
+ }
27
+ const hex1 = Buffer.from(val1).toString("hex");
28
+ const hex2 = Buffer.from(val2).toString("hex");
29
+ if (hex1 !== hex2) {
30
+ return [
31
+ {
32
+ objectPath,
33
+ val1: `0x${hex1}`,
34
+ val2: `0x${hex2}`,
35
+ },
36
+ ];
37
+ }
38
+ return [];
39
+ }
40
+
41
+ export function diffUint32Array(val1: Uint32Array, val2: PrimitiveType, objectPath: string): Diff[] {
42
+ if (!(val2 instanceof Uint32Array)) {
43
+ return [
44
+ {
45
+ objectPath,
46
+ errorMessage: `val1${objectPath} is a Uint32Array, but val2${objectPath} is not`,
47
+ val1,
48
+ val2,
49
+ },
50
+ ];
51
+ }
52
+ const diffs: Diff[] = [];
53
+ val1.forEach((value, index) => {
54
+ const value2 = val2[index];
55
+ if (value !== value2) {
56
+ diffs.push({
57
+ objectPath: `${objectPath}[${index}]`,
58
+ val1: `0x${value.toString(16).padStart(8, "0")}`,
59
+ val2: value2 ? `0x${val2[index].toString(16).padStart(8, "0")}` : "undefined",
60
+ });
61
+ }
62
+ });
63
+ return diffs;
64
+ }
65
+
66
+ function diffPrimitiveValue(val1: PrimitiveType, val2: PrimitiveType, objectPath: string): Diff[] {
67
+ if (val1 instanceof Uint8Array) {
68
+ return diffUint8Array(val1, val2, objectPath);
69
+ }
70
+ if (val1 instanceof Uint32Array) {
71
+ return diffUint32Array(val1, val2, objectPath);
72
+ }
73
+
74
+ const diff = {objectPath, val1, val2} as Diff;
75
+ const type1 = typeof val1;
76
+ if (!primitiveTypeof.includes(type1)) {
77
+ diff.errorMessage = `val1${objectPath} is not a supported type`;
78
+ }
79
+ const type2 = typeof val2;
80
+ if (!primitiveTypeof.includes(type2)) {
81
+ diff.errorMessage = `val2${objectPath} is not a supported type`;
82
+ }
83
+ if (type1 !== type2) {
84
+ diff.errorMessage = `val1${objectPath} is not the same type as val2${objectPath}`;
85
+ }
86
+ if (val1 !== val2) {
87
+ return [diff];
88
+ }
89
+ return [];
90
+ }
91
+
92
+ function isPrimitiveValue(val: unknown): val is PrimitiveType {
93
+ if (Array.isArray(val)) return false;
94
+ if (typeof val === "object") {
95
+ return val instanceof Uint8Array || val instanceof Uint32Array;
96
+ }
97
+ return true;
98
+ }
99
+
100
+ function isDiffable(val: unknown): val is Diffable {
101
+ return !(typeof val === "function" || typeof val === "symbol" || typeof val === "undefined" || val === null);
102
+ }
103
+
104
+ export function getDiffs(val1: Diffable, val2: Diffable, objectPath: string): Diff[] {
105
+ if (isPrimitiveValue(val1)) {
106
+ if (!isPrimitiveValue(val2)) {
107
+ return [
108
+ {
109
+ objectPath,
110
+ errorMessage: `val1${objectPath} is a primitive value and val2${objectPath} is not`,
111
+ val1,
112
+ val2,
113
+ },
114
+ ];
115
+ }
116
+ return diffPrimitiveValue(val1, val2, objectPath);
117
+ }
118
+
119
+ const isArray = Array.isArray(val1);
120
+ let errorMessage: string | undefined;
121
+ if (isArray && !Array.isArray(val2)) {
122
+ errorMessage = `val1${objectPath} is an array and val2${objectPath} is not`;
123
+ } else if (typeof val1 === "object" && typeof val2 !== "object") {
124
+ errorMessage = `val1${objectPath} is a nested object and val2${objectPath} is not`;
125
+ }
126
+ if (errorMessage) {
127
+ return [
128
+ {
129
+ objectPath,
130
+ errorMessage,
131
+ val1,
132
+ val2,
133
+ },
134
+ ];
135
+ }
136
+
137
+ const diffs: Diff[] = [];
138
+ for (const [index, value] of Object.entries(val1)) {
139
+ if (!isDiffable(value)) {
140
+ diffs.push({objectPath, val1, val2, errorMessage: `val1${objectPath} is not Diffable`});
141
+ continue;
142
+ }
143
+ const value2 = (val2 as DiffableCollection)[index];
144
+ if (!isDiffable(value2)) {
145
+ diffs.push({objectPath, val1, val2, errorMessage: `val2${objectPath} is not Diffable`});
146
+ continue;
147
+ }
148
+ const innerPath = isArray ? `${objectPath}[${index}]` : `${objectPath}.${index}`;
149
+ diffs.push(...getDiffs(value, value2, innerPath));
150
+ }
151
+ return diffs;
152
+ }
153
+
154
+ /**
155
+ * Find the different values on complex, nested objects. Outputs the path through the object to
156
+ * each value that does not match from val1 and val2. Optionally can output the values that differ.
157
+ *
158
+ * For objects that differ greatly, can write to a file instead of the terminal for analysis
159
+ *
160
+ * ## Example
161
+ * ```ts
162
+ * const obj1 = {
163
+ * key1: {
164
+ * key2: [
165
+ * { key3: 1 },
166
+ * { key3: new Uint8Array([1, 2, 3]) }
167
+ * ]
168
+ * },
169
+ * key4: new Uint32Array([1, 2, 3]),
170
+ * key5: 362436
171
+ * };
172
+ *
173
+ * const obj2 = {
174
+ * key1: {
175
+ * key2: [
176
+ * { key3: 1 },
177
+ * { key3: new Uint8Array([1, 2, 4]) }
178
+ * ]
179
+ * },
180
+ * key4: new Uint32Array([1, 2, 4])
181
+ * key5: true
182
+ * };
183
+ *
184
+ * diffObjects(obj1, obj2, true);
185
+ *
186
+ *
187
+ * ```
188
+ *
189
+ * ## Output
190
+ * ```sh
191
+ * val.key1.key2[1].key3
192
+ * - 0x010203
193
+ * - 0x010204
194
+ * val.key4[2]
195
+ * - 0x00000003
196
+ * - 0x00000004
197
+ * val.key5
198
+ * val1.key5 is not the same type as val2.key5
199
+ * - 362436
200
+ * - true
201
+ * ```
202
+ */
203
+ export function diff(val1: unknown, val2: unknown, outputValues = false, filename?: string): void {
204
+ if (!isDiffable(val1)) {
205
+ // biome-ignore lint/suspicious/noConsole: We need to log to the console
206
+ console.log("val1 is not Diffable");
207
+ return;
208
+ }
209
+ if (!isDiffable(val2)) {
210
+ // biome-ignore lint/suspicious/noConsole: We need to log to the console
211
+ console.log("val2 is not Diffable");
212
+ return;
213
+ }
214
+ const diffs = getDiffs(val1, val2, "");
215
+ let output = "";
216
+ if (diffs.length) {
217
+ for (const diff of diffs) {
218
+ let diffOutput = `value${diff.objectPath}`;
219
+ if (diff.errorMessage) {
220
+ diffOutput += `\n ${diff.errorMessage}`;
221
+ }
222
+ if (outputValues) {
223
+ diffOutput += `\n - ${diff.val1.toString()}\n - ${diff.val2.toString()}\n`;
224
+ }
225
+ output += `${diffOutput}\n`;
226
+ }
227
+ if (filename) {
228
+ fs.writeFileSync(filename, output);
229
+ } else {
230
+ // biome-ignore lint/suspicious/noConsole: We need to log to the console
231
+ console.log(output);
232
+ }
233
+ }
234
+ }