Repository: denoland/std
Pull request #6480: feat(encoding/unstable): add encode/decodeRawHex and rewrite underlying code
Author: login: BlackAsLight
Labels: name: encoding
Created: 2025-03-13T06:59:35Z | Merged: 2025-03-18T08:09:58Z

Related: https://github.com/denoland/std/pull/6453

This pull request does three things:
~1. Introduces an unstable version of `encodeHex` and `decodeHex` that takes an optional redundant format argument merely to match that of the unstable base64 and base32 which have a few variants.~
2. Rewrites the underlying encoding/decoding code to introduce better memory management and performance gains.
3. Introduces `encodeRawHex` and `decodeRawHex` which is a more low level version of the previous functions meant to enable users to encode/decode part of a buffer in place.


Diff:
diff --git a/_tools/check_docs.ts b/_tools/check_docs.ts
index 4a6c3ed16bf1..84623f068407 100644
--- a/_tools/check_docs.ts
+++ b/_tools/check_docs.ts
@@ -50,8 +50,8 @@ const ENTRY_POINTS = [
   "../encoding/mod.ts",
   "../encoding/unstable_base32.ts",
   "../encoding/unstable_base64.ts",
+  "../encoding/unstable_hex.ts",
   "../encoding/unstable_base64_stream.ts",
-  "../encoding/unstable_base32hex_stream.ts",
   "../encoding/unstable_base32_stream.ts",
   "../encoding/unstable_base32hex_stream.ts",
   "../encoding/unstable_hex_stream.ts",
diff --git a/encoding/_common16.ts b/encoding/_common16.ts
new file mode 100644
index 000000000000..97610f7bda00
--- /dev/null
+++ b/encoding/_common16.ts
@@ -0,0 +1,65 @@
+// Copyright 2018-2025 the Deno authors. MIT license.
+
+import type { Uint8Array_ } from "./_types.ts";
+export type { Uint8Array_ };
+
+/**
+ * Calculate the output size needed to encode a given input size for
+ * {@linkcode encodeRawHex}.
+ *
+ * @param originalSize The size of the input buffer.
+ * @returns The size of the output buffer.
+ *
+ * @example Basic Usage
+ * ```ts
+ * import { assertEquals } from "@std/assert";
+ * import { calcMax } from "@std/encoding/unstable-hex";
+ *
+ * assertEquals(calcMax(1), 2);
+ * ```
+ */
+export function calcMax(originalSize: number): number {
+  return originalSize * 2;
+}
+
+export function encode(
+  buffer: Uint8Array_,
+  i: number,
+  o: number,
+  alphabet: Uint8Array,
+): number {
+  for (; i < buffer.length; ++i) {
+    const x = buffer[i]!;
+    buffer[o++] = alphabet[x >> 4]!;
+    buffer[o++] = alphabet[x & 0xF]!;
+  }
+  return o;
+}
+
+export function decode(
+  buffer: Uint8Array_,
+  i: number,
+  o: number,
+  alphabet: Uint8Array,
+): number {
+  if ((buffer.length - o) % 2 === 1) {
+    throw new TypeError(
+      `Invalid Character (${String.fromCharCode(buffer[buffer.length - 1]!)})`,
+    );
+  }
+
+  i += 1;
+  for (; i < buffer.length; i += 2) {
+    buffer[o++] = (getByte(buffer[i - 1]!, alphabet) << 4) |
+      getByte(buffer[i]!, alphabet);
+  }
+  return o;
+}
+
+function getByte(char: number, alphabet: Uint8Array): number {
+  const byte = alphabet[char] ?? 16;
+  if (byte === 16) { // alphabet.Hex.length
+    throw new TypeError(`Invalid Character (${String.fromCharCode(char)})`);
+  }
+  return byte;
+}
diff --git a/encoding/deno.json b/encoding/deno.json
index 246688086f95..45219ae3a920 100644
--- a/encoding/deno.json
+++ b/encoding/deno.json
@@ -14,6 +14,7 @@
     "./unstable-base64-stream": "./unstable_base64_stream.ts",
     "./base64url": "./base64url.ts",
     "./hex": "./hex.ts",
+    "./unstable-hex": "./unstable_hex.ts",
     "./unstable-hex-stream": "./unstable_hex_stream.ts",
     "./varint": "./varint.ts"
   }
diff --git a/encoding/unstable_hex.ts b/encoding/unstable_hex.ts
new file mode 100644
index 000000000000..448356c95a6a
--- /dev/null
+++ b/encoding/unstable_hex.ts
@@ -0,0 +1,199 @@
+// Copyright 2018-2025 the Deno authors. MIT license.
+// This module is browser compatible.
+
+/**
+ * Functions to encode and decode to and from hexadecimal strings.
+ *
+ * ```ts
+ * import { assertEquals } from "@std/assert";
+ * import { encodeHex, type Uint8Array_ } from "@std/encoding/unstable-hex";
+ *
+ * assertEquals(encodeHex("Hello World"), "48656c6c6f20576f726c64");
+ * assertEquals(
+ *   encodeHex(new TextEncoder().encode("Hello World") as Uint8Array_),
+ *   "48656c6c6f20576f726c64",
+ * );
+ * ```
+ *
+ * @see {@link https://www.rfc-editor.org/rfc/rfc4648.html#section-8}
+ * @experimental **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @module
+ */
+
+import type { Uint8Array_ } from "./_types.ts";
+export type { Uint8Array_ };
+import { calcMax, decode, encode } from "./_common16.ts";
+export { calcMax };
+import { detach } from "./_common_detach.ts";
+
+const alphabet = new TextEncoder().encode("0123456789abcdef");
+const rAlphabet = new Uint8Array(128).fill(16); // alphabet.Hex.length
+alphabet.forEach((byte, i) => rAlphabet[byte] = i);
+new TextEncoder()
+  .encode("ABCDEF")
+  .forEach((byte, i) => rAlphabet[byte] = i + 10);
+
+/**
+ * `encodeHex` takes an input source and encodes it into a hexadecimal string.
+ * If a {@linkcode Uint8Array<ArrayBuffer>} or {@linkcode ArrayBuffer} is
+ * provided, the underlying source will be detached and reused for the encoding.
+ * If you need the input source after providing it to this function, call
+ * `.slice()` to pass in a copy.
+ *
+ * @experimental **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @param input The input source to encode.
+ * @returns The hexadecimal string representation of the input.
+ *
+ * @example Basic Usage
+ * ```ts
+ * import { assertEquals } from "@std/assert";
+ * import { encodeHex, type Uint8Array_ } from "@std/encoding/unstable-hex";
+ *
+ * assertEquals(encodeHex("Hello World"), "48656c6c6f20576f726c64");
+ * assertEquals(
+ *   encodeHex(new TextEncoder().encode("Hello World") as Uint8Array_),
+ *   "48656c6c6f20576f726c64",
+ * );
+ * ```
+ */
+export function encodeHex(
+  input: string | Uint8Array_ | ArrayBuffer,
+): string {
+  if (typeof input === "string") {
+    input = new TextEncoder().encode(input) as Uint8Array_;
+  } else if (input instanceof ArrayBuffer) {
+    input = new Uint8Array(input);
+  }
+  const [output, i] = detach(
+    input as Uint8Array_,
+    calcMax((input as Uint8Array_).length),
+  );
+  encode(output, i, 0, alphabet);
+  return new TextDecoder().decode(output);
+}
+
+/**
+ * `encodeRawHex` is a low-level function that encodes a
+ * {@linkcode Uint8Array<ArrayBuffer>} to hexadecimal in place. The function
+ * assumes that the raw data starts at param {@linkcode i} and ends at the end
+ * of the buffer, and that the entire buffer provided is large enough to hold
+ * the encoded data.
+ *
+ * @experimental **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @param buffer The buffer to encode in place.
+ * @param i The index of where the raw data starts reading from.
+ * @param o The index of where the encoded data starts writing to.
+ * @returns The index of where the encoded data finished writing to.
+ *
+ * @example Basic Usage
+ * ```ts
+ * import { assertEquals } from "@std/assert";
+ * import { calcMax, encodeHex, encodeRawHex } from "@std/encoding/unstable-hex";
+ *
+ * const prefix = new TextEncoder().encode("data:url/fake,");
+ * const input = await Deno.readFile("./deno.lock");
+ *
+ * const originalSize = input.length;
+ * const newSize = prefix.length + calcMax(originalSize);
+ * const i = newSize - originalSize;
+ * const o = prefix.length;
+ *
+ * // deno-lint-ignore no-explicit-any
+ * const output = new Uint8Array((input.buffer as any).transfer(newSize));
+ * output.set(output.subarray(0, originalSize), i);
+ * output.set(prefix);
+ *
+ * encodeRawHex(output, i, o);
+ * assertEquals(
+ *   new TextDecoder().decode(output),
+ *   "data:url/fake," + encodeHex(await Deno.readFile("./deno.lock")),
+ * );
+ * ```
+ */
+export function encodeRawHex(
+  buffer: Uint8Array_,
+  i: number,
+  o: number,
+): number {
+  const max = calcMax(buffer.length - i);
+  if (max > buffer.length - o) throw new RangeError("Buffer too small");
+  return encode(buffer, i, o, alphabet);
+}
+
+/**
+ * `decodeHex` takes an input source and decodes it into a
+ * {@linkcode Uint8Array<ArrayBuffer>} using the specified format.
+ *
+ * @experimental **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @param input The input source to decode.
+ * @returns The decoded {@linkcode Uint8Array<ArrayBuffer>}.
+ *
+ * @example Basic Usage
+ * ```ts
+ * import { assertEquals } from "@std/assert";
+ * import { decodeHex } from "@std/encoding/unstable-hex";
+ *
+ * assertEquals(
+ *   decodeHex("48656c6c6f20576f726c64"),
+ *   new TextEncoder().encode("Hello World"),
+ * );
+ * ```
+ */
+export function decodeHex(
+  input: string,
+): Uint8Array_ {
+  const output = new TextEncoder().encode(input) as Uint8Array_;
+  return output
+    .subarray(0, decode(output, 0, 0, rAlphabet));
+}
+
+/**
+ * `decodeRawHex` is a low-level function that decodes a
+ * {@linkcode Uint8Array<ArrayBuffer>} from hexadecimal in place. Param
+ * {@linkcode i} must be greater than or equal to param {@linkcode o}. The
+ * function assumes that the encoded data starts at param {@linkcode i} and ends
+ * at the end of the buffer.
+ *
+ * @experimental **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @param buffer The buffer to decode in place.
+ * @param i The index of where the encoded data starts reading from.
+ * @param o The index of where the decoded data starts writing to.
+ * @returns The index of where the decoded data finished writing to.
+ *
+ * @example Basic Usage
+ * ```ts
+ * import { assertEquals } from "@std/assert";
+ * import {
+ *   decodeRawHex,
+ *   encodeHex,
+ *   type Uint8Array_,
+ * } from "@std/encoding/unstable-hex";
+ *
+ * let buffer = new TextEncoder().encode(
+ *   "data:url/fake," + encodeHex(await Deno.readFile("./deno.lock")),
+ * ) as Uint8Array_;
+ *
+ * const i = buffer.indexOf(",".charCodeAt(0)) + 1;
+ * const o = decodeRawHex(buffer, i, i);
+ *
+ * buffer = buffer.subarray(i, o);
+ * assertEquals(buffer, await Deno.readFile("./deno.lock"));
+ * ```
+ */
+export function decodeRawHex(
+  buffer: Uint8Array_,
+  i: number,
+  o: number,
+): number {
+  if (i < o) {
+    throw new RangeError(
+      "Input (i) must be greater than or equal to output (o)",
+    );
+  }
+  return decode(buffer, i, o, rAlphabet);
+}
diff --git a/encoding/unstable_hex_test.ts b/encoding/unstable_hex_test.ts
new file mode 100644
index 000000000000..1bdc3f7082f9
--- /dev/null
+++ b/encoding/unstable_hex_test.ts
@@ -0,0 +1,157 @@
+// Copyright 2018-2025 the Deno authors. MIT license.
+
+import { assertEquals, assertThrows } from "@std/assert";
+import { concat } from "@std/bytes";
+import {
+  calcMax,
+  decodeHex,
+  decodeRawHex,
+  encodeHex,
+  encodeRawHex,
+} from "./unstable_hex.ts";
+
+const inputOutput: [string | ArrayBuffer, string][] = [
+  ["", ""],
+  ["Z", "5a"],
+  ["ZZ", "5a5a"],
+  [new Uint8Array(0).fill("Z".charCodeAt(0)).buffer, ""],
+  [new Uint8Array(1).fill("Z".charCodeAt(0)).buffer, "5a"],
+  [new Uint8Array(2).fill("Z".charCodeAt(0)).buffer, "5a5a"],
+];
+
+Deno.test("encodeHex()", () => {
+  for (const [input, hex] of inputOutput) {
+    assertEquals(encodeHex(input.slice(0)), hex);
+  }
+});
+
+Deno.test("encodeHex() subarray", () => {
+  for (const [input, output] of inputOutput) {
+    if (typeof input === "string") continue;
+
+    const buffer = new Uint8Array(10);
+    buffer.set(new Uint8Array(input), 10 - input.byteLength);
+
+    assertEquals(
+      encodeHex(buffer.slice().subarray(10 - input.byteLength)),
+      output,
+    );
+  }
+});
+
+Deno.test("encodeRawHex()", () => {
+  const prefix = new TextEncoder().encode("data:fake/url,");
+  for (const [input, output] of inputOutput) {
+    if (typeof input === "string") continue;
+
+    const buffer = new Uint8Array(prefix.length + calcMax(input.byteLength));
+    buffer.set(prefix);
+    buffer.set(new Uint8Array(input), buffer.length - input.byteLength);
+
+    encodeRawHex(
+      buffer,
+      buffer.length - input.byteLength,
+      prefix.length,
+    );
+    assertEquals(buffer, concat([prefix, new TextEncoder().encode(output)]));
+  }
+});
+
+Deno.test("encodeRawHex() with too small buffer", () => {
+  const prefix = new TextEncoder().encode("data:fake/url,");
+  for (const [input] of inputOutput) {
+    if (typeof input === "string" || input.byteLength === 0) continue;
+
+    const buffer = new Uint8Array(
+      prefix.length + calcMax(input.byteLength) - 2,
+    );
+    buffer.set(prefix);
+    buffer.set(new Uint8Array(input), buffer.length - input.byteLength);
+
+    assertThrows(
+      () =>
+        encodeRawHex(
+          buffer,
+          buffer.length - input.byteLength,
+          prefix.length,
+        ),
+      RangeError,
+      "Buffer too small",
+    );
+  }
+});
+
+Deno.test("decodeHex()", () => {
+  for (const [input, output] of inputOutput) {
+    if (input instanceof ArrayBuffer) continue;
+
+    assertEquals(decodeHex(output), new TextEncoder().encode(input));
+  }
+});
+
+Deno.test("decodeHex() invalid length", () => {
+  for (const [input, output] of inputOutput) {
+    if (input instanceof ArrayBuffer) continue;
+
+    assertThrows(
+      () => decodeHex(output + "a"),
+      TypeError,
+      "Invalid Character (a)",
+    );
+  }
+});
+
+Deno.test("decodeHex() invalid char", () => {
+  for (const [input, output] of inputOutput) {
+    if (input instanceof ArrayBuffer) continue;
+
+    assertThrows(
+      () => decodeHex(".".repeat(2) + output),
+      TypeError,
+      "Invalid Character (.)",
+    );
+  }
+});
+
+Deno.test("decodeRawHex()", () => {
+  const prefix = new TextEncoder().encode("data:fake/url,");
+  for (const [input, output] of inputOutput) {
+    if (typeof input === "string") continue;
+
+    const buffer = concat([prefix, new TextEncoder().encode(output)]);
+    assertEquals(
+      buffer.subarray(
+        prefix.length,
+        decodeRawHex(buffer, prefix.length, prefix.length),
+      ),
+      new Uint8Array(input),
+    );
+  }
+});
+
+Deno.test("decodeRawHex() with invalid offsets", () => {
+  const prefix = new TextEncoder().encode("data:fake/url,");
+  for (const [input, output] of inputOutput) {
+    if (typeof input === "string") continue;
+
+    assertThrows(
+      () =>
+        decodeRawHex(
+          concat([prefix, new TextEncoder().encode(output)]),
+          prefix.length - 2,
+          prefix.length,
+        ),
+      RangeError,
+      "Input (i) must be greater than or equal to output (o)",
+    );
+  }
+});
+
+Deno.test("decodeHex() throws with invalid byte >= 128", () => {
+  const input = new TextDecoder().decode(new Uint8Array(2).fill(200));
+  assertThrows(
+    () => decodeHex(input),
+    TypeError,
+    "Invalid Character",
+  );
+});


Reviews:
user: login: kt3k
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T07:41:42Z
user: login: BlackAsLight
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T07:43:49Z
user: login: kt3k
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T08:05:10Z
user: login: kt3k
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T08:06:01Z

Review comments:
diff_hunk: @@ -0,0 +1,199 @@
+// Copyright 2018-2025 the Deno authors. MIT license.
+// This module is browser compatible.
+
+/**
+ * Functions to encode and decode to and from hexadecimal strings.
+ *
+ * ```ts
+ * import { assertEquals } from "@std/assert";
+ * import { encodeHex, type Uint8Array_ } from "@std/encoding/unstable-hex";
+ *
+ * assertEquals(encodeHex("Hello World"), "48656c6c6f20576f726c64");
+ * assertEquals(
+ *   encodeHex(new TextEncoder().encode("Hello World") as Uint8Array_),
+ *   "48656c6c6f20576f726c64",
+ * );
+ * ```
+ *
+ * @see {@link https://www.rfc-editor.org/rfc/rfc4648.html#section-8}
+ * @experimental **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @module
+ */
+
+import type { Uint8Array_ } from "./_types.ts";
+export type { Uint8Array_ };
+import { calcMax, decode, encode } from "./_common16.ts";
+export { calcMax };
+import { detach } from "./_common_detach.ts";
+
+const alphabet = new TextEncoder().encode("0123456789abcdef");
+const rAlphabet = new Uint8Array(128).fill(16); // alphabet.Hex.length
+alphabet.forEach((byte, i) => rAlphabet[byte] = i);
+new TextEncoder()
+  .encode("ABCDEF")
+  .forEach((byte, i) => rAlphabet[byte] = i + 10);
+
+/**
+ * `encodeHex` takes an input source and encodes it into a hexadecimal string.
+ * If a {@linkcode Uint8Array<ArrayBuffer>} or {@linkcode ArrayBuffer} is
+ * provided, the underlying source will be detached and reused for the encoding.
+ * If you need the input source after providing it to this function, call
+ * `.slice()` to pass in a copy.
+ *
+ * @experimental **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @param input The input source to encode.
+ * @returns The hexadecimal string representation of the input.
+ *
+ * @example Basic Usage
+ * ```ts
+ * import { assertEquals } from "@std/assert";
+ * import { encodeHex, type Uint8Array_ } from "@std/encoding/unstable-hex";
+ *
+ * assertEquals(encodeHex("Hello World"), "48656c6c6f20576f726c64");
+ * assertEquals(
+ *   encodeHex(new TextEncoder().encode("Hello World") as Uint8Array_),
+ *   "48656c6c6f20576f726c64",
+ * );
+ * ```
+ */
+export function encodeHex(
path: encoding/unstable_hex.ts
user: login: kt3k
body: Feels like there's no need of unstable version of this as this seems completely compatible with the existing version. What do you think?
created_at: 2025-03-18T07:41:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,199 @@
+// Copyright 2018-2025 the Deno authors. MIT license.
+// This module is browser compatible.
+
+/**
+ * Functions to encode and decode to and from hexadecimal strings.
+ *
+ * ```ts
+ * import { assertEquals } from "@std/assert";
+ * import { encodeHex, type Uint8Array_ } from "@std/encoding/unstable-hex";
+ *
+ * assertEquals(encodeHex("Hello World"), "48656c6c6f20576f726c64");
+ * assertEquals(
+ *   encodeHex(new TextEncoder().encode("Hello World") as Uint8Array_),
+ *   "48656c6c6f20576f726c64",
+ * );
+ * ```
+ *
+ * @see {@link https://www.rfc-editor.org/rfc/rfc4648.html#section-8}
+ * @experimental **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @module
+ */
+
+import type { Uint8Array_ } from "./_types.ts";
+export type { Uint8Array_ };
+import { calcMax, decode, encode } from "./_common16.ts";
+export { calcMax };
+import { detach } from "./_common_detach.ts";
+
+const alphabet = new TextEncoder().encode("0123456789abcdef");
+const rAlphabet = new Uint8Array(128).fill(16); // alphabet.Hex.length
+alphabet.forEach((byte, i) => rAlphabet[byte] = i);
+new TextEncoder()
+  .encode("ABCDEF")
+  .forEach((byte, i) => rAlphabet[byte] = i + 10);
+
+/**
+ * `encodeHex` takes an input source and encodes it into a hexadecimal string.
+ * If a {@linkcode Uint8Array<ArrayBuffer>} or {@linkcode ArrayBuffer} is
+ * provided, the underlying source will be detached and reused for the encoding.
+ * If you need the input source after providing it to this function, call
+ * `.slice()` to pass in a copy.
+ *
+ * @experimental **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @param input The input source to encode.
+ * @returns The hexadecimal string representation of the input.
+ *
+ * @example Basic Usage
+ * ```ts
+ * import { assertEquals } from "@std/assert";
+ * import { encodeHex, type Uint8Array_ } from "@std/encoding/unstable-hex";
+ *
+ * assertEquals(encodeHex("Hello World"), "48656c6c6f20576f726c64");
+ * assertEquals(
+ *   encodeHex(new TextEncoder().encode("Hello World") as Uint8Array_),
+ *   "48656c6c6f20576f726c64",
+ * );
+ * ```
+ */
+export function encodeHex(
path: encoding/unstable_hex.ts
user: login: BlackAsLight
body: There is a difference in that a Uint8Array or ArrayBuffer passed in will detach the buffer to reuse making the user no longer have access to the original content. 
created_at: 2025-03-18T07:43:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,199 @@
+// Copyright 2018-2025 the Deno authors. MIT license.
+// This module is browser compatible.
+
+/**
+ * Functions to encode and decode to and from hexadecimal strings.
+ *
+ * ```ts
+ * import { assertEquals } from "@std/assert";
+ * import { encodeHex, type Uint8Array_ } from "@std/encoding/unstable-hex";
+ *
+ * assertEquals(encodeHex("Hello World"), "48656c6c6f20576f726c64");
+ * assertEquals(
+ *   encodeHex(new TextEncoder().encode("Hello World") as Uint8Array_),
+ *   "48656c6c6f20576f726c64",
+ * );
+ * ```
+ *
+ * @see {@link https://www.rfc-editor.org/rfc/rfc4648.html#section-8}
+ * @experimental **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @module
+ */
+
+import type { Uint8Array_ } from "./_types.ts";
+export type { Uint8Array_ };
+import { calcMax, decode, encode } from "./_common16.ts";
+export { calcMax };
+import { detach } from "./_common_detach.ts";
+
+const alphabet = new TextEncoder().encode("0123456789abcdef");
+const rAlphabet = new Uint8Array(128).fill(16); // alphabet.Hex.length
+alphabet.forEach((byte, i) => rAlphabet[byte] = i);
+new TextEncoder()
+  .encode("ABCDEF")
+  .forEach((byte, i) => rAlphabet[byte] = i + 10);
+
+/**
+ * `encodeHex` takes an input source and encodes it into a hexadecimal string.
+ * If a {@linkcode Uint8Array<ArrayBuffer>} or {@linkcode ArrayBuffer} is
+ * provided, the underlying source will be detached and reused for the encoding.
+ * If you need the input source after providing it to this function, call
+ * `.slice()` to pass in a copy.
+ *
+ * @experimental **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @param input The input source to encode.
+ * @returns The hexadecimal string representation of the input.
+ *
+ * @example Basic Usage
+ * ```ts
+ * import { assertEquals } from "@std/assert";
+ * import { encodeHex, type Uint8Array_ } from "@std/encoding/unstable-hex";
+ *
+ * assertEquals(encodeHex("Hello World"), "48656c6c6f20576f726c64");
+ * assertEquals(
+ *   encodeHex(new TextEncoder().encode("Hello World") as Uint8Array_),
+ *   "48656c6c6f20576f726c64",
+ * );
+ * ```
+ */
+export function encodeHex(
path: encoding/unstable_hex.ts
user: login: kt3k
body: Ah, ok. That seems a significant difference. Ok, let's land this as unstable
created_at: 2025-03-18T08:05:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-03-13T07:02:41Z
body: ## [Codecov](https://app.codecov.io/gh/denoland/std/pull/6480?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 95.54%. Comparing base [(`64690f9`)](https://app.codecov.io/gh/denoland/std/commit/64690f9dd886fc3988e5809a7a5644912ede3ac0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland) to head [(`087883d`)](https://app.codecov.io/gh/denoland/std/commit/087883da9cc2d83790d126876a162dbe26c95f01?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).
> Report is 12 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #6480      +/-   ##
==========================================
- Coverage   95.67%   95.54%   -0.13%     
==========================================
  Files         575      577       +2     
  Lines       43192    43378     +186     
  Branches     6475     6484       +9     
==========================================
+ Hits        41323    41445     +122     
- Misses       1830     1899      +69     
+ Partials       39       34       -5     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/denoland/std/pull/6480?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=denoland).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: kt3k
created_at: 2025-03-17T05:33:06Z
body: > Introduces an unstable version of encodeHex and decodeHex that takes an optional redundant format argument merely to match that of the unstable base64 and base32 which have a few variants.

I'd personally prefer not to have option if there's no option. I found this `format` option only confusing..
author_association: CONTRIBUTOR