Summary
SqsService.onModuleInit() resolves the handler for @SqsMessageHandler() via:
handleMessage: metadata.discoveredMethod.handler.bind(metadata.discoveredMethod.parentClass.instance)
This captures a snapshot of the method at the moment onModuleInit runs, and hands sqs-consumer a .bind()'d copy of it.
Any Nest provider that applies cross-cutting behavior to decorated methods after that point - by re-assigning instance[methodName] at a later lifecycle phase - has its wrapping silently ignored for SQS-bound handlers. The message still processes fine (so nothing crashes or errors), but any cross-cutting logic layered on top of the method never fires. No warning, no log line — it just silently doesn't happen.
This is a standard, sanctioned Nest extension pattern, not something unusual: @golevelup/nestjs-discovery's own docs describe exactly this ("scan providers for decorator metadata, connect functionality"), citing @nestjs/graphql's @Mutation/@Resolver discovery as the reference example. @ssut/nestjs-sqs itself is built the same way. Real-world users of this pattern: audit logging, tracing/telemetry auto-instrumentation, retry policies, @Transactional()-style decorators, metrics collectors.
Also not a contrived timing edge case: onModuleInit runs strictly before onApplicationBootstrap for the entire application, across every module, regardless of import order — this is guaranteed by Nest, not incidental. Any such executor using OnApplicationBootstrap (a common choice, since it's guaranteed to run after all onModuleInit hooks) hits this on every SQS handler, unconditionally.
Reproduction
Minimal standalone repo: https://github.com/bro-ankit/nestjs-sqs-bug-repro
Two tests, same decorated method, same generic (SQS-agnostic) DiscoveryService-based executor:
- Calling the method directly on the DI-resolved instance → wrapper fires correctly.
- Calling the method through the SQS-bound handler → wrapper is silently skipped.
Expected behavior
Both call paths should observe the same wrapped method, since they're calling the same instance method.
Actual behavior
The SQS-bound path calls a frozen, pre-wrapping snapshot of the method.
This is behaviorally invisible to anyone not doing post-construction method wrapping - instance[methodName](...) and a bound copy of the same function are identical when nothing else touches the method afterward. No change to the public API, dependencies, or lifecycle hook.
Environment
@ssut/nestjs-sqs: 3.0.1
- Node: 20.19.6
Summary
SqsService.onModuleInit()resolves the handler for@SqsMessageHandler()via:This captures a snapshot of the method at the moment
onModuleInitruns, and handssqs-consumera.bind()'d copy of it.Any Nest provider that applies cross-cutting behavior to decorated methods after that point - by re-assigning
instance[methodName]at a later lifecycle phase - has its wrapping silently ignored for SQS-bound handlers. The message still processes fine (so nothing crashes or errors), but any cross-cutting logic layered on top of the method never fires. No warning, no log line — it just silently doesn't happen.This is a standard, sanctioned Nest extension pattern, not something unusual:
@golevelup/nestjs-discovery's own docs describe exactly this ("scan providers for decorator metadata, connect functionality"), citing@nestjs/graphql's@Mutation/@Resolverdiscovery as the reference example.@ssut/nestjs-sqsitself is built the same way. Real-world users of this pattern: audit logging, tracing/telemetry auto-instrumentation, retry policies,@Transactional()-style decorators, metrics collectors.Also not a contrived timing edge case:
onModuleInitruns strictly beforeonApplicationBootstrapfor the entire application, across every module, regardless of import order — this is guaranteed by Nest, not incidental. Any such executor usingOnApplicationBootstrap(a common choice, since it's guaranteed to run after allonModuleInithooks) hits this on every SQS handler, unconditionally.Reproduction
Minimal standalone repo: https://github.com/bro-ankit/nestjs-sqs-bug-repro
Two tests, same decorated method, same generic (SQS-agnostic)
DiscoveryService-based executor:Expected behavior
Both call paths should observe the same wrapped method, since they're calling the same instance method.
Actual behavior
The SQS-bound path calls a frozen, pre-wrapping snapshot of the method.
This is behaviorally invisible to anyone not doing post-construction method wrapping -
instance[methodName](...)and a bound copy of the same function are identical when nothing else touches the method afterward. No change to the public API, dependencies, or lifecycle hook.Environment
@ssut/nestjs-sqs: 3.0.1