Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8d323f3
feat: create event bus server + client communication for devtools
AlemTuzlak Aug 4, 2025
0162bc1
ci: apply automated fixes
autofix-ci[bot] Aug 4, 2025
467ea88
chore: fix some bugs, eslint, knip
AlemTuzlak Aug 5, 2025
b16e430
Merge branch 'feat/bus-connection' of https://github.com/TanStack/dev…
AlemTuzlak Aug 5, 2025
479415b
feat: drastically improve the event buses, split into server/client, …
AlemTuzlak Aug 6, 2025
77f700d
chore: update lock
AlemTuzlak Aug 6, 2025
f1b9a89
ci: apply automated fixes
autofix-ci[bot] Aug 6, 2025
06553f8
feat: improve server/client logging via flag, fix bugs and duplicate …
AlemTuzlak Aug 7, 2025
a1242f8
chore: update README
AlemTuzlak Aug 7, 2025
1333d87
ci: apply automated fixes
autofix-ci[bot] Aug 7, 2025
8e362c1
feat: enhance plugin api contract and typing
AlemTuzlak Aug 7, 2025
f7009a5
feat: improve plugin ts typing
AlemTuzlak Aug 7, 2025
26f09ba
ci: apply automated fixes
autofix-ci[bot] Aug 7, 2025
51ba6bf
chore: rename package exports and names
AlemTuzlak Aug 8, 2025
236d90a
Merge branch 'main' into feat/bus-connection
AlemTuzlak Aug 8, 2025
b6cf44d
ci: apply automated fixes
autofix-ci[bot] Aug 8, 2025
6329d4d
chore: add tests for the EventClient
AlemTuzlak Aug 8, 2025
537bc1f
ci: apply automated fixes
autofix-ci[bot] Aug 8, 2025
b72cc40
chore: more tests for client bus
AlemTuzlak Aug 8, 2025
7924487
chore: improve the client event bus api
AlemTuzlak Aug 8, 2025
0a0219b
chore: fix extra config issue in start example
AlemTuzlak Aug 8, 2025
a9ee4dc
chore: fix logs in client bus
AlemTuzlak Aug 8, 2025
77e2f6e
chore: changeset added
AlemTuzlak Aug 9, 2025
ac8c398
ci: apply automated fixes
autofix-ci[bot] Aug 9, 2025
758e3a8
Merge branch 'main' into feat/bus-connection
AlemTuzlak Aug 9, 2025
d268447
Merge branch 'feat/bus-connection' of https://github.com/TanStack/dev…
AlemTuzlak Aug 9, 2025
9500cc6
chore: fix readme
AlemTuzlak Aug 9, 2025
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
15 changes: 15 additions & 0 deletions .changeset/metal-aliens-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@tanstack/devtools-event-client': minor
'@tanstack/react-devtools': minor
'@tanstack/solid-devtools': minor
'@tanstack/devtools-event-bus': minor
'@tanstack/devtools': minor
---

Added event bus functionality into @tanstack/devtools

- @tanstack/devtools now comes with an integrated Event Bus on the Client.
- The Event Bus allows for seamless communication between different parts of your running application
without tight coupling.
- Exposed APIs for publishing and subscribing to events.
- Added config for the client event bus
4 changes: 3 additions & 1 deletion examples/react/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
"test:types": "tsc"
},
"dependencies": {
"@tanstack/devtools-event-client": "workspace:^",
"@tanstack/react-devtools": "^0.1.1",
"@tanstack/react-query": "^5.83.0",
"@tanstack/react-query-devtools": "^5.83.0",
"@tanstack/react-router": "^1.130.2",
"@tanstack/react-router-devtools": "^1.130.2",
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"zod": "^4.0.14"
},
"devDependencies": {
"@types/react": "^19.1.2",
Expand Down
11 changes: 11 additions & 0 deletions examples/react/basic/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import ReactDOM from 'react-dom/client'
import Devtools from './setup'
import { queryPlugin } from './plugin'
setTimeout(() => {
queryPlugin.emit('test', {
title: 'Test Event',
description:
'This is a test event from the TanStack Query Devtools plugin.',
})
}, 1000)

queryPlugin.on('test', (event) => {
console.log('Received test event:', event)
})
function App() {
return (
<div>
Expand Down
25 changes: 25 additions & 0 deletions examples/react/basic/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { EventClient } from '@tanstack/devtools-event-client'

interface EventMap {
'query-devtools:test': {
title: string
description: string
}
'query-devtools:init': {
title: string
description: string
}
'query-devtools:query': {
title: string
description: string
}
}
class QueryDevtoolsPlugin extends EventClient<EventMap> {
constructor() {
super({
pluginId: 'query-devtools',
})
}
}

export const queryPlugin = new QueryDevtoolsPlugin()
8 changes: 6 additions & 2 deletions examples/react/start/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,25 @@
"@prisma/extension-accelerate": "^2.0.2",
"@prisma/studio-core": "^0.5.1",
"@tailwindcss/vite": "^4.0.6",
"@tanstack/devtools-event-bus": "workspace:^",
"@tanstack/devtools-event-client": "workspace:^",
"@tanstack/react-devtools": "^0.1.1",
"@tanstack/react-query": "^5.83.0",
"@tanstack/react-query-devtools": "^5.83.0",
"@tanstack/react-router": "^1.130.2",
"@tanstack/react-router-devtools": "^1.130.2",
"@tanstack/react-router-with-query": "^1.130.2",
"@tanstack/react-start": "^1.130.2",
"@tanstack/react-start": "^1.130.15",
"@tanstack/router-plugin": "^1.121.2",
"prisma": "^6.13.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"tailwindcss": "^4.0.6",
"vite-tsconfig-paths": "^5.1.4"
"vite-tsconfig-paths": "^5.1.4",
"zod": "^4.0.14"
},
"devDependencies": {
"@tanstack/devtools": "workspace:^",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0",
"@types/react": "^19.1.2",
Expand Down
41 changes: 41 additions & 0 deletions examples/react/start/src/components/client-plugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { queryPlugin } from '@/plugin'
import { useEffect, useState } from 'react'

export default function ClientPlugin() {
const [events, setEvents] = useState<
Array<{ type: string; payload: { title: string; description: string } }>
>([])
useEffect(() => {
const cleanup = queryPlugin.on('test', (event) => {
console.log('Received message in here:', event)
setEvents((prev) => [...prev, event as any])
})

return cleanup
}, [])
return (
<div>
<h1>Client Plugin Initialized</h1>
<p>Devtools Client is connected.</p>
<button
className="bg-blue-500 text-white px-4 py-2 rounded"
onClick={() => {
console.log('Button clicked, emitting event')
queryPlugin.emit('test', {
title: 'Button Clicked',
description:
'This is a custom event triggered by the client plugin.',
})
}}
>
Click me
</button>
{events.map((event, i) => (
<div key={i}>
<h2>{event.payload.title}</h2>
<p style={{ color: 'gray' }}>{event.payload.description}</p>
</div>
))}
</div>
)
}
8 changes: 8 additions & 0 deletions examples/react/start/src/components/devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
import { TanstackDevtools } from '@tanstack/react-devtools'
import { StudioPlugin } from './prisma-plugin'
import ClientPlugin from './client-plugin'

const queryClient = new QueryClient()

Expand All @@ -11,6 +12,9 @@ export default function DevtoolsExample() {
<>
<QueryClientProvider client={queryClient}>
<TanstackDevtools
eventBusConfig={{
debug: true,
}}
plugins={[
{
name: 'Tanstack Query',
Expand All @@ -24,6 +28,10 @@ export default function DevtoolsExample() {
name: 'Prisma Studio',
render: <StudioPlugin />,
},
{
name: 'Client Plugin',
render: <ClientPlugin />,
},
]}
/>
</QueryClientProvider>
Expand Down
26 changes: 26 additions & 0 deletions examples/react/start/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { EventClient } from '@tanstack/devtools-event-client'

interface EventMap {
'query-devtools:test': {
title: string
description: string
}
'query-devtools:init': {
title: string
description: string
}
'query-devtools:query': {
title: string
description: string
}
}

class QueryDevtoolsClient extends EventClient<EventMap> {
constructor() {
super({
pluginId: 'query-devtools',
})
}
}

export const queryPlugin = new QueryDevtoolsClient()
7 changes: 7 additions & 0 deletions examples/react/start/src/server-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ServerEventBus } from '@tanstack/devtools-event-bus/server'

const devtoolsServer = new ServerEventBus()

devtoolsServer.start()

export { devtoolsServer }
2 changes: 2 additions & 0 deletions examples/react/start/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'
import viteTsConfigPaths from 'vite-tsconfig-paths'
import tailwindcss from '@tailwindcss/vite'
//import { devtoolsServer } from './src/server-setup'

const config = defineConfig({
plugins: [
// this is the plugin that enables path aliases
viteTsConfigPaths({
projects: ['./tsconfig.json'],
}),

tailwindcss(),
tanstackStart({
customViteReactPlugin: true,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"changeset:version": "changeset version && pnpm install --no-frozen-lockfile && pnpm prettier:write",
"clean": "find . -name 'dist' -type d -prune -exec rm -rf {} +",
"clean:node_modules": "find . -name 'node_modules' -type d -prune -exec rm -rf {} +",
"clean:all": "pnpm run clean && pnpm run clean:node_modules",
"dev": "pnpm run watch",
"docs:generate": "node scripts/generateDocs.js",
"format": "pnpm run prettier:write",
Expand Down
4 changes: 2 additions & 2 deletions packages/devtools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ In order to create a plugin for TanStack Devtools, you can use the `plugins` arg
import { TanStackDevtoolsCore } from '@tanstack/devtools'

const devtools = new TanStackDevtoolsCore({
options: {
// your options here
config: {
// your config here
},
plugins: [
{
Expand Down
1 change: 1 addition & 0 deletions packages/devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
},
"dependencies": {
"@solid-primitives/keyboard": "^1.2.8",
"@tanstack/devtools-event-bus": "workspace:*",
"clsx": "^2.1.1",
"goober": "^2.1.16",
"solid-js": "^1.9.7"
Expand Down
13 changes: 11 additions & 2 deletions packages/devtools/src/core.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { lazy } from 'solid-js'
import { Portal, render } from 'solid-js/web'
import { ClientEventBus } from '@tanstack/devtools-event-bus/client'
import { DevtoolsProvider } from './context/devtools-context'
import { initialState } from './context/devtools-store'
import type { ClientEventBusConfig } from '@tanstack/devtools-event-bus/client'
import type {
TanStackDevtoolsConfig,
TanStackDevtoolsPlugin,
Expand Down Expand Up @@ -34,6 +36,7 @@ export interface TanStackDevtoolsInit {
* ```
*/
plugins?: Array<TanStackDevtoolsPlugin>
eventBusConfig?: ClientEventBusConfig
}

export class TanStackDevtoolsCore {
Expand All @@ -44,9 +47,12 @@ export class TanStackDevtoolsCore {
#isMounted = false
#dispose?: () => void
#Component: any
#eventBus: ClientEventBus | undefined
#eventBusConfig: ClientEventBusConfig | undefined

constructor(init: TanStackDevtoolsInit) {
this.#plugins = init.plugins || []
this.#eventBusConfig = init.eventBusConfig
this.#config = {
...this.#config,
...init.config,
Expand All @@ -60,9 +66,9 @@ export class TanStackDevtoolsCore {
const mountTo = el
const dispose = render(() => {
this.#Component = lazy(() => import('./devtools'))

const Devtools = this.#Component

this.#eventBus = new ClientEventBus(this.#eventBusConfig)
this.#eventBus.start()
return (
<DevtoolsProvider plugins={this.#plugins} config={this.#config}>
<Portal mount={mountTo}>
Expand All @@ -80,6 +86,7 @@ export class TanStackDevtoolsCore {
if (!this.#isMounted) {
throw new Error('Devtools is not mounted')
}
this.#eventBus?.stop()
this.#dispose?.()
this.#isMounted = false
}
Expand All @@ -91,3 +98,5 @@ export class TanStackDevtoolsCore {
}
}
}

export type { ClientEventBusConfig }
2 changes: 1 addition & 1 deletion packages/devtools/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { PLUGIN_CONTAINER_ID, PLUGIN_TITLE_CONTAINER_ID } from './constants'
export { TanStackDevtoolsCore } from './core'
export type { TanStackDevtoolsInit } from './core'
export type { TanStackDevtoolsInit, ClientEventBusConfig } from './core'
export type {
TanStackDevtoolsPlugin,
TanStackDevtoolsConfig,
Expand Down
8 changes: 7 additions & 1 deletion packages/devtools/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"extends": "../../tsconfig.json",
"include": ["src", "eslint.config.js", "vite.config.ts", "tests"],
"include": [
"src",
"eslint.config.js",
"vite.config.ts",
"tests",
"src/server"
],
"compilerOptions": {
"jsxImportSource": "solid-js",
"jsx": "preserve"
Expand Down
39 changes: 39 additions & 0 deletions packages/event-bus-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# @tanstack/devtools

This package is still under active development and might have breaking changes in the future. Please use it with caution.

## General Usage

```tsx
import { EventClient } from '@tanstack/devtools-event-client'

interface EventMap {
'query-devtools:a': { foo: string }
'query-devtools:b': { foo: number }
}

class QueryDevtoolsPlugin extends EventClient<EventMap> {
constructor() {
super({
pluginId: 'query-devtools',
})
}
}

export const queryPlugin = new QueryDevtoolsPlugin()

// I'm fully typed here
plugin.emit('a', {
foo: 'bar',
})
plugin.on('b', (e) => {
// I'm fully typed here
e.payload.foo
})
```

## Understanding the implementation

The `EventClient` class is a base class for creating plugins that can subscribe to events in the Tanstack Devtools event bus. It allows you to define a set of events and their corresponding data schemas using a standard-schema based schemas.

It will work on both the client/server side and all you have to worry about is emitting/listening to events.
10 changes: 10 additions & 0 deletions packages/event-bus-client/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @ts-check

import rootConfig from '../../eslint.config.js'

export default [
...rootConfig,
{
rules: {},
},
]
Loading