Skip to content

SqsService binds handlers via .bind() at onModuleInit, silently breaking any post-boot method wrapping (metrics/tracing/@Transactional-style decorators) #108

Description

@bro-ankit

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

npm install
npm test

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

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions