From 08a4fe34050b17fc1f5aff0eaf9a3373bfc8ede6 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Mon, 8 Dec 2025 11:45:26 +0100 Subject: [PATCH 1/4] feat: add optional event emission to current event target and fix up memory leak --- packages/event-bus-client/src/plugin.ts | 58 +++++++++++++++---- packages/event-bus-client/tests/index.test.ts | 21 +++++++ 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/packages/event-bus-client/src/plugin.ts b/packages/event-bus-client/src/plugin.ts index 6372ea1f..15f81851 100644 --- a/packages/event-bus-client/src/plugin.ts +++ b/packages/event-bus-client/src/plugin.ts @@ -30,6 +30,7 @@ export class EventClient< #retryCount = 0 #maxRetries = 5 #connecting = false + #internalEventTarget: EventTarget | null = null #onConnected = () => { this.debugLog('Connected to event bus') @@ -112,6 +113,7 @@ export class EventClient< } clearInterval(this.#connectIntervalId) this.#connectIntervalId = null + this.#queuedEvents = [] this.debugLog('Stopped connect loop') } @@ -189,6 +191,23 @@ export class EventClient< this.dispatchCustomEvent('tanstack-dispatch-event', event) } + createEventPayload< + TSuffix extends Extract< + keyof TEventMap, + `${TPluginId & string}:${string}` + > extends `${TPluginId & string}:${infer S}` + ? S + : never, + >( + eventSuffix: TSuffix, + payload: TEventMap[`${TPluginId & string}:${TSuffix}`], + ) { + return { + type: `${this.#pluginId}:${eventSuffix}`, + payload, + pluginId: this.#pluginId, + } + } emit< TSuffix extends Extract< keyof TEventMap, @@ -200,6 +219,18 @@ export class EventClient< eventSuffix: TSuffix, payload: TEventMap[`${TPluginId & string}:${TSuffix}`], ) { + if (this.#internalEventTarget) { + this.debugLog( + 'Emitting event to internal event target', + eventSuffix, + payload, + ) + this.#internalEventTarget.dispatchEvent( + new CustomEvent(`${this.#pluginId}:${eventSuffix}`, { + detail: this.createEventPayload(eventSuffix, payload), + }), + ) + } if (!this.#enabled) { this.debugLog( 'Event bus client is disabled, not emitting event', @@ -211,11 +242,7 @@ export class EventClient< // wait to connect to the bus if (!this.#connected) { this.debugLog('Bus not available, will be pushed as soon as connected') - this.#queuedEvents.push({ - type: `${this.#pluginId}:${eventSuffix}`, - payload, - pluginId: this.#pluginId, - }) + this.#queuedEvents.push(this.createEventPayload(eventSuffix, payload)) // start connection to event bus if (typeof CustomEvent !== 'undefined' && !this.#connecting) { this.#connectFunction() @@ -224,11 +251,7 @@ export class EventClient< return } // emit right now - return this.emitEventToBus({ - type: `${this.#pluginId}:${eventSuffix}`, - payload, - pluginId: this.#pluginId, - }) + return this.emitEventToBus(this.createEventPayload(eventSuffix, payload)) } on< @@ -246,8 +269,20 @@ export class EventClient< TEventMap[`${TPluginId & string}:${TSuffix}`] >, ) => void, + options?: { + withEventTarget?: boolean + }, ) { + const withEventTarget = options?.withEventTarget ?? false const eventName = `${this.#pluginId}:${eventSuffix}` as const + if (withEventTarget) { + if (!this.#internalEventTarget) { + this.#internalEventTarget = new EventTarget() + } + this.#internalEventTarget.addEventListener(eventName, (e) => { + cb((e as CustomEvent).detail) + }) + } if (!this.#enabled) { this.debugLog( 'Event bus client is disabled, not registering event', @@ -262,6 +297,9 @@ export class EventClient< this.#eventTarget().addEventListener(eventName, handler) this.debugLog('Registered event to bus', eventName) return () => { + if (withEventTarget) { + this.#internalEventTarget?.removeEventListener(eventName, handler) + } this.#eventTarget().removeEventListener(eventName, handler) } } diff --git a/packages/event-bus-client/tests/index.test.ts b/packages/event-bus-client/tests/index.test.ts index 219ae67d..3d9bc2ba 100644 --- a/packages/event-bus-client/tests/index.test.ts +++ b/packages/event-bus-client/tests/index.test.ts @@ -284,6 +284,27 @@ describe('EventClient', () => { }) }) + describe('emitting to internal event target', () => { + it('should initialize and dispatch events to the internal event target', () => { + const client = new EventClient({ + debug: false, + pluginId: 'test-internal', + }) + const internalEventHandler = vi.fn() + client.on('event', internalEventHandler, { + withEventTarget: true, + }) + client.emit('event', { foo: 'bar' }) + expect(internalEventHandler).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'test-internal:event', + payload: { foo: 'bar' }, + pluginId: 'test-internal', + }), + ) + }) + }) + describe('connecting behavior', () => { it('should only attempt connection once when #connecting flag is set', async () => { bus.stop() From 573cb57ae338c0b2a889b2afd6d4b1f17583d381 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Mon, 8 Dec 2025 11:46:12 +0100 Subject: [PATCH 2/4] chore: changeset --- .changeset/real-weeks-relax.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/real-weeks-relax.md diff --git a/.changeset/real-weeks-relax.md b/.changeset/real-weeks-relax.md new file mode 100644 index 00000000..4d7ff56e --- /dev/null +++ b/.changeset/real-weeks-relax.md @@ -0,0 +1,5 @@ +--- +'@tanstack/devtools-event-client': minor +--- + +fix memory leak and add internal event emission From b3f3b9bae7d72b698e03ab580d64eba2dbd549d6 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Mon, 8 Dec 2025 11:56:08 +0100 Subject: [PATCH 3/4] chore: fix up potential evvent buildup on failed connections --- package.json | 4 ++-- packages/event-bus-client/src/plugin.ts | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index c8d90d6a..f1a71890 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ }, { "path": "packages/event-bus-client/dist/esm/plugin.js", - "limit": "1.1 KB" + "limit": "1.2 KB" } ], "devDependencies": { @@ -91,4 +91,4 @@ "@tanstack/solid-devtools": "workspace:*", "@tanstack/devtools-vite": "workspace:*" } -} +} \ No newline at end of file diff --git a/packages/event-bus-client/src/plugin.ts b/packages/event-bus-client/src/plugin.ts index 15f81851..99b36151 100644 --- a/packages/event-bus-client/src/plugin.ts +++ b/packages/event-bus-client/src/plugin.ts @@ -30,6 +30,7 @@ export class EventClient< #retryCount = 0 #maxRetries = 5 #connecting = false + #failedToConnect = false #internalEventTarget: EventTarget | null = null #onConnected = () => { @@ -57,7 +58,7 @@ export class EventClient< 'tanstack-connect', this.#retryConnection, ) - + this.#failedToConnect = true this.debugLog('Max retries reached, giving up on connection') this.stopConnectLoop() } @@ -91,6 +92,7 @@ export class EventClient< this.debugLog(' Initializing event subscription for plugin', this.#pluginId) this.#queuedEvents = [] this.#connected = false + this.#failedToConnect = false this.#connectIntervalId = null this.#connectEveryMs = reconnectEveryMs } @@ -108,6 +110,7 @@ export class EventClient< private stopConnectLoop() { this.#connecting = false + if (this.#connectIntervalId === null) { return } @@ -219,6 +222,14 @@ export class EventClient< eventSuffix: TSuffix, payload: TEventMap[`${TPluginId & string}:${TSuffix}`], ) { + if (!this.#enabled) { + this.debugLog( + 'Event bus client is disabled, not emitting event', + eventSuffix, + payload, + ) + return + } if (this.#internalEventTarget) { this.debugLog( 'Emitting event to internal event target', @@ -231,12 +242,9 @@ export class EventClient< }), ) } - if (!this.#enabled) { - this.debugLog( - 'Event bus client is disabled, not emitting event', - eventSuffix, - payload, - ) + + if (this.#failedToConnect) { + this.debugLog('Previously failed to connect, not emitting to bus') return } // wait to connect to the bus From b143983c320b40bdb3e9be7ecad66f86be7971f0 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:56:47 +0000 Subject: [PATCH 4/4] ci: apply automated fixes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f1a71890..04f4a63a 100644 --- a/package.json +++ b/package.json @@ -91,4 +91,4 @@ "@tanstack/solid-devtools": "workspace:*", "@tanstack/devtools-vite": "workspace:*" } -} \ No newline at end of file +}