Skip to content

Conversation

@AlemTuzlak
Copy link
Collaborator

@AlemTuzlak AlemTuzlak commented Aug 4, 2025

This PR does the following:

  • Implements both a client/server side event-bus and integrates the client-side directly into the devtools to be used to emit/listen to events on the client via the EventClient interface from @tanstack/devtools-event-client.
  • Adds two new packages, @tanstack/devtools-event-bus and @tanstack/detools-event-client
  • Allows anyone to use the event-bus to write plugins for the devtools that interact between their devtools panel and their core library
image image

Comment on lines 1 to 7
import { DevtoolsClient } from '@tanstack/devtools/client'

const devtoolsClient = new DevtoolsClient()

devtoolsClient.connect()

export { devtoolsClient }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create a client connection to the server to listen to incoming events

@pkg-pr-new
Copy link

pkg-pr-new bot commented Aug 4, 2025

More templates

@tanstack/devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools@11

@tanstack/devtools-event-bus

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-event-bus@11

@tanstack/devtools-event-client

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/devtools-event-client@11

@tanstack/react-devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/react-devtools@11

@tanstack/solid-devtools

npm i https://pkg.pr.new/TanStack/devtools/@tanstack/solid-devtools@11

commit: 9500cc6

Comment on lines 9 to 19
devtoolsClient.onAll((event) => {
console.log('Received message:', event)
setEvents((prev) => [...prev, event])
})
devtoolsClient.emit({
type: 'init',
payload: {
title: 'Client Plugin Initialized',
description: 'Listening for events',
},
})
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listens to events from client/server

<p>Devtools Client is connected.</p>
<button
onClick={() =>
devtoolsClient.emit({
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emits the event to server/client listeners

import { z } from 'zod'
const devtoolsServer = new DevtoolsServer()

devtoolsServer.start()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start the dev server to listen to events

Comment on lines 11 to 28
devtoolsServer.on('click-event', (payload) => {
console.log('Click event received:', payload)
})

devtoolsServer.onAll((e) => {
console.log('All events:', e)
})

setInterval(() => {
console.log('Emitting server event...')
devtoolsServer.emit({
type: 'server-event',
payload: {
title: 'Server Event',
description: 'This is a custom event emitted by the server.',
},
})
}, 5000)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

send/listen to client/server events

Comment on lines 30 to 53
const eventMap = {
'query-devtools:test': z.object({
title: z.string(),
description: z.string(),
}),
'query-devtools:init': z.object({
title: z.string(),
description: z.string(),
}),
'query-devtools:b': z.object({
title: z.string(),
description: z.string(),
}),
} satisfies EventMap<'query-devtools'>

class QueryDevtoolsPlugin extends DevtoolsPlugin<typeof eventMap> {
constructor() {
super({
pluginId: 'query-devtools',
})
}
}

const plugin = new QueryDevtoolsPlugin()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potential plugin API right here, WDYT?

}),
} satisfies EventMap<'query-devtools'>

class QueryDevtoolsPlugin extends DevtoolsPlugin<typeof eventMap> {
Copy link
Collaborator Author

@AlemTuzlak AlemTuzlak Aug 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any typescript wizards who know how to infer the types automatically without providing type args by passing it into the constructor maybe and still constraining the keys to start with the pluginId?

Comment on lines 61 to 68
plugin.onAll((e) => {
if (e.type === 'query-devtools:test') {
console.log('Received query-devtools:test event:', e.payload)
}
})
plugin.on('query-devtools:test', (e) => {
e.payload
})
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fully type-safe

payload: TPayload
}

export class DevtoolsClient {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connects to the server, sends events there, also listens to the events from the server, uses websockets, falls back to SSE

Comment on lines 21 to 26
TEventMap extends Record<string, any>,
TPluginId extends string = TEventMap extends Record<infer P, any>
? P extends `${infer Id}:${string}`
? Id
: never
: never,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would appreciate some help on auto-inferring these without providing type-args

var __EVENT_TARGET__: EventTarget | null
}

export class DevtoolsServer {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

core of the event-bus, emits events to server listeners via EventTarget (might change it to WS instead) and to the client via WS and falls back to SSE.

@KyleAMathews
Copy link

Here's chatgpt's feedback https://chatgpt.com/share/6890e13d-d5ec-800c-a58a-a36afcb640e7

@AlemTuzlak
Copy link
Collaborator Author

Thanks, it made some good points 🙏

@samwillis
Copy link

Really excited by this!

One thought I had was that it could be modelled on MessageChannel / MessagePort api (https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API), it then may be possible to make it plug-and-play with other tools that work with that interface.

For most use cases the event emitter is perfect, just update the state in the devtools, but sometimes you need a RPC type mechanism (trigger some action within the page and read the results). If it was models on the message port api, it may be possible to use it with ComLink to implement RPC: https://github.com/GoogleChromeLabs/comlink

For the DB devtools I intend to bake in an event bus from the start so that we can make a version that works with react native in future.

@AlemTuzlak
Copy link
Collaborator Author

Interesting, I need to check this API out, do you have some advanced examples to get a feeling for what's possible?

@AlemTuzlak
Copy link
Collaborator Author

AlemTuzlak commented Aug 6, 2025

TODO:

  • create a mechanism for not dropping events from plugins
  • figure out why tanstack start emits 2 events instead of 1 (like the spa app does)
  • make the plugin API leaner and not for it to require the prefix
  • figure out how to test this out
  • vite plugin for the dev-server

@AlemTuzlak AlemTuzlak merged commit 190cac8 into main Aug 9, 2025
4 checks passed
@github-actions github-actions bot mentioned this pull request Aug 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants