From 12d710f67a8d2a51167ece9e25f6af3c5f17844b Mon Sep 17 00:00:00 2001 From: erKam <88717125+erkamyaman@users.noreply.github.com> Date: Sun, 11 Oct 2026 00:08:08 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20keep=20a=20real=20"=E2=80=A6"=20key=20wh?= =?UTF-8?q?en=20serialize=20adds=20its=20overflow=20marker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an object had more keys than the `keys` limit, serialize wrote the "N more" marker straight to `out['…']`. If the object had its own `…` key among the kept ones, its value was silently replaced by the marker. Real keys already go through safeKey to avoid collisions; the marker now does too, so it lands on `… (2)` when `…` is taken. --- packages/devtools/src/__tests__/serialize.test.ts | 8 ++++++++ packages/devtools/src/serialize.ts | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/devtools/src/__tests__/serialize.test.ts b/packages/devtools/src/__tests__/serialize.test.ts index bb5b1bea..1a85ee1a 100644 --- a/packages/devtools/src/__tests__/serialize.test.ts +++ b/packages/devtools/src/__tests__/serialize.test.ts @@ -28,6 +28,14 @@ describe('serialize', () => { expect(serialize('abcdef', { text: 3 })).toBe('abc…'); }); + it('keeps a real "…" key when the key limit adds its marker', () => { + expect(serialize({ '…': 'real', b: 2, c: 3 }, { keys: 2 })).toEqual({ + '…': 'real', + b: 2, + '… (2)': '1 more', + }); + }); + it('marks text cut by the redaction window when the text limit is larger', () => { const out = serialize('a'.repeat(70000), { text: 100000 }) as string; expect(out).toBe(`${'a'.repeat(65536)}…`); diff --git a/packages/devtools/src/serialize.ts b/packages/devtools/src/serialize.ts index a4c9caa5..b848393b 100644 --- a/packages/devtools/src/serialize.ts +++ b/packages/devtools/src/serialize.ts @@ -145,7 +145,8 @@ export function serialize(value: unknown, limits: SerializeLimits = {}): unknown } setKey(out, safeKey(key, out), walkNamed(key, item, level + 1)); } - if (keys.length > maxKeys) out['…'] = `${keys.length - maxKeys} more`; + // Through safeKey, so the marker never replaces a real `…` key. + if (keys.length > maxKeys) setKey(out, safeKey('…', out), `${keys.length - maxKeys} more`); return out; } finally { path.delete(v);