Skip to content

Commit d0567fc

Browse files
authored
Big int fix (#231)
* fix * fix: fix issues with bigInt parsing * changeset
1 parent 5de9631 commit d0567fc

File tree

6 files changed

+191
-7
lines changed

6 files changed

+191
-7
lines changed

.changeset/three-queens-stay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/devtools-event-bus': patch
3+
---
4+
5+
fixed an issue where bigInt was not parsed properly

packages/event-bus/src/client/client.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { parseWithBigInt, stringifyWithBigInt } from '../utils/json'
2+
13
interface TanStackDevtoolsEvent<TEventName extends string, TPayload = any> {
24
type: TEventName
35
payload: TPayload
@@ -55,7 +57,7 @@ export class ClientEventBus {
5557
this.#connectToServerBus = connectToServerBus
5658
this.#eventTarget = this.getGlobalTarget()
5759
this.#broadcastChannel.onmessage = (e) => {
58-
this.emitToClients(JSON.parse(e.data), true)
60+
this.emitToClients(parseWithBigInt(e.data), true)
5961
}
6062
this.debugLog('Initializing client event bus')
6163
}
@@ -74,14 +76,14 @@ export class ClientEventBus {
7476
// We only emit the events if they didn't come from the broadcast channel
7577
// otherwise it would infinitely send events between
7678
if (!fromBroadcastChannel) {
77-
this.#broadcastChannel?.postMessage(JSON.stringify(event))
79+
this.#broadcastChannel?.postMessage(stringifyWithBigInt(event))
7880
}
7981
this.debugLog('Emitting event to global client listeners', event)
8082
this.#eventTarget.dispatchEvent(globalEvent)
8183
}
8284

8385
private emitToServer(event: TanStackDevtoolsEvent<string, any>) {
84-
const json = JSON.stringify(event)
86+
const json = stringifyWithBigInt(event)
8587
// try to emit it to the event bus first
8688
if (this.#socket && this.#socket.readyState === WebSocket.OPEN) {
8789
this.debugLog('Emitting event to server via WS', event)
@@ -185,7 +187,7 @@ export class ClientEventBus {
185187

186188
private handleEventReceived(data: string) {
187189
try {
188-
const event = JSON.parse(data) as TanStackDevtoolsEvent<string, any>
190+
const event = parseWithBigInt(data) as TanStackDevtoolsEvent<string, any>
189191
this.emitToClients(event)
190192
} catch {}
191193
}

packages/event-bus/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { parseWithBigInt, stringifyWithBigInt } from './utils/json'

packages/event-bus/src/server/server.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import http from 'node:http'
22
import { WebSocket, WebSocketServer } from 'ws'
3+
import { parseWithBigInt, stringifyWithBigInt } from '../utils/json'
34

45
// Shared types
56
export interface TanStackDevtoolsEvent<
@@ -76,7 +77,7 @@ export class ServerEventBus {
7677

7778
private emitEventToClients(event: TanStackDevtoolsEvent<string>) {
7879
this.debugLog('Emitting event to clients', event)
79-
const json = JSON.stringify(event)
80+
const json = stringifyWithBigInt(event)
8081

8182
for (const client of this.#clients) {
8283
if (client.readyState === WebSocket.OPEN) {
@@ -117,7 +118,7 @@ export class ServerEventBus {
117118
req.on('data', (chunk) => (body += chunk))
118119
req.on('end', () => {
119120
try {
120-
const msg = JSON.parse(body)
121+
const msg = parseWithBigInt(body)
121122
this.debugLog('Received event from client', msg)
122123
this.emitToServer(msg)
123124
} catch {}
@@ -155,7 +156,7 @@ export class ServerEventBus {
155156
})
156157
ws.on('message', (msg) => {
157158
this.debugLog('Received message from WebSocket client', msg.toString())
158-
const data = JSON.parse(msg.toString())
159+
const data = parseWithBigInt(msg.toString())
159160
this.emitToServer(data)
160161
})
161162
})
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { parseWithBigInt, stringifyWithBigInt } from './json'
3+
4+
describe('json utils', () => {
5+
describe('stringifyWithBigInt', () => {
6+
it('should handle regular JSON data', () => {
7+
const data = { name: 'test', count: 42, nested: { value: true } }
8+
const result = stringifyWithBigInt(data)
9+
expect(result).toBe(JSON.stringify(data))
10+
})
11+
12+
it('should convert BigInt to object with marker', () => {
13+
const data = { id: BigInt(9007199254740991) }
14+
const result = stringifyWithBigInt(data)
15+
const parsed = JSON.parse(result)
16+
expect(parsed.id).toEqual({
17+
__type: 'bigint',
18+
value: '9007199254740991',
19+
})
20+
})
21+
22+
it('should handle nested BigInt values', () => {
23+
const data = {
24+
user: { id: BigInt(123), balance: BigInt(456789) },
25+
timestamp: BigInt(1234567890),
26+
}
27+
const result = stringifyWithBigInt(data)
28+
const parsed = JSON.parse(result)
29+
expect(parsed.user.id).toEqual({ __type: 'bigint', value: '123' })
30+
expect(parsed.user.balance).toEqual({
31+
__type: 'bigint',
32+
value: '456789',
33+
})
34+
expect(parsed.timestamp).toEqual({
35+
__type: 'bigint',
36+
value: '1234567890',
37+
})
38+
})
39+
40+
it('should handle arrays with BigInt', () => {
41+
const data = [BigInt(1), BigInt(2), BigInt(3)]
42+
const result = stringifyWithBigInt(data)
43+
const parsed = JSON.parse(result)
44+
expect(parsed).toEqual([
45+
{ __type: 'bigint', value: '1' },
46+
{ __type: 'bigint', value: '2' },
47+
{ __type: 'bigint', value: '3' },
48+
])
49+
})
50+
51+
it('should handle mixed types with BigInt', () => {
52+
const data = {
53+
string: 'text',
54+
number: 42,
55+
bigint: BigInt(999),
56+
boolean: true,
57+
null: null,
58+
array: [1, BigInt(2), 'three'],
59+
}
60+
const result = stringifyWithBigInt(data)
61+
const parsed = JSON.parse(result)
62+
expect(parsed.bigint).toEqual({ __type: 'bigint', value: '999' })
63+
expect(parsed.array[1]).toEqual({ __type: 'bigint', value: '2' })
64+
})
65+
})
66+
67+
describe('parseWithBigInt', () => {
68+
it('should handle regular JSON data', () => {
69+
const json = JSON.stringify({ name: 'test', count: 42 })
70+
const result = parseWithBigInt(json)
71+
expect(result).toEqual({ name: 'test', count: 42 })
72+
})
73+
74+
it('should restore BigInt from marker object', () => {
75+
const json = JSON.stringify({ id: { __type: 'bigint', value: '123' } })
76+
const result = parseWithBigInt(json)
77+
expect(result.id).toBe(BigInt(123))
78+
expect(typeof result.id).toBe('bigint')
79+
})
80+
81+
it('should handle nested BigInt restoration', () => {
82+
const json = JSON.stringify({
83+
user: {
84+
id: { __type: 'bigint', value: '9007199254740991' },
85+
balance: { __type: 'bigint', value: '456789' },
86+
},
87+
timestamp: { __type: 'bigint', value: '1234567890' },
88+
})
89+
const result = parseWithBigInt(json)
90+
expect(result.user.id).toBe(BigInt(9007199254740991))
91+
expect(result.user.balance).toBe(BigInt(456789))
92+
expect(result.timestamp).toBe(BigInt(1234567890))
93+
})
94+
95+
it('should handle arrays with BigInt restoration', () => {
96+
const json = JSON.stringify([
97+
{ __type: 'bigint', value: '1' },
98+
{ __type: 'bigint', value: '2' },
99+
{ __type: 'bigint', value: '3' },
100+
])
101+
const result = parseWithBigInt(json)
102+
expect(result).toEqual([BigInt(1), BigInt(2), BigInt(3)])
103+
})
104+
105+
it('should not convert objects that look like BigInt markers but are not', () => {
106+
const json = JSON.stringify({
107+
fake: { __type: 'bigint', value: 123 }, // value is not a string
108+
real: { __type: 'bigint', value: '456' },
109+
})
110+
const result = parseWithBigInt(json)
111+
expect(result.fake).toEqual({ __type: 'bigint', value: 123 })
112+
expect(result.real).toBe(BigInt(456))
113+
})
114+
})
115+
116+
describe('round-trip', () => {
117+
it('should correctly round-trip BigInt values', () => {
118+
const original = {
119+
id: BigInt(9007199254740991),
120+
nested: {
121+
value: BigInt(123456789),
122+
},
123+
array: [BigInt(1), BigInt(2)],
124+
}
125+
const stringified = stringifyWithBigInt(original)
126+
const parsed = parseWithBigInt(stringified)
127+
expect(parsed.id).toBe(original.id)
128+
expect(parsed.nested.value).toBe(original.nested.value)
129+
expect(parsed.array[0]).toBe(original.array[0])
130+
expect(parsed.array[1]).toBe(original.array[1])
131+
})
132+
133+
it('should handle very large BigInt values', () => {
134+
const original = {
135+
huge: BigInt('123456789012345678901234567890'),
136+
}
137+
const stringified = stringifyWithBigInt(original)
138+
const parsed = parseWithBigInt(stringified)
139+
expect(parsed.huge).toBe(original.huge)
140+
})
141+
})
142+
})
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Safely stringify data that may contain BigInt values
3+
* BigInt values are converted to objects with a special marker
4+
*/
5+
export function stringifyWithBigInt(data: any): string {
6+
return JSON.stringify(data, (_key, value) => {
7+
if (typeof value === 'bigint') {
8+
return {
9+
__type: 'bigint',
10+
value: value.toString(),
11+
}
12+
}
13+
return value
14+
})
15+
}
16+
17+
/**
18+
* Parse JSON and restore BigInt values
19+
* Objects with __type: 'bigint' are converted back to BigInt
20+
*/
21+
export function parseWithBigInt(json: string): any {
22+
return JSON.parse(json, (_key, value) => {
23+
if (
24+
value &&
25+
typeof value === 'object' &&
26+
value.__type === 'bigint' &&
27+
typeof value.value === 'string'
28+
) {
29+
return BigInt(value.value)
30+
}
31+
return value
32+
})
33+
}

0 commit comments

Comments
 (0)