You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iggy.apache.org by pi...@apache.org on 2025/11/24 14:14:24 UTC
(iggy) branch master updated: fix(web): improve u64 and u128 parsing and rendering in UI (#2400)
This is an automated email from the ASF dual-hosted git repository.
piotr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new 8ef0d746b fix(web): improve u64 and u128 parsing and rendering in UI (#2400)
8ef0d746b is described below
commit 8ef0d746b809b7bd0416e7b381eb79340918324b
Author: Piotr Gankiewicz <pi...@gmail.com>
AuthorDate: Mon Nov 24 15:14:20 2025 +0100
fix(web): improve u64 and u128 parsing and rendering in UI (#2400)
Allow to pass either numeric or string message ID for HTTP API,
fix Web UI to render u64-u128 numbers.
---
core/common/src/commands/messages/send_messages.rs | 76 ++++++-
core/server/server.http | 2 +-
.../Iggy_SDK/JsonConverters/MessageConverter.cs | 3 +-
web/package-lock.json | 221 ++++++++++-----------
web/package.json | 3 +
web/src/lib/api/getJson.ts | 55 ++++-
.../lib/components/Modals/InspectMessage.svelte | 12 +-
web/src/lib/domain/Message.ts | 36 +++-
web/src/lib/utils/formatters/uuidFormatter.ts | 65 ++++++
9 files changed, 332 insertions(+), 141 deletions(-)
diff --git a/core/common/src/commands/messages/send_messages.rs b/core/common/src/commands/messages/send_messages.rs
index c490a4da5..05a4187ab 100644
--- a/core/common/src/commands/messages/send_messages.rs
+++ b/core/common/src/commands/messages/send_messages.rs
@@ -293,8 +293,9 @@ impl<'de> Deserialize<'de> for SendMessages {
let mut iggy_messages = Vec::new();
for msg in message_data {
- let id =
- msg.get("id").and_then(|v| v.as_u64()).unwrap_or(0) as u128;
+ let id = parse_message_id(msg.get("id")).map_err(|errror| {
+ de::Error::custom(format!("Invalid message ID: {errror}"))
+ })?;
let payload = msg
.get("payload")
@@ -371,6 +372,36 @@ impl<'de> Deserialize<'de> for SendMessages {
}
}
+fn parse_message_id(value: Option<&serde_json::Value>) -> Result<u128, String> {
+ let value = match value {
+ Some(v) => v,
+ None => return Ok(0),
+ };
+
+ match value {
+ serde_json::Value::Number(id) => id
+ .as_u64()
+ .map(|v| v as u128)
+ .ok_or_else(|| "ID must be a positive integer".to_string()),
+ serde_json::Value::String(id) => {
+ if let Ok(id) = id.parse::<u128>() {
+ return Ok(id);
+ }
+
+ let hex_str = id.replace('-', "");
+ if hex_str.len() == 32 && hex_str.chars().all(|c| c.is_ascii_hexdigit()) {
+ u128::from_str_radix(&hex_str, 16)
+ .map_err(|error| format!("Invalid UUID format: {error}"))
+ } else {
+ Err(format!(
+ "Invalid ID string: '{id}' - must be a decimal number or UUID hex format",
+ ))
+ }
+ }
+ _ => Err("ID must be a number or string".to_string()),
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -426,4 +457,45 @@ mod tests {
let key = Partitioning::messages_key_str(&messages_key);
assert!(key.is_err());
}
+
+ #[test]
+ fn parse_message_id_from_number() {
+ let value = serde_json::json!(12345);
+ let id = parse_message_id(Some(&value)).unwrap();
+ assert_eq!(id, 12345u128);
+ }
+
+ #[test]
+ fn parse_message_id_from_large_number_string() {
+ let value = serde_json::json!("340282366920938463463374607431768211455");
+ let id = parse_message_id(Some(&value)).unwrap();
+ assert_eq!(id, 340282366920938463463374607431768211455u128);
+ }
+
+ #[test]
+ fn parse_message_id_from_uuid_with_dashes() {
+ let value = serde_json::json!("af362865-042c-4000-0000-000000000000");
+ let id = parse_message_id(Some(&value)).unwrap();
+ assert_eq!(id, 0xaf362865042c40000000000000000000u128);
+ }
+
+ #[test]
+ fn parse_message_id_from_uuid_without_dashes() {
+ let value = serde_json::json!("af362865042c40000000000000000000");
+ let id = parse_message_id(Some(&value)).unwrap();
+ assert_eq!(id, 0xaf362865042c40000000000000000000u128);
+ }
+
+ #[test]
+ fn parse_message_id_defaults_to_zero_when_missing() {
+ let id = parse_message_id(None).unwrap();
+ assert_eq!(id, 0u128);
+ }
+
+ #[test]
+ fn parse_message_id_rejects_invalid_string() {
+ let value = serde_json::json!("not-a-valid-id");
+ let result = parse_message_id(Some(&value));
+ assert!(result.is_err());
+ }
}
diff --git a/core/server/server.http b/core/server/server.http
index f9e49908d..dd52429e4 100644
--- a/core/server/server.http
+++ b/core/server/server.http
@@ -22,7 +22,7 @@
@consumer_group_id = 1
@consumer_id = 1
@client_id = 1
-@partition_id_payload_base64 = AQAAAA==
+@partition_id_payload_base64 = AAAAAA==
@message_1_payload_base64 = aGVsbG8=
@message_2_payload_base64 = d29ybGQ=
@header_1_payload_base_64 = dmFsdWUgMQ==
diff --git a/foreign/csharp/Iggy_SDK/JsonConverters/MessageConverter.cs b/foreign/csharp/Iggy_SDK/JsonConverters/MessageConverter.cs
index 6e346057d..e154f6a8e 100644
--- a/foreign/csharp/Iggy_SDK/JsonConverters/MessageConverter.cs
+++ b/foreign/csharp/Iggy_SDK/JsonConverters/MessageConverter.cs
@@ -42,8 +42,7 @@ internal sealed class MessageConverter : JsonConverter<Message>
private static void WriteMessageId(Utf8JsonWriter writer, UInt128 id)
{
- writer.WritePropertyName("id");
- writer.WriteRawValue(id.ToString());
+ writer.WriteString("id", id.ToString());
}
private static void WritePayload(Utf8JsonWriter writer, byte[] payload)
diff --git a/web/package-lock.json b/web/package-lock.json
index 72a69a307..44feafb3e 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "iggy-web-ui",
- "version": "0.1.0-edge.1",
+ "version": "0.1.0-edge.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "iggy-web-ui",
- "version": "0.1.0-edge.1",
+ "version": "0.1.0-edge.2",
"dependencies": {
"@floating-ui/dom": "^1.7.4",
"@tailwindcss/postcss": "^4.1.17",
@@ -14,6 +14,7 @@
"d3-interpolate": "^3.0.1",
"date-fns": "^4.1.0",
"gsap": "^3.13.0",
+ "json-bigint": "^1.0.0",
"svelte-headlessui": "^0.0.46",
"svelte-popperjs": "^1.3.2",
"svelte-transition": "^0.0.17",
@@ -27,8 +28,10 @@
"@sveltejs/kit": "^2.48.5",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/d3-interpolate": "^3.0.4",
+ "@types/json-bigint": "^1.0.4",
"@types/node": "^24.10.1",
"autoprefixer": "^10.4.22",
+ "esbuild": "^0.27.0",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.13.0",
@@ -104,7 +107,6 @@
"os": [
"aix"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -122,7 +124,6 @@
"os": [
"android"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -140,7 +141,6 @@
"os": [
"android"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -158,7 +158,6 @@
"os": [
"android"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -176,7 +175,6 @@
"os": [
"darwin"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -194,7 +192,6 @@
"os": [
"darwin"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -212,7 +209,6 @@
"os": [
"freebsd"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -230,7 +226,6 @@
"os": [
"freebsd"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -248,7 +243,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -266,7 +260,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -284,7 +277,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -302,7 +294,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -320,7 +311,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -338,7 +328,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -356,7 +345,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -374,7 +362,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -392,7 +379,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -410,7 +396,6 @@
"os": [
"netbsd"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -428,7 +413,6 @@
"os": [
"netbsd"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -446,7 +430,6 @@
"os": [
"openbsd"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -464,7 +447,6 @@
"os": [
"openbsd"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -482,7 +464,6 @@
"os": [
"openharmony"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -500,7 +481,6 @@
"os": [
"sunos"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -518,7 +498,6 @@
"os": [
"win32"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -536,7 +515,6 @@
"os": [
"win32"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -554,7 +532,6 @@
"os": [
"win32"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1485,9 +1462,9 @@
}
},
"node_modules/@sveltejs/kit": {
- "version": "2.48.5",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.48.5.tgz",
- "integrity": "sha512-/rnwfSWS3qwUSzvHynUTORF9xSJi7PCR9yXkxUOnRrNqyKmCmh3FPHH+E9BbgqxXfTevGXBqgnlh9kMb+9T5XA==",
+ "version": "2.49.0",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.49.0.tgz",
+ "integrity": "sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1858,6 +1835,13 @@
"gsap": "*"
}
},
+ "node_modules/@types/json-bigint": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@types/json-bigint/-/json-bigint-1.0.4.tgz",
+ "integrity": "sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -1926,17 +1910,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.46.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.4.tgz",
- "integrity": "sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==",
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz",
+ "integrity": "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.46.4",
- "@typescript-eslint/type-utils": "8.46.4",
- "@typescript-eslint/utils": "8.46.4",
- "@typescript-eslint/visitor-keys": "8.46.4",
+ "@typescript-eslint/scope-manager": "8.47.0",
+ "@typescript-eslint/type-utils": "8.47.0",
+ "@typescript-eslint/utils": "8.47.0",
+ "@typescript-eslint/visitor-keys": "8.47.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -1950,7 +1934,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.46.4",
+ "@typescript-eslint/parser": "^8.47.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
@@ -1966,16 +1950,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.46.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.4.tgz",
- "integrity": "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==",
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz",
+ "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.46.4",
- "@typescript-eslint/types": "8.46.4",
- "@typescript-eslint/typescript-estree": "8.46.4",
- "@typescript-eslint/visitor-keys": "8.46.4",
+ "@typescript-eslint/scope-manager": "8.47.0",
+ "@typescript-eslint/types": "8.47.0",
+ "@typescript-eslint/typescript-estree": "8.47.0",
+ "@typescript-eslint/visitor-keys": "8.47.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1991,14 +1975,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.46.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.4.tgz",
- "integrity": "sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==",
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz",
+ "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.46.4",
- "@typescript-eslint/types": "^8.46.4",
+ "@typescript-eslint/tsconfig-utils": "^8.47.0",
+ "@typescript-eslint/types": "^8.47.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2013,14 +1997,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.46.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.4.tgz",
- "integrity": "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==",
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz",
+ "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.46.4",
- "@typescript-eslint/visitor-keys": "8.46.4"
+ "@typescript-eslint/types": "8.47.0",
+ "@typescript-eslint/visitor-keys": "8.47.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2031,9 +2015,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.46.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.4.tgz",
- "integrity": "sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==",
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz",
+ "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2048,15 +2032,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.46.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.4.tgz",
- "integrity": "sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==",
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz",
+ "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.46.4",
- "@typescript-eslint/typescript-estree": "8.46.4",
- "@typescript-eslint/utils": "8.46.4",
+ "@typescript-eslint/types": "8.47.0",
+ "@typescript-eslint/typescript-estree": "8.47.0",
+ "@typescript-eslint/utils": "8.47.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -2073,9 +2057,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.46.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.4.tgz",
- "integrity": "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==",
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.47.0.tgz",
+ "integrity": "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2087,16 +2071,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.46.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.4.tgz",
- "integrity": "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==",
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz",
+ "integrity": "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.46.4",
- "@typescript-eslint/tsconfig-utils": "8.46.4",
- "@typescript-eslint/types": "8.46.4",
- "@typescript-eslint/visitor-keys": "8.46.4",
+ "@typescript-eslint/project-service": "8.47.0",
+ "@typescript-eslint/tsconfig-utils": "8.47.0",
+ "@typescript-eslint/types": "8.47.0",
+ "@typescript-eslint/visitor-keys": "8.47.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@@ -2142,16 +2126,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.46.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.4.tgz",
- "integrity": "sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==",
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz",
+ "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.46.4",
- "@typescript-eslint/types": "8.46.4",
- "@typescript-eslint/typescript-estree": "8.46.4"
+ "@typescript-eslint/scope-manager": "8.47.0",
+ "@typescript-eslint/types": "8.47.0",
+ "@typescript-eslint/typescript-estree": "8.47.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2166,13 +2150,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.46.4",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.4.tgz",
- "integrity": "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==",
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz",
+ "integrity": "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.46.4",
+ "@typescript-eslint/types": "8.47.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -2382,6 +2366,15 @@
"baseline-browser-mapping": "dist/cli.js"
}
},
+ "node_modules/bignumber.js": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
+ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -2757,8 +2750,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
- "optional": true,
- "peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -3503,6 +3494,15 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/json-bigint": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "bignumber.js": "^9.0.0"
+ }
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -4657,9 +4657,9 @@
}
},
"node_modules/svelte": {
- "version": "5.43.6",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.43.6.tgz",
- "integrity": "sha512-RnyO9VXI85Bmsf4b8AuQFBKFYL3LKUl+ZrifOjvlrQoboAROj5IITVLK1yOXBjwUWUn2BI5cfmurktgCzuZ5QA==",
+ "version": "5.43.14",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.43.14.tgz",
+ "integrity": "sha512-pHeUrp1A5S6RGaXhJB7PtYjL1VVjbVrJ2EfuAoPu9/1LeoMaJa/pcdCsCSb0gS4eUHAHnhCbUDxORZyvGK6kOQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
@@ -5048,16 +5048,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.46.4",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.4.tgz",
- "integrity": "sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg==",
+ "version": "8.47.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.47.0.tgz",
+ "integrity": "sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.46.4",
- "@typescript-eslint/parser": "8.46.4",
- "@typescript-eslint/typescript-estree": "8.46.4",
- "@typescript-eslint/utils": "8.46.4"
+ "@typescript-eslint/eslint-plugin": "8.47.0",
+ "@typescript-eslint/parser": "8.47.0",
+ "@typescript-eslint/typescript-estree": "8.47.0",
+ "@typescript-eslint/utils": "8.47.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -5167,9 +5167,9 @@
}
},
"node_modules/vite": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
- "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
+ "version": "7.2.4",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz",
+ "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5771,21 +5771,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/yaml": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
- "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "peer": true,
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14.6"
- }
- },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -5820,9 +5805,9 @@
"license": "MIT"
},
"node_modules/zod": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
- "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz",
+ "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
"dev": true,
"license": "MIT",
"funding": {
diff --git a/web/package.json b/web/package.json
index 6b47f3217..ede1de699 100644
--- a/web/package.json
+++ b/web/package.json
@@ -19,8 +19,10 @@
"@sveltejs/kit": "^2.48.5",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@types/d3-interpolate": "^3.0.4",
+ "@types/json-bigint": "^1.0.4",
"@types/node": "^24.10.1",
"autoprefixer": "^10.4.22",
+ "esbuild": "^0.27.0",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.13.0",
@@ -47,6 +49,7 @@
"d3-interpolate": "^3.0.1",
"date-fns": "^4.1.0",
"gsap": "^3.13.0",
+ "json-bigint": "^1.0.0",
"svelte-headlessui": "^0.0.46",
"svelte-popperjs": "^1.3.2",
"svelte-transition": "^0.0.17",
diff --git a/web/src/lib/api/getJson.ts b/web/src/lib/api/getJson.ts
index 2b0d465e7..8a399dfb8 100644
--- a/web/src/lib/api/getJson.ts
+++ b/web/src/lib/api/getJson.ts
@@ -17,17 +17,60 @@
* under the License.
*/
+import JSONBigInt from 'json-bigint';
+
+// Configure json-bigint to parse as strings to avoid BigInt issues with math operations
+const JSONbig = JSONBigInt({
+ useNativeBigInt: true,
+ alwaysParseAsBig: false,
+ strict: false,
+ // Custom reviver to convert BigInt to string to avoid math operation issues
+ protoAction: 'ignore',
+ constructorAction: 'ignore'
+});
+
export const getJson = async (res: Response): Promise<unknown | null> => {
const text = await res.text();
try {
- const json = JSON.parse(text, (_key, value) => {
- if (typeof value === 'number' && value > Number.MAX_SAFE_INTEGER) {
- return BigInt(value);
- }
- return value;
- });
+ // Parse with json-bigint which handles large numbers correctly
+ const parsed = JSONbig.parse(text);
+
+ // Convert any BigInt values to strings or numbers depending on size
+ const json = convertBigInts(parsed);
return json as unknown;
} catch (err) {
return null;
}
};
+
+// Recursively convert BigInt values to appropriate types
+function convertBigInts(obj: any): any {
+ if (obj === null || obj === undefined) {
+ return obj;
+ }
+
+ if (typeof obj === 'bigint') {
+ // If it fits in a safe number, convert to number for math operations
+ // Otherwise keep as string for display
+ if (obj <= Number.MAX_SAFE_INTEGER && obj >= Number.MIN_SAFE_INTEGER) {
+ return Number(obj);
+ }
+ return obj.toString();
+ }
+
+ if (Array.isArray(obj)) {
+ return obj.map(convertBigInts);
+ }
+
+ if (typeof obj === 'object') {
+ const result: any = {};
+ for (const key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ result[key] = convertBigInts(obj[key]);
+ }
+ }
+ return result;
+ }
+
+ return obj;
+}
diff --git a/web/src/lib/components/Modals/InspectMessage.svelte b/web/src/lib/components/Modals/InspectMessage.svelte
index c80035f4a..210468fc2 100644
--- a/web/src/lib/components/Modals/InspectMessage.svelte
+++ b/web/src/lib/components/Modals/InspectMessage.svelte
@@ -4,6 +4,7 @@
import { type Message, type HeaderField } from '$lib/domain/Message';
import MessageDecoder from '$lib/components/MessageDecoder/MessageDecoder.svelte';
import { decodeBase64 } from '$lib/utils/base64Utils';
+ import { formatMessageId } from '$lib/utils/formatters/uuidFormatter';
interface Props {
closeModal: CloseModalFn;
@@ -12,6 +13,8 @@
let { closeModal, message }: Props = $props();
+ const formattedId = $derived(message?.id ? formatMessageId(message.id) : 'N/A');
+
const formatHeaders = (headers: Record<string, HeaderField> | null | undefined) => {
if (!headers || Object.keys(headers).length === 0) {
return 'No headers';
@@ -36,7 +39,14 @@
<div class="flex flex-col gap-4">
<div class="bg-shade-l200 dark:bg-shade-d400 p-3 lg:p-4 rounded-md">
<span class="text-xs text-shade-l900 dark:text-shade-l700 block mb-1">ID</span>
- <div class="text-sm text-color font-medium">{message?.id ?? 'N/A'}</div>
+ <div class="text-sm text-color font-medium font-mono break-all">
+ {formattedId}
+ {#if formattedId !== message?.id && message?.id}
+ <span class="text-xs text-shade-l900 dark:text-shade-l700 block mt-1">
+ Raw: {message.id}
+ </span>
+ {/if}
+ </div>
</div>
<div class="bg-shade-l200 dark:bg-shade-d400 p-3 lg:p-4 rounded-md">
diff --git a/web/src/lib/domain/Message.ts b/web/src/lib/domain/Message.ts
index e0099deee..d4d775ef4 100644
--- a/web/src/lib/domain/Message.ts
+++ b/web/src/lib/domain/Message.ts
@@ -26,11 +26,11 @@ export type MessagePartition = {
};
export type Message = {
- checksum: number;
- id: number;
- offset: number;
- timestamp: number;
- origin_timestamp: number;
+ checksum: string;
+ id: string;
+ offset: string;
+ timestamp: string;
+ origin_timestamp: string;
user_headers_length: number;
payload_length: number;
formattedTimestamp: string;
@@ -47,14 +47,28 @@ export type HeaderField = {
export function messageMapper(item: any): Message {
const payload = item.payload;
const truncatedPayload = payload.length > 30 ? `${payload.slice(0, 30)} [...]` : payload;
- const formattedTimestamp = formatDateWithMicroseconds(item.header.timestamp);
+
+ // Convert all header numbers to strings to preserve precision
+ // getJson() returns numbers for values <= MAX_SAFE_INTEGER, strings for larger values
+ const checksum = String(item.header.checksum);
+ const id = String(item.header.id);
+ const offset = String(item.header.offset);
+ const timestamp = String(item.header.timestamp);
+ const origin_timestamp = String(item.header.origin_timestamp);
+
+ // Use numeric value for date formatting (getJson already converted to number if safe)
+ const timestampNum =
+ typeof item.header.timestamp === 'number'
+ ? item.header.timestamp
+ : Number(item.header.timestamp);
+ const formattedTimestamp = formatDateWithMicroseconds(timestampNum);
return {
- checksum: item.header.checksum,
- id: item.header.id,
- offset: item.header.offset,
- timestamp: item.header.timestamp,
- origin_timestamp: item.header.origin_timestamp,
+ checksum,
+ id,
+ offset,
+ timestamp,
+ origin_timestamp,
user_headers_length: item.header.user_headers_length,
payload_length: item.header.payload_length,
formattedTimestamp: formattedTimestamp,
diff --git a/web/src/lib/utils/formatters/uuidFormatter.ts b/web/src/lib/utils/formatters/uuidFormatter.ts
new file mode 100644
index 000000000..59a8b2b43
--- /dev/null
+++ b/web/src/lib/utils/formatters/uuidFormatter.ts
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Checks if a u128 value looks like it could be a UUID
+ * UUIDs typically have specific bit patterns, but for simplicity
+ * we check if the value is within a reasonable range for UUIDs
+ */
+export function looksLikeUuid(id: string): boolean {
+ try {
+ const num = BigInt(id);
+ // Check if the number is large enough to potentially be a UUID
+ // UUIDs are 128-bit, so they're typically very large numbers
+ // We'll consider anything > u64::MAX as potentially a UUID
+ const u64Max = BigInt('18446744073709551615');
+ return num > u64Max;
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * Formats a u128 number as a UUID string (8-4-4-4-12 format)
+ * Example: 340282366920938463463374607431768211455 -> ffffffff-ffff-ffff-ffff-ffffffffffff
+ */
+export function formatAsUuid(id: string): string {
+ try {
+ const num = BigInt(id);
+ // Convert to hex and pad to 32 characters (128 bits = 32 hex chars)
+ const hex = num.toString(16).padStart(32, '0');
+
+ // Format as UUID: 8-4-4-4-12
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
+ } catch {
+ return id;
+ }
+}
+
+/**
+ * Formats a message ID intelligently:
+ * - If it looks like a UUID, formats it as UUID
+ * - Otherwise returns the raw string
+ */
+export function formatMessageId(id: string): string {
+ if (looksLikeUuid(id)) {
+ return formatAsUuid(id);
+ }
+ return id;
+}