Skip to content

Commit 035a375

Browse files
authored
Merge pull request #18 from TanStack/feat/event-queue
feat: add queuing to event-clients
2 parents 8752b6d + 8a4bfae commit 035a375

File tree

7 files changed

+152
-8
lines changed

7 files changed

+152
-8
lines changed

.changeset/eleven-garlics-work.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@tanstack/devtools-event-client': patch
3+
'@tanstack/devtools-event-bus': patch
4+
---
5+
6+
add queued events to event bus

examples/react/start/src/plugin.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@ class QueryDevtoolsClient extends EventClient<EventMap> {
1919
constructor() {
2020
super({
2121
pluginId: 'query-devtools',
22+
debug: true,
2223
})
2324
}
2425
}
2526

2627
export const queryPlugin = new QueryDevtoolsClient()
28+
// this should be queued and emitted when bus is available
29+
queryPlugin.emit('test', {
30+
title: 'Query Devtools',
31+
description: 'A plugin for query debugging',
32+
})

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

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,37 @@ export class EventClient<
2323
#pluginId: TPluginId
2424
#eventTarget: () => EventTarget
2525
#debug: boolean
26+
#queuedEvents: Array<TanStackDevtoolsEvent<string, any>>
27+
#connected: boolean
28+
#connectIntervalId: number | null
29+
#connectEveryMs: number
30+
#retryCount = 0
31+
#maxRetries = 5
32+
#onConnected = () => {
33+
this.debugLog('Connected to event bus')
34+
this.#connected = true
35+
this.debugLog('Emitting queued events', this.#queuedEvents)
36+
this.#queuedEvents.forEach((event) => this.emitEventToBus(event))
37+
this.#queuedEvents = []
38+
this.stopConnectLoop()
39+
this.#eventTarget().removeEventListener(
40+
'tanstack-connect-success',
41+
this.#onConnected,
42+
)
43+
}
44+
#connectFunction = () => {
45+
if (this.#retryCount < this.#maxRetries) {
46+
this.#retryCount++
47+
this.#eventTarget().dispatchEvent(new CustomEvent('tanstack-connect'))
48+
return
49+
}
50+
this.#eventTarget().removeEventListener(
51+
'tanstack-connect',
52+
this.#connectFunction,
53+
)
54+
this.debugLog('Max retries reached, giving up on connection')
55+
this.stopConnectLoop()
56+
}
2657

2758
constructor({
2859
pluginId,
@@ -35,6 +66,36 @@ export class EventClient<
3566
this.#eventTarget = this.getGlobalTarget
3667
this.#debug = debug
3768
this.debugLog(' Initializing event subscription for plugin', this.#pluginId)
69+
this.#queuedEvents = []
70+
this.#connected = false
71+
this.#connectIntervalId = null
72+
this.#connectEveryMs = 500
73+
74+
this.#eventTarget().addEventListener(
75+
'tanstack-connect-success',
76+
this.#onConnected,
77+
)
78+
this.#connectFunction()
79+
this.startConnectLoop()
80+
}
81+
82+
private startConnectLoop() {
83+
if (this.#connectIntervalId !== null || this.#connected) return
84+
this.debugLog(`Starting connect loop (every ${this.#connectEveryMs}ms)`)
85+
86+
this.#connectIntervalId = setInterval(
87+
this.#connectFunction,
88+
this.#connectEveryMs,
89+
) as unknown as number
90+
}
91+
92+
private stopConnectLoop() {
93+
if (this.#connectIntervalId === null) {
94+
return
95+
}
96+
clearInterval(this.#connectIntervalId)
97+
this.#connectIntervalId = null
98+
this.debugLog('Stopped connect loop')
3899
}
39100

40101
private debugLog(...args: Array<any>) {
@@ -84,7 +145,17 @@ export class EventClient<
84145
eventSuffix: TSuffix,
85146
payload: TEventMap[`${TPluginId & string}:${TSuffix}`],
86147
) {
87-
this.emitEventToBus({
148+
// wait to connect to the bus
149+
if (!this.#connected) {
150+
this.debugLog('Bus not available, will be pushed as soon as connected')
151+
return this.#queuedEvents.push({
152+
type: `${this.#pluginId}:${eventSuffix}`,
153+
payload,
154+
pluginId: this.#pluginId,
155+
})
156+
}
157+
// emit right now
158+
return this.emitEventToBus({
88159
type: `${this.#pluginId}:${eventSuffix}`,
89160
payload,
90161
pluginId: this.#pluginId,

packages/event-bus-client/tests/index.test.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { describe, expect, it, vi } from 'vitest'
2-
import { EventClient } from '../src'
32
import { ClientEventBus } from '@tanstack/devtools-event-bus/client'
3+
import { EventClient } from '../src'
44

55
// start the client bus for testing
6-
new ClientEventBus().start()
6+
const bus = new ClientEventBus()
7+
bus.start()
78
// client bus uses window to dispatch events
89
const clientBusEmitTarget = window
9-
1010
describe('EventClient', () => {
1111
describe('debug config', () => {
1212
it('should emit logs when debug set to true and have the correct plugin name', () => {
@@ -35,7 +35,19 @@ describe('EventClient', () => {
3535
describe('getGlobalTarget', () => {
3636
it('if the global target is set it should re-use it for emitting/listening/removing of events', () => {
3737
const target = new EventTarget()
38-
globalThis.__TANSTACK_EVENT_TARGET__ = target
38+
const handleSuccessConnection = vi.fn()
39+
target.addEventListener('tanstack-connect', () => {
40+
target.dispatchEvent(new CustomEvent('tanstack-connect-success'))
41+
})
42+
globalThis.__TANSTACK_EVENT_TARGET__ = null
43+
44+
vi.spyOn(
45+
globalThis,
46+
'__TANSTACK_EVENT_TARGET__',
47+
'get',
48+
).mockImplementation(() => {
49+
return target
50+
})
3951
const client = new EventClient({
4052
debug: false,
4153
pluginId: 'test',
@@ -55,9 +67,9 @@ describe('EventClient', () => {
5567
expect.any(String),
5668
expect.any(Function),
5769
)
58-
globalThis.__TANSTACK_EVENT_TARGET__ = null
70+
vi.resetAllMocks()
71+
target.removeEventListener('tanstack-connect', handleSuccessConnection)
5972
})
60-
6173
it('should use the window object if the globalTarget is not set for emitting/listening/removing of events', () => {
6274
const target = window
6375
const client = new EventClient({
@@ -183,6 +195,27 @@ describe('EventClient', () => {
183195
})
184196
})
185197

198+
describe('queued events', () => {
199+
it('emits queued events when connected to the event bus', async () => {
200+
bus.stop()
201+
const client = new EventClient({
202+
debug: false,
203+
pluginId: 'test',
204+
})
205+
const eventHandler = vi.fn()
206+
client.on('event', eventHandler)
207+
client.emit('event', { foo: 'bar' })
208+
209+
bus.start()
210+
// wait to connect to the bus
211+
await new Promise((resolve) => setTimeout(resolve, 500))
212+
expect(eventHandler).toHaveBeenCalledWith({
213+
type: 'test:event',
214+
payload: { foo: 'bar' },
215+
pluginId: 'test',
216+
})
217+
})
218+
})
186219
describe('onAllPluginEvents', () => {
187220
it('should listen to all events that come from the plugin', () => {
188221
const client = new EventClient({

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,18 @@ export class ClientEventBus {
3030
#eventTarget: EventTarget
3131
#debug: boolean
3232
#connectToServerBus: boolean
33+
3334
#dispatcher = (e: Event) => {
3435
const event = (e as CustomEvent).detail
3536
this.emitToServer(event)
3637
this.emitToClients(event)
3738
}
39+
#connectFunction = () => {
40+
this.debugLog(
41+
'Connection request made to event-bus, replying back with success',
42+
)
43+
this.#eventTarget.dispatchEvent(new CustomEvent('tanstack-connect-success'))
44+
}
3845
constructor({
3946
port = 42069,
4047
debug = false,
@@ -46,6 +53,7 @@ export class ClientEventBus {
4653
this.#socket = null
4754
this.#connectToServerBus = connectToServerBus
4855
this.#eventTarget = this.getGlobalTarget()
56+
4957
this.debugLog('Initializing client event bus')
5058
}
5159

@@ -91,6 +99,10 @@ export class ClientEventBus {
9199
'tanstack-dispatch-event',
92100
this.#dispatcher,
93101
)
102+
this.#eventTarget.addEventListener(
103+
'tanstack-connect',
104+
this.#connectFunction,
105+
)
94106
}
95107
stop() {
96108
this.debugLog('Stopping client event bus')
@@ -101,6 +113,10 @@ export class ClientEventBus {
101113
'tanstack-dispatch-event',
102114
this.#dispatcher,
103115
)
116+
this.#eventTarget.removeEventListener(
117+
'tanstack-connect',
118+
this.#connectFunction,
119+
)
104120
this.#eventSource?.close()
105121
this.#socket?.close()
106122
this.#socket = null

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ export class ServerEventBus {
3333
this.debugLog('Dispatching event from dispatcher, forwarding', event)
3434
this.emit(event)
3535
}
36-
36+
#connectFunction = () => {
37+
this.#eventTarget.dispatchEvent(new CustomEvent('tanstack-connect-success'))
38+
}
3739
constructor({ port = 42069, debug = false } = {}) {
3840
this.#port = port
3941
this.#eventTarget = globalThis.__EVENT_TARGET__ ?? new EventTarget()
@@ -165,6 +167,10 @@ export class ServerEventBus {
165167
'tanstack-dispatch-event',
166168
this.#dispatcher,
167169
)
170+
this.#eventTarget.addEventListener(
171+
'tanstack-connect',
172+
this.#connectFunction,
173+
)
168174
this.handleNewConnection(wss)
169175

170176
// Handle connection upgrade for WebSocket
@@ -200,6 +206,10 @@ export class ServerEventBus {
200206
'tanstack-dispatch-event',
201207
this.#dispatcher,
202208
)
209+
this.#eventTarget.removeEventListener(
210+
'tanstack-connect',
211+
this.#connectFunction,
212+
)
203213
this.debugLog('[tanstack-devtools] All connections cleared')
204214
}
205215
}

pnpm-lock.yaml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)