Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/three-queens-stay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/devtools-event-bus': patch
---

fixed an issue where bigInt was not parsed properly
10 changes: 6 additions & 4 deletions packages/event-bus/src/client/client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { parseWithBigInt, stringifyWithBigInt } from '../utils/json'

interface TanStackDevtoolsEvent<TEventName extends string, TPayload = any> {
type: TEventName
payload: TPayload
Expand Down Expand Up @@ -55,7 +57,7 @@ export class ClientEventBus {
this.#connectToServerBus = connectToServerBus
this.#eventTarget = this.getGlobalTarget()
this.#broadcastChannel.onmessage = (e) => {
this.emitToClients(JSON.parse(e.data), true)
this.emitToClients(parseWithBigInt(e.data), true)
}
this.debugLog('Initializing client event bus')
}
Expand All @@ -74,14 +76,14 @@ export class ClientEventBus {
// We only emit the events if they didn't come from the broadcast channel
// otherwise it would infinitely send events between
if (!fromBroadcastChannel) {
this.#broadcastChannel?.postMessage(JSON.stringify(event))
this.#broadcastChannel?.postMessage(stringifyWithBigInt(event))
}
this.debugLog('Emitting event to global client listeners', event)
this.#eventTarget.dispatchEvent(globalEvent)
}

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

private handleEventReceived(data: string) {
try {
const event = JSON.parse(data) as TanStackDevtoolsEvent<string, any>
const event = parseWithBigInt(data) as TanStackDevtoolsEvent<string, any>
this.emitToClients(event)
} catch {}
}
Expand Down
1 change: 1 addition & 0 deletions packages/event-bus/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { parseWithBigInt, stringifyWithBigInt } from './utils/json'
7 changes: 4 additions & 3 deletions packages/event-bus/src/server/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import http from 'node:http'
import { WebSocket, WebSocketServer } from 'ws'
import { parseWithBigInt, stringifyWithBigInt } from '../utils/json'

// Shared types
export interface TanStackDevtoolsEvent<
Expand Down Expand Up @@ -76,7 +77,7 @@ export class ServerEventBus {

private emitEventToClients(event: TanStackDevtoolsEvent<string>) {
this.debugLog('Emitting event to clients', event)
const json = JSON.stringify(event)
const json = stringifyWithBigInt(event)

for (const client of this.#clients) {
if (client.readyState === WebSocket.OPEN) {
Expand Down Expand Up @@ -117,7 +118,7 @@ export class ServerEventBus {
req.on('data', (chunk) => (body += chunk))
req.on('end', () => {
try {
const msg = JSON.parse(body)
const msg = parseWithBigInt(body)
this.debugLog('Received event from client', msg)
this.emitToServer(msg)
} catch {}
Expand Down Expand Up @@ -155,7 +156,7 @@ export class ServerEventBus {
})
ws.on('message', (msg) => {
this.debugLog('Received message from WebSocket client', msg.toString())
const data = JSON.parse(msg.toString())
const data = parseWithBigInt(msg.toString())
this.emitToServer(data)
})
})
Expand Down
142 changes: 142 additions & 0 deletions packages/event-bus/src/utils/json.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { describe, expect, it } from 'vitest'
import { parseWithBigInt, stringifyWithBigInt } from './json'

describe('json utils', () => {
describe('stringifyWithBigInt', () => {
it('should handle regular JSON data', () => {
const data = { name: 'test', count: 42, nested: { value: true } }
const result = stringifyWithBigInt(data)
expect(result).toBe(JSON.stringify(data))
})

it('should convert BigInt to object with marker', () => {
const data = { id: BigInt(9007199254740991) }
const result = stringifyWithBigInt(data)
const parsed = JSON.parse(result)
expect(parsed.id).toEqual({
__type: 'bigint',
value: '9007199254740991',
})
})

it('should handle nested BigInt values', () => {
const data = {
user: { id: BigInt(123), balance: BigInt(456789) },
timestamp: BigInt(1234567890),
}
const result = stringifyWithBigInt(data)
const parsed = JSON.parse(result)
expect(parsed.user.id).toEqual({ __type: 'bigint', value: '123' })
expect(parsed.user.balance).toEqual({
__type: 'bigint',
value: '456789',
})
expect(parsed.timestamp).toEqual({
__type: 'bigint',
value: '1234567890',
})
})

it('should handle arrays with BigInt', () => {
const data = [BigInt(1), BigInt(2), BigInt(3)]
const result = stringifyWithBigInt(data)
const parsed = JSON.parse(result)
expect(parsed).toEqual([
{ __type: 'bigint', value: '1' },
{ __type: 'bigint', value: '2' },
{ __type: 'bigint', value: '3' },
])
})

it('should handle mixed types with BigInt', () => {
const data = {
string: 'text',
number: 42,
bigint: BigInt(999),
boolean: true,
null: null,
array: [1, BigInt(2), 'three'],
}
const result = stringifyWithBigInt(data)
const parsed = JSON.parse(result)
expect(parsed.bigint).toEqual({ __type: 'bigint', value: '999' })
expect(parsed.array[1]).toEqual({ __type: 'bigint', value: '2' })
})
})

describe('parseWithBigInt', () => {
it('should handle regular JSON data', () => {
const json = JSON.stringify({ name: 'test', count: 42 })
const result = parseWithBigInt(json)
expect(result).toEqual({ name: 'test', count: 42 })
})

it('should restore BigInt from marker object', () => {
const json = JSON.stringify({ id: { __type: 'bigint', value: '123' } })
const result = parseWithBigInt(json)
expect(result.id).toBe(BigInt(123))
expect(typeof result.id).toBe('bigint')
})

it('should handle nested BigInt restoration', () => {
const json = JSON.stringify({
user: {
id: { __type: 'bigint', value: '9007199254740991' },
balance: { __type: 'bigint', value: '456789' },
},
timestamp: { __type: 'bigint', value: '1234567890' },
})
const result = parseWithBigInt(json)
expect(result.user.id).toBe(BigInt(9007199254740991))
expect(result.user.balance).toBe(BigInt(456789))
expect(result.timestamp).toBe(BigInt(1234567890))
})

it('should handle arrays with BigInt restoration', () => {
const json = JSON.stringify([
{ __type: 'bigint', value: '1' },
{ __type: 'bigint', value: '2' },
{ __type: 'bigint', value: '3' },
])
const result = parseWithBigInt(json)
expect(result).toEqual([BigInt(1), BigInt(2), BigInt(3)])
})

it('should not convert objects that look like BigInt markers but are not', () => {
const json = JSON.stringify({
fake: { __type: 'bigint', value: 123 }, // value is not a string
real: { __type: 'bigint', value: '456' },
})
const result = parseWithBigInt(json)
expect(result.fake).toEqual({ __type: 'bigint', value: 123 })
expect(result.real).toBe(BigInt(456))
})
})

describe('round-trip', () => {
it('should correctly round-trip BigInt values', () => {
const original = {
id: BigInt(9007199254740991),
nested: {
value: BigInt(123456789),
},
array: [BigInt(1), BigInt(2)],
}
const stringified = stringifyWithBigInt(original)
const parsed = parseWithBigInt(stringified)
expect(parsed.id).toBe(original.id)
expect(parsed.nested.value).toBe(original.nested.value)
expect(parsed.array[0]).toBe(original.array[0])
expect(parsed.array[1]).toBe(original.array[1])
})

it('should handle very large BigInt values', () => {
const original = {
huge: BigInt('123456789012345678901234567890'),
}
const stringified = stringifyWithBigInt(original)
const parsed = parseWithBigInt(stringified)
expect(parsed.huge).toBe(original.huge)
})
})
})
33 changes: 33 additions & 0 deletions packages/event-bus/src/utils/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Safely stringify data that may contain BigInt values
* BigInt values are converted to objects with a special marker
*/
export function stringifyWithBigInt(data: any): string {
return JSON.stringify(data, (_key, value) => {
if (typeof value === 'bigint') {
return {
__type: 'bigint',
value: value.toString(),
}
}
return value
})
}

/**
* Parse JSON and restore BigInt values
* Objects with __type: 'bigint' are converted back to BigInt
*/
export function parseWithBigInt(json: string): any {
return JSON.parse(json, (_key, value) => {
if (
value &&
typeof value === 'object' &&
value.__type === 'bigint' &&
typeof value.value === 'string'
) {
return BigInt(value.value)
}
return value
})
}
Loading