From e116b39faf75582bfcfa5a75cf3a06ff8d3bcfee Mon Sep 17 00:00:00 2001 From: Stefan van der Wolf Date: Thu, 25 Sep 2025 15:15:29 +0200 Subject: [PATCH] fix(nestjs): Add support for Symbol as event name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The @OnEvent decorator accepts the following types for its argument: ```typescript string | symbol | Array `` If a Symbol is included in an array, the code to get the eventName will throw a TypeError (String(event)) This occurs because JavaScript’s Array.prototype.join internally calls ToString on each array element. Per the specification, ToString(Symbol) is not allowed and results in a TypeError. To avoid this issue, do not rely on String(array) or .join() on arrays containing symbols directly. Instead, explicitly convert each element to a string while handling symbols safely. doc: https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.join doc: https://tc39.es/ecma262/multipage/abstract-operations.html#sec-tostring --- .../sentry-nest-event-instrumentation.ts | 12 +++- .../nestjs/test/integrations/nest.test.ts | 61 ++++++++++++++++++- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts b/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts index f8076087fd5d..92c90c3719de 100644 --- a/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts +++ b/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts @@ -74,10 +74,18 @@ export class SentryNestEventInstrumentation extends InstrumentationBase { return decoratorResult(target, propertyKey, descriptor); } + function eventNameFromEvent(event: unknown): string { + if (typeof event === 'string') { + return event; + } else if (Array.isArray(event)) { + return event.map(eventNameFromEvent).join(','); + } else return String(event); + } + const originalHandler = descriptor.value; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const handlerName = originalHandler.name || propertyKey; - let eventName = typeof event === 'string' ? event : String(event); + let eventName = eventNameFromEvent(event); // Instrument the actual handler descriptor.value = async function (...args: unknown[]) { @@ -93,7 +101,7 @@ export class SentryNestEventInstrumentation extends InstrumentationBase { eventName = eventData .map((data: unknown) => { if (data && typeof data === 'object' && 'event' in data && data.event) { - return data.event; + return eventNameFromEvent(data.event); } return ''; }) diff --git a/packages/nestjs/test/integrations/nest.test.ts b/packages/nestjs/test/integrations/nest.test.ts index 69fb022441dd..2d1d73b4657a 100644 --- a/packages/nestjs/test/integrations/nest.test.ts +++ b/packages/nestjs/test/integrations/nest.test.ts @@ -75,17 +75,72 @@ describe('Nest', () => { await descriptor.value(); - expect(core.startSpan).toHaveBeenCalled(); + expect(core.startSpan).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'event test.event', + }), + expect.any(Function), + ); expect(originalHandler).toHaveBeenCalled(); }); - it('should wrap array event handlers', async () => { + it('should wrap symbol event handlers', async () => { + const decorated = wrappedOnEvent(Symbol('test.event')); + decorated(mockTarget, 'testMethod', descriptor); + + await descriptor.value(); + + expect(core.startSpan).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'event Symbol(test.event)', + }), + expect.any(Function), + ); + expect(originalHandler).toHaveBeenCalled(); + }); + + it('should wrap string array event handlers', async () => { const decorated = wrappedOnEvent(['test.event1', 'test.event2']); decorated(mockTarget, 'testMethod', descriptor); await descriptor.value(); - expect(core.startSpan).toHaveBeenCalled(); + expect(core.startSpan).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'event test.event1,test.event2', + }), + expect.any(Function), + ); + expect(originalHandler).toHaveBeenCalled(); + }); + + it('should wrap symbol array event handlers', async () => { + const decorated = wrappedOnEvent([Symbol('test.event1'), Symbol('test.event2')]); + decorated(mockTarget, 'testMethod', descriptor); + + await descriptor.value(); + + expect(core.startSpan).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'event Symbol(test.event1),Symbol(test.event2)', + }), + expect.any(Function), + ); + expect(originalHandler).toHaveBeenCalled(); + }); + + it('should wrap mixed type array event handlers', async () => { + const decorated = wrappedOnEvent([Symbol('test.event1'), 'test.event2', Symbol('test.event3')]); + decorated(mockTarget, 'testMethod', descriptor); + + await descriptor.value(); + + expect(core.startSpan).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'event Symbol(test.event1),test.event2,Symbol(test.event3)', + }), + expect.any(Function), + ); expect(originalHandler).toHaveBeenCalled(); });