Agents & OrchestrationSûr100100/100
Devtools Event Client (TanStack)
Create typed EventClient for a library. Define event maps with typed payloads, pluginId auto-prepend namespacing, emit()/on()/onAll()/onAllPluginEvents() API. Connection lifecycle (5 retries, 300ms), event queuing, enabled/disabled state, SSR fallbacks, singleton pattern. Unique pluginId requirement to avoid event collisions.
ou envoie-le directement à ton agent.
Installer dans ton projet
$ curl -fsSL https://arboristhree.xyz/cli/install-skill.mjs -o /tmp/arboris-install-skill.mjs && node /tmp/arboris-install-skill.mjs devtools-event-client__tanstack-devtools__packages-event-bus-client-skills-788b186426e6TanStack
TanStack/devtools
Contenu à copier
---
name: devtools-event-client
description: Create typed EventClient for a library. Define event maps with typed payloads, pluginId auto-prepend namespacing, emit()/on()/onAll()/onAllPluginEvents() API. Connection lifecycle (5 retries, 300ms), event queuing, enabled/disabled state, SSR fallbacks, singleton pattern. Unique pluginId requirement to avoid event collisions.
type: core
library: '@tanstack/devtools-event-client'
library_version: '0.10.12'
sources:
- packages/event-bus-client/src/plugin.ts
- docs/event-system.md
- docs/building-custom-plugins.md
metadata:
origin: TanStack
---
# devtools-event-client
Typed event emitter/listener that connects application code to TanStack Devtools panels. Framework-agnostic. Works in React, Vue, Solid, Preact, and vanilla JS.
## Setup
Install the package:
```bash
npm i @tanstack/devtools-event-client
```
The package has two entry points — the root export (the real client in development, a no-op tree-shaken out of production), and the `/production` subpath (always the real client, for libraries that want devtools events in production):
```ts
import { EventClient } from '@tanstack/devtools-event-client'
```
### Constructor Options
| Option | Type | Required | Default | Description |
| ------------------ | --------- | -------- | ------- | ------------------------------------------------------------------------------------- |
| `pluginId` | `string` | Yes | -- | Identifies this plugin in the event system. Must be unique across all plugins. |
| `debug` | `boolean` | No | `false` | Enable verbose console logging prefixed with `[tanstack-devtools:{pluginId}-plugin]`. |
| `enabled` | `boolean` | No | `true` | When `false`, `emit()` is a no-op and `on()` returns a no-op cleanup function. |
| `reconnectEveryMs` | `number` | No | `300` | Interval in ms between connection retry attempts (max 5 retries). |
## Core Patterns
### 1. Define an Event Map and Create a Singleton Client
Define a TypeScript type mapping event suffixes to payload types. Extend `EventClient` and export a single instance at module level.
```ts
import { EventClient } from '@tanstack/devtools-event-client'
type StoreEvents = {
'state-changed': { storeName: string; state: unknown; timestamp: number }
'action-dispatched': { storeName: string; action: string; payload: unknown }
reset: void
}
class StoreInspectorClient extends EventClient<StoreEvents> {
constructor() {
super({ pluginId: 'store-inspector' })
}
}
// Module-level singleton -- one instance per plugin
export const storeInspector = new StoreInspectorClient()
```
Event map keys are suffixes only. The `pluginId` is prepended automatically. With `pluginId: 'store-inspector'` and key `'state-changed'`, the fully qualified event on the bus is `'store-inspector:state-changed'`.
### 2. Emit Events
Call `emit(suffix, payload)` from library code. Pass only the suffix.
```ts
function dispatch(action: string, payload: unknown) {
state = reducer(state, action, payload)
storeInspector.emit('state-changed', {
storeName: 'main',
state,
timestamp: Date.now(),
})
storeInspector.emit('action-dispatched', {
storeName: 'main',
action,
payload,
})
}
```
If the bus is not connected yet, events are queued in memory and flushed once the connection succeeds. If the connection fails after 5 retries (1.5s at default settings), the client gives up and subsequent `emit()` calls are silently dropped.
Connection to the bus is initiated lazily on the first `emit()` call, not on construction or `on()`.
### 3. Listen to Events
All listener methods return a cleanup function.
**`on(suffix, callback)`** -- listen to a specific event from this plugin:
```ts
const cleanup = storeInspector.on('state-changed', (event) => {
// event.type === 'store-inspector:state-changed'
// event.payload === { storeName: string; state: unknown; timestamp: number }
// event.pluginId === 'store-inspector'
console.log(event.payload.state)
})
// Stop listening
cleanup()
```
**`on(suffix, callback, { withEventTarget: true })`** -- also register on an internal EventTarget so events emitted and listened to on the same client instance are delivered immediately without going through the global bus:
```ts
const cleanup = storeInspector.on(
'state-changed',
(event) => {
console.log(event.payload.state)
},
{ withEventTarget: true },
)
```
**`onAll(callback)`** -- listen to all events from all plugins:
```ts
const cleanup = storeInspector.onAll((event) => {
console.log(event.type, event.payload)
})
```
**`onAllPluginEvents(callback)`** -- listen to all events from this plugin only (filtered by `pluginId`):
```ts
const cleanup = storeInspector.onAllPluginEvents((event) => {
// Only fires when event.pluginId === 'store-inspector'
console.log(event.type, event.payload)
})
```
### 4. Connection Lifecycle and Disabling
The connection lifecycle is:
1. First `emit()` dispatches `tanstack-connect` and starts a retry loop.
2. Retries every `reconnectEveryMs` (default 300ms), up to 5 attempts.
3. On `tanstack-connect-success`, queued events are flushed in order.
4. After 5 failed retries, `failedToConnect` is set permanently. All subsequent `emit()` calls are silently dropped (not queued).
To disable the client entirely (e.g., in production):
```ts
class StoreInspectorClient extends EventClient<StoreEvents> {
constructor() {
super({
pluginId: 'store-inspector',
enabled: process.env.NODE_ENV !== 'production',
})
}
}
```
When `enabled` is `false`, `emit()` is a no-op and `on()`/`onAll()`/`onAllPluginEvents()` return no-op cleanup functions.
## Common Mistakes
### 1. Including pluginId prefix in event names (CRITICAL)
`EventClient` auto-prepends the `pluginId` to all event names. Including the prefix manually produces a double-prefixed event name that nothing will match.
Wrong:
```ts
storeInspector.emit('store-inspector:state-changed', data)
// Dispatches 'store-inspector:store-inspector:state-changed'
```
Correct:
```ts
storeInspector.emit('state-changed', data)
// Dispatches 'store-inspector:state-changed'
```
This applies to `on()` as well. Pass only the suffix.
### 2. Creating multiple EventClient instances per plugin (CRITICAL)
Each `EventClient` instance manages its own connection, event queue, and listeners independently. Creating multiple instances for the same plugin causes duplicate handlers, multiple connection attempts, and unpredictable event delivery.
Wrong:
```tsx
function MyComponent() {
// New instance on every render
const client = new StoreInspectorClient()
client.emit('state-changed', data)
}
```
Correct:
```ts
// store-inspector-client.ts
export const storeInspector = new StoreInspectorClient()
// MyComponent.tsx
import { storeInspector } from './store-inspector-client'
function MyComponent() {
storeInspector.emit('state-changed', data)
}
```
### 3. Non-unique pluginId causing event collisions (CRITICAL)
Two plugins with the same `pluginId` share an event namespace. Events emitted by one are received by listeners on the other. Choose a unique, descriptive `pluginId` (e.g., `'my-org-store-inspector'` rather than `'store'`).
### 4. Not realizing events drop after 5 failed retries (HIGH)
After 5 retries (1.5s at default `reconnectEveryMs: 300`), `failedToConnect` is set permanently. Subsequent `emit()` calls are silently dropped -- they are not queued and will never be delivered, even if the bus becomes available later.
If you need events to survive longer startup delays, increase `reconnectEveryMs`:
```ts
super({ pluginId: 'store-inspector', reconnectEveryMs: 1000 })
// 5 retries * 1000ms = 5s window
```
There is no way to increase the retry count (hardcoded to 5).
### 5. Expecting connection on construction or on() (HIGH)
The connection to the event bus is initiated lazily on the first `emit()` call. Calling `on()` alone does not trigger a connection. If your panel calls `on()` but the library side never calls `emit()`, the client never connects to the bus.
This means if you only listen (no emitting), the `on()` handler still works for events dispatched directly on the global event target, but the connection handshake (`tanstack-connect` / `tanstack-connect-success`) never runs.
### 6. Using non-serializable payloads (HIGH)
When the server event bus is enabled, events are serialized via JSON for transport over WebSocket/SSE/BroadcastChannel. Payloads containing functions, DOM nodes, class instances, `Map`/`Set`, or circular references will fail silently or lose data.
Wrong:
```ts
storeInspector.emit('state-changed', {
storeName: 'main',
state,
callback: () => {}, // Function -- not serializable
element: document.body, // DOM node -- not serializable
})
```
Correct:
```ts
storeInspector.emit('state-changed', {
storeName: 'main',
state: JSON.parse(JSON.stringify(state)), // Ensure serializable
timestamp: Date.now(),
})
```
### 7. Forgetting the root export no-ops in production (HIGH)
The **root import** of `@tanstack/devtools-event-client` resolves to a no-op
when `process.env.NODE_ENV !== 'development'`, and the real client is
tree-shaken out of production bundles. This is the default and what you want for
most libraries — your `emit()` calls cost nothing in production.
"Outside development" includes when `NODE_ENV` is unset — common in plain Node scripts, some SSR dev servers, and test runners — so the root import resolves to the no-op there too. Set `NODE_ENV=development`, or use the `/production` subpath, to get the real client in those contexts.
```ts
// dev: real client — production: no-op, removed from the bundle
import { EventClient } from '@tanstack/devtools-event-client'
```
If you are publishing an open-source library and deliberately want devtools
events to keep working in production, import from the `/production` subpath,
which always ships the real client:
```ts
import { EventClient } from '@tanstack/devtools-event-client/production'
```
The `enabled` constructor option still works for fine-grained runtime control,
but you no longer need to guard `emit()` calls manually for bundle size — the
root export handles that for you.
## See Also
- `devtools-instrumentation` -- after creating a client, instrument library code with strategic emissions
- `devtools-plugin-panel` -- the client emits events, the panel listens using the same event map
- `devtools-bidirectional` -- two-way communication between panel and application using the same EventClientColle ce Markdown dans ton agent ou utilise les boutons ci-dessus pour l’écrire dans ton projet.
Ce que fait Devtools Event Client (TanStack)
`devtools-instrumentation` -- after creating a client, instrument library code with strategic emissions
`devtools-plugin-panel` -- the client emits events, the panel listens using the same event map
`devtools-bidirectional` -- two-way communication between panel and application using the same EventClient
Comment utiliser Devtools Event Client (TanStack)
1
Copie le prompt
Un clic copie le prompt packagé (ou l'envoie à ton agent).
2
L'agent installe le skill
Il ajoute le SKILL.md et ses ressources à ton projet.
3
Activation automatique
Le skill s'active dès que le contexte correspond.
Déclencheurs pour Devtools Event Client (TanStack)
Dis simplement à ton agent quelque chose comme :
Applique le skill devtools-event-client__tanstack-devtools__packages-event-bus-client-skills-788b186426e6 à cette tâche
Utilise devtools-event-client__tanstack-devtools__packages-event-bus-client-skills-788b186426e6 pour améliorer cette implémentation
Passe en revue ce sujet avec devtools-event-client__tanstack-devtools__packages-event-bus-client-skills-788b186426e6
Skills liés à Devtools Event Client (TanStack)
Acceleration (Microsoft)
Compare synthetic acceleration options and clearly separate scenario value from forecast commitments.Account Overview (Microsoft)
Create a synthetic Acme account overview and identify evidence that merits seller attention.Acroform Writer (Microsoft)
Use this skill whenever the user wants values written INTO an existing PDF form that lives in the connected SharePoint knowledge source — filling out, completing, populating, or submitting a fillable PDF (application, intake sheet, contract, government form) from data they supply, a spreadsheet row, or the conversation. Triggers include "fill out the job application form", "complete the intake form for [person]", "populate our standard NDA template", and "make this ready to send / non-editable" (flatten). The source form should be found via knowledge search; only ask the user to upload it if knowledge search turns up nothing plausible. different task), and do NOT use it on scanned/image-only PDFs that have no real fillable fields — those need OCR or manual overlay instead, which this skill explicitly detects and reports rather than silently failing.