@@ -36,6 +36,33 @@ const { subscribers: subscriberCounts } = dc_binding;
3636const { WeakReference } = require ( 'internal/util' ) ;
3737const { isPromise } = require ( 'internal/util/types' ) ;
3838
39+ // Internal only: tracks a Set of active suppression keys for the current async
40+ // context. Uses a simple stack-based approach to avoid bootstrap issues with
41+ // async_hooks. This is a simplified implementation that works for typical usage.
42+ let suppressionStorage = null ;
43+
44+ function getSuppressionsStorage ( ) {
45+ if ( suppressionStorage === null ) {
46+ try {
47+ const { AsyncLocalStorage } = require ( 'async_hooks' ) ;
48+ suppressionStorage = new AsyncLocalStorage ( ) ;
49+ } catch {
50+ // If AsyncLocalStorage fails to initialize (rare), use a fallback
51+ // that won't provide async context isolation but at least works
52+ suppressionStorage = false ; // Marker for "tried and failed"
53+ }
54+ }
55+ return suppressionStorage || undefined ;
56+ }
57+
58+ function withSuppressionsContext ( set , fn , thisArg , args ) {
59+ const storage = getSuppressionsStorage ( ) ;
60+ if ( storage ) {
61+ return storage . run ( set , ( ) => ReflectApply ( fn , thisArg , args ) ) ;
62+ }
63+ // Fallback: just call the function without context
64+ return ReflectApply ( fn , thisArg , args ) ;
65+ }
3966// Can't delete when weakref count reaches 0 as it could increment again.
4067// Only GC can be used as a valid time to clean up the channels map.
4168class WeakRefMap extends SafeMap {
@@ -93,9 +120,17 @@ class RunStoresScope {
93120
94121 // Enter stores using withScope
95122 if ( activeChannel . _stores ) {
123+ const storage = getSuppressionsStorage ( ) ;
124+ const activeKeys = storage ? storage . getStore ( ) : undefined ;
96125 for ( const entry of activeChannel . _stores . entries ( ) ) {
97126 const store = entry [ 0 ] ;
98- const transform = entry [ 1 ] ;
127+ const { transform, suppressedBy = null } = entry [ 1 ] ;
128+
129+ // Skip this bound store if it opted into suppression and its key
130+ // is active in the current async context.
131+ if ( suppressedBy !== null && activeKeys ?. has ( suppressedBy ) ) {
132+ continue ;
133+ }
99134
100135 let newContext = data ;
101136 if ( transform ) {
@@ -127,16 +162,32 @@ class RunStoresScope {
127162
128163// TODO(qard): should there be a C++ channel interface?
129164class ActiveChannel {
130- subscribe ( subscription ) {
165+ subscribe ( subscription , options = { } ) {
131166 validateFunction ( subscription , 'subscription' ) ;
167+ let suppressedBy = options && options . suppressedBy !== undefined ? options . suppressedBy : null ;
168+ if ( suppressedBy !== null ) {
169+ const t = typeof suppressedBy ;
170+ if ( t === 'string' || t === 'number' || t === 'bigint' || t === 'boolean' ) {
171+ throw new ERR_INVALID_ARG_TYPE ( 'suppressedBy' , [ 'object' , 'symbol' , 'function' ] , suppressedBy ) ;
172+ }
173+ }
174+
175+ const handler = subscription ;
132176 this . _subscribers = ArrayPrototypeSlice ( this . _subscribers ) ;
133- ArrayPrototypePush ( this . _subscribers , subscription ) ;
177+ ArrayPrototypePush ( this . _subscribers , { handler , suppressedBy } ) ;
134178 channels . incRef ( this . name ) ;
135179 if ( this . _index !== undefined ) subscriberCounts [ this . _index ] ++ ;
136180 }
137181
138182 unsubscribe ( subscription ) {
139- const index = ArrayPrototypeIndexOf ( this . _subscribers , subscription ) ;
183+ // Find subscriber entry by handler identity.
184+ let index = - 1 ;
185+ for ( let i = 0 ; i < ( this . _subscribers ?. length || 0 ) ; i ++ ) {
186+ if ( this . _subscribers [ i ] . handler === subscription ) {
187+ index = i ;
188+ break ;
189+ }
190+ }
140191 if ( index === - 1 ) return false ;
141192
142193 const before = ArrayPrototypeSlice ( this . _subscribers , 0 , index ) ;
@@ -151,13 +202,21 @@ class ActiveChannel {
151202 return true ;
152203 }
153204
154- bindStore ( store , transform ) {
205+ bindStore ( store , transform , options = { } ) {
206+ const suppressedBy = options && options . suppressedBy !== undefined ? options . suppressedBy : null ;
207+ if ( suppressedBy !== null ) {
208+ const t = typeof suppressedBy ;
209+ if ( t === 'string' || t === 'number' || t === 'bigint' || t === 'boolean' ) {
210+ throw new ERR_INVALID_ARG_TYPE ( 'suppressedBy' , [ 'object' , 'symbol' , 'function' ] , suppressedBy ) ;
211+ }
212+ }
213+
155214 const replacing = this . _stores . has ( store ) ;
156215 if ( ! replacing ) {
157216 channels . incRef ( this . name ) ;
158217 if ( this . _index !== undefined ) subscriberCounts [ this . _index ] ++ ;
159218 }
160- this . _stores . set ( store , transform ) ;
219+ this . _stores . set ( store , { transform, suppressedBy } ) ;
161220 }
162221
163222 unbindStore ( store ) {
@@ -180,10 +239,15 @@ class ActiveChannel {
180239
181240 publish ( data ) {
182241 const subscribers = this . _subscribers ;
242+ const storage = getSuppressionsStorage ( ) ;
243+ const activeKeys = storage ? storage . getStore ( ) : undefined ;
183244 for ( let i = 0 ; i < ( subscribers ?. length || 0 ) ; i ++ ) {
184245 try {
185- const onMessage = subscribers [ i ] ;
186- onMessage ( data , this . name ) ;
246+ const { handler, suppressedBy = null } = subscribers [ i ] ;
247+ if ( suppressedBy !== null && activeKeys ?. has ( suppressedBy ) ) {
248+ continue ;
249+ }
250+ handler ( data , this . name ) ;
187251 } catch ( err ) {
188252 process . nextTick ( ( ) => {
189253 triggerUncaughtException ( err , false ) ;
@@ -221,18 +285,18 @@ class Channel {
221285 prototype === ActiveChannel . prototype ;
222286 }
223287
224- subscribe ( subscription ) {
288+ subscribe ( subscription , options ) {
225289 markActive ( this ) ;
226- this . subscribe ( subscription ) ;
290+ this . subscribe ( subscription , options ) ;
227291 }
228292
229293 unsubscribe ( ) {
230294 return false ;
231295 }
232296
233- bindStore ( store , transform ) {
297+ bindStore ( store , transform , options ) {
234298 markActive ( this ) ;
235- this . bindStore ( store , transform ) ;
299+ this . bindStore ( store , transform , options ) ;
236300 }
237301
238302 unbindStore ( ) {
@@ -366,12 +430,12 @@ class BoundedChannel {
366430 this . end ?. hasSubscribers ;
367431 }
368432
369- subscribe ( handlers ) {
433+ subscribe ( handlers , options ) {
370434 for ( let i = 0 ; i < boundedEvents . length ; ++ i ) {
371435 const name = boundedEvents [ i ] ;
372436 if ( ! handlers [ name ] ) continue ;
373437
374- this [ name ] ?. subscribe ( handlers [ name ] ) ;
438+ this [ name ] ?. subscribe ( handlers [ name ] , options ) ;
375439 }
376440 }
377441
@@ -458,26 +522,26 @@ class TracingChannel {
458522 this . error ?. hasSubscribers ;
459523 }
460524
461- subscribe ( handlers ) {
525+ subscribe ( handlers , options ) {
462526 // Subscribe to call window (start/end)
463527 if ( handlers . start || handlers . end ) {
464528 this . #callWindow. subscribe ( {
465529 start : handlers . start ,
466530 end : handlers . end ,
467- } ) ;
531+ } , options ) ;
468532 }
469533
470534 // Subscribe to continuation window (asyncStart/asyncEnd)
471535 if ( handlers . asyncStart || handlers . asyncEnd ) {
472536 this . #continuationWindow. subscribe ( {
473537 start : handlers . asyncStart ,
474538 end : handlers . asyncEnd ,
475- } ) ;
539+ } , options ) ;
476540 }
477541
478542 // Subscribe to error channel
479543 if ( handlers . error ) {
480- this . error . subscribe ( handlers . error ) ;
544+ this . error . subscribe ( handlers . error , options ) ;
481545 }
482546 }
483547
@@ -633,10 +697,38 @@ function tracingChannel(nameOrChannels) {
633697
634698dc_binding . linkNativeChannel ( ( name ) => channel ( name ) ) ;
635699
700+ function suppressed ( key , fn , thisArg , ...args ) {
701+ validateFunction ( fn , 'fn' ) ;
702+
703+ if ( key === null || key === undefined ) {
704+ throw new ERR_INVALID_ARG_TYPE ( 'key' , [ 'object' , 'symbol' , 'function' ] , key ) ;
705+ }
706+ const t = typeof key ;
707+ if ( t === 'string' || t === 'number' || t === 'bigint' || t === 'boolean' ) {
708+ throw new ERR_INVALID_ARG_TYPE ( 'key' , [ 'object' , 'symbol' , 'function' ] , key ) ;
709+ }
710+
711+ const storage = getSuppressionsStorage ( ) ;
712+ let currentSet ;
713+ if ( storage ) {
714+ currentSet = storage . getStore ( ) ;
715+ }
716+ const next = currentSet ? new Set ( currentSet ) : new Set ( ) ;
717+ next . add ( key ) ;
718+ const wrapped = function ( ) {
719+ try {
720+ console . error ( 'diagnostics_channel: suppressed() executing callback' ) ;
721+ } catch ( _ ) { }
722+ return ReflectApply ( fn , thisArg , args ) ;
723+ } ;
724+ return withSuppressionsContext ( next , wrapped , thisArg , [ ] ) ;
725+ }
726+
636727module . exports = {
637728 channel,
638729 hasSubscribers,
639730 subscribe,
731+ suppressed,
640732 tracingChannel,
641733 unsubscribe,
642734 boundedChannel,
0 commit comments