Skip to content

chore: Adding protected DLQ props getter - #443

Merged
CarlosGamero merged 9 commits into
mainfrom
chore/protected_dead_letter_queue_props_getter
May 26, 2026
Merged

CarlosGamero merged 9 commits into
mainfrom
chore/protected_dead_letter_queue_props_getter

Conversation

@CarlosGamero

@CarlosGamero CarlosGamero commented May 25, 2026 •

Copy link
Copy Markdown
Collaborator

Context -> https://lokalise.slack.com/archives/C0194K4AC1L/p1779712247913279

In order to use DLQ in susbcription we need to expose queue are

Summary by CodeRabbit

  • Refactor

    • Structured dead‑letter queue handling for more reliable failure routing and FIFO preservation
    • Centralized queue resource management with safer startup/teardown and clearer unavailable-queue semantics
  • Tests

    • Updated fixtures and specs to reflect the new queue resource behavior and more robust URL handling
  • Chores

    • Consistent queue metadata sourcing across publishers and consumers; new type exports for queue/attribute shapes

Review Change Stack

@CarlosGamero CarlosGamero self-assigned this May 25, 2026
@coderabbitai

coderabbitai Bot commented May 25, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@CarlosGamero, we couldn't start this review because you've used your available PR reviews for now.

Your plan includes 1 review of capacity. Refill in 23 minutes and 48 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8530d44b-fee2-43dd-9472-8a118f9e5c53

📥 Commits

Reviewing files that changed from the base of the PR and between d5e9768 and 92f80b1.

📒 Files selected for processing (2)
  • packages/sns/test/consumers/SnsSqsPermissionConsumer.subscriptionDeadLetterQueue.spec.ts
  • packages/sns/test/consumers/SnsSqsPermissionConsumer.ts
📝 Walkthrough

Walkthrough

This PR models queue and DLQ identities as structured resources, makes initSqs optionally return undefined (requiring ARN when present), and updates services, consumers, publishers, fakes, exports, and tests to use the new resources.

Changes

DLQ Resource and InitSqs Contract Update

Layer / File(s) Summary
InitSqs contract: required ARN and optional result
packages/sqs/lib/utils/sqsInitter.ts
InitSqsResult.queueArn is now required as a string; initSqs returns Promise<InitSqsResult | undefined> so non-blocking startup can yield no result when ARN is unavailable; polling checkFn and queue-reuse paths are adjusted.
Service initialization and QueueResource
packages/sqs/lib/sqs/AbstractSqsService.ts, packages/sqs/lib/index.ts
Add exported QueueResource type; replace protected queueName/queueUrl/queueArn with private _queue, protected queue getter, and setQueueResource; init() early-returns on falsy initSqs result and uses setQueueResource; close() clears _queue.
Consumer DLQ refactor and queue-field wiring
packages/sqs/lib/sqs/AbstractSqsConsumer.ts
Introduce DeadLetterQueueResource and private _deadLetterQueue with protected getter; DLQ init early-returns on undefined initSqs result and stores both URL and ARN; failProcessing routes to DLQ via _deadLetterQueue.url; replace this.queueUrl/this.queueName with this.queue.url/this.queue.name across consumer logic, retries, FIFO handling, and visibility commands.
Publisher wiring and test fake updates
packages/sqs/lib/sqs/AbstractSqsPublisher.ts, packages/sqs/lib/fakes/TestSqsPublisher.ts
AbstractSqsPublisher now reads queue identifiers from this.queue (used for metadata, errors, SendMessage QueueUrl); TestSqsPublisher reads queue URLs from options.consumer._queue?.url / options.publisher._queue?.url and updates initialization checks.
Index and utils exports
packages/sqs/lib/index.ts, packages/sqs/lib/utils/sqsUtils.ts
Re-export QueueResource and export QueueAttributesResult type for external consumers.
Tests and fixtures updated
packages/sqs/test/** (consumers/publishers specs and helpers)
Test helpers and specs updated to derive name/url/arn from this.queue and DLQ URL from this.deadLetterQueue?.url; tests capture publisher queue URL before close where needed; non-blocking initSqs expectations updated to expect undefined and consumer.queueProps to throw when uninitialized.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

minor

Suggested reviewers

  • kibertoad
  • kjamrog

Poem

🐰 I hopped along the queues today, so neat,
Dead letters carry names and ARNs complete,
Init may pause and gently say "not yet",
Publishers, consumers learn the new vignette,
A rabbit nods — tidy code is sweet.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'chore: Adding protected DLQ props getter' is partially related to the changeset but does not capture the main scope of changes, which involve a comprehensive refactoring of queue resource encapsulation across the SQS package. Revise the title to reflect the primary change: e.g., 'refactor: Encapsulate queue and DLQ identities in resource objects' or 'refactor: Replace individual queue/DLQ fields with resource objects for cleaner encapsulation'.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/protected_dead_letter_queue_props_getter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/sqs/lib/sqs/AbstractSqsConsumer.ts (1)

346-349: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard DLQ setup on unresolved base init and reset stale DLQ handle.

super.init() can now exit without queue initialization; this method still proceeds, and _deadLetterQueue also isn’t cleared when DLQ init returns no result. That can preserve stale DLQ routing across re-inits.

Suggested fix
   override async init(): Promise<void> {
     await super.init()
+    if (!this.isInitted) {
+      this._deadLetterQueue = undefined
+      return
+    }
     await this.initDeadLetterQueue()
   }

   protected async initDeadLetterQueue() {
+    this._deadLetterQueue = undefined
     if (!this.deadLetterQueueOptions) return
@@
     const result = await initSqs(this.sqsClient, locatorConfig, creationConfig, this.isFifoQueue)
     if (!result) return
@@
     this._deadLetterQueue = { url: result.queueUrl, arn: result.queueArn }
   }

Also applies to: 363-363, 377-377

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sqs/lib/sqs/AbstractSqsConsumer.ts` around lines 346 - 349,
super.init() can return without initializing the base queue so calling
initDeadLetterQueue() unguarded can preserve stale routing; change override
async init() to first ensure the base init actually created the queue (e.g.
check the instance’s queue handle/state produced by super.init(), such as
this._queue / this.queueUrl / similar) before calling initDeadLetterQueue(), and
when calling initDeadLetterQueue() capture its return and only assign it to
this._deadLetterQueue if truthy; if initDeadLetterQueue() returns no result,
explicitly clear this._deadLetterQueue (set to undefined/null) to avoid stale
DLQ handles; apply the same guard-and-clear pattern to the other init overrides
referenced in this class (the other override implementations noted in the
review).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/sqs/lib/sqs/AbstractSqsService.ts`:
- Around line 120-124: The early-return when result is falsy leaves stale values
in this.queueName/this.queueUrl/this.queueArn; update the init flow in
AbstractSqsService to explicitly clear these fields (set to
undefined/null/empty) before returning on !result so a failed/unresolved init
cannot reuse prior queueName/queueUrl/queueArn; locate the block that checks if
(!result) and add clearing of this.queueName, this.queueUrl, and this.queueArn
immediately before the return.

---

Outside diff comments:
In `@packages/sqs/lib/sqs/AbstractSqsConsumer.ts`:
- Around line 346-349: super.init() can return without initializing the base
queue so calling initDeadLetterQueue() unguarded can preserve stale routing;
change override async init() to first ensure the base init actually created the
queue (e.g. check the instance’s queue handle/state produced by super.init(),
such as this._queue / this.queueUrl / similar) before calling
initDeadLetterQueue(), and when calling initDeadLetterQueue() capture its return
and only assign it to this._deadLetterQueue if truthy; if initDeadLetterQueue()
returns no result, explicitly clear this._deadLetterQueue (set to
undefined/null) to avoid stale DLQ handles; apply the same guard-and-clear
pattern to the other init overrides referenced in this class (the other override
implementations noted in the review).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c7fd6b8-beae-410b-b2d9-8d110ab0d547

📥 Commits

Reviewing files that changed from the base of the PR and between 2702e74 and af93454.

📒 Files selected for processing (6)
  • packages/sqs/lib/sqs/AbstractSqsConsumer.ts
  • packages/sqs/lib/sqs/AbstractSqsService.ts
  • packages/sqs/lib/utils/sqsInitter.ts
  • packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts
  • packages/sqs/test/consumers/SqsPermissionConsumer.ts
  • packages/sqs/test/consumers/SqsPermissionConsumerFifo.ts

Comment thread packages/sqs/lib/sqs/AbstractSqsService.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/sqs/lib/sqs/AbstractSqsConsumer.ts (1)

347-360: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset DLQ resource to avoid stale failover routing across re-init/close.

_deadLetterQueue is never cleared when DLQ init is skipped/falsy (Line 359) and also not cleared on close (Line 407). A restarted/reconfigured consumer can keep sending failed messages to an old DLQ URL.

Proposed fix
 protected async initDeadLetterQueue() {
+  // Prevent stale DLQ routing across re-init attempts.
+  this._deadLetterQueue = undefined
   if (!this.deadLetterQueueOptions) return
@@
   const result = await initSqs(this.sqsClient, locatorConfig, creationConfig, this.isFifoQueue)
   if (!result) return
@@
   this._deadLetterQueue = { url: result.queueUrl, arn: result.queueArn }
 }

 public override async close(abort?: boolean): Promise<void> {
+  this._deadLetterQueue = undefined
   await super.close()
   await this.stopExistingConsumers(abort ?? false)
 }

Also applies to: 407-410

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sqs/lib/sqs/AbstractSqsConsumer.ts` around lines 347 - 360, The
_deadLetterQueue field is left stale when initDeadLetterQueue exits early or
when DLQ init is skipped, causing rerouted failures to an old DLQ; update
initDeadLetterQueue to explicitly clear this._deadLetterQueue when
deadLetterQueueOptions is falsy and also when initSqs returns no result (after
the deleteSqs/initSqs calls) so the consumer no longer holds an outdated URL,
and add a clear of this._deadLetterQueue in the class close method (the same
place other shutdown cleanup happens) to ensure re-initialized consumers don’t
reuse a previous DLQ; reference initDeadLetterQueue, this._deadLetterQueue,
initSqs, deleteSqs, and the close method when applying the change.
packages/sqs/lib/sqs/AbstractSqsPublisher.ts (1)

107-114: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard publish flow when init completes without queue resource.

With non-blocking init, this.init() can resolve while isInitted is still false. The method continues and this.queue access can throw; then Line 175/176 can throw again while building error details, masking the original failure.

Proposed fix
     if (!this.isInitted) {
       // avoid multiple concurrent inits
       if (!this.initPromise) {
         this.initPromise = this.init()
       }
       await this.initPromise
       this.initPromise = undefined
+      if (!this.isInitted) {
+        throw new InternalError({
+          message: 'Queue is not ready yet',
+          errorCode: 'SQS_QUEUE_NOT_READY',
+          details: { publisher: this.constructor.name },
+        })
+      }
     }
@@
     } catch (error) {
       const err = error as Error
       this.handleError(err)
+      const queueDetails = this.isInitted
+        ? { queueArn: this.queue.arn, queueName: this.queue.name }
+        : {}
       throw new InternalError({
         message: `Error while publishing to SQS: ${err.message}`,
         errorCode: 'SQS_PUBLISH_ERROR',
         details: {
           publisher: this.constructor.name,
-          queueArn: this.queue.arn,
-          queueName: this.queue.name,
+          ...queueDetails,
           messageType: this.resolveMessageTypeFromMessage(message) ?? 'unknown',
         },
         cause: err,
       })
     }

Also applies to: 170-177

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sqs/lib/sqs/AbstractSqsPublisher.ts` around lines 107 - 114, The
publish flow can race with the non-blocking init: after awaiting
this.initPromise the code assumes this.isInitted and this.queue exist, but
init() may have resolved without creating the queue causing subsequent accesses
to throw and obscure the original failure; update the logic in the publish path
(where isInitted, initPromise, init() and this.queue are used—e.g., the block
around isInitted and the code that builds error details at/near publish()) to
re-check this.isInitted after awaiting this.initPromise and bail out with a
clear error (or return) if still not initted, and when constructing error
details guard against this.queue being undefined (use explicit checks/optional
chaining) so you never dereference this.queue after a failed init.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/sqs/lib/sqs/AbstractSqsConsumer.ts`:
- Around line 347-360: The _deadLetterQueue field is left stale when
initDeadLetterQueue exits early or when DLQ init is skipped, causing rerouted
failures to an old DLQ; update initDeadLetterQueue to explicitly clear
this._deadLetterQueue when deadLetterQueueOptions is falsy and also when initSqs
returns no result (after the deleteSqs/initSqs calls) so the consumer no longer
holds an outdated URL, and add a clear of this._deadLetterQueue in the class
close method (the same place other shutdown cleanup happens) to ensure
re-initialized consumers don’t reuse a previous DLQ; reference
initDeadLetterQueue, this._deadLetterQueue, initSqs, deleteSqs, and the close
method when applying the change.

In `@packages/sqs/lib/sqs/AbstractSqsPublisher.ts`:
- Around line 107-114: The publish flow can race with the non-blocking init:
after awaiting this.initPromise the code assumes this.isInitted and this.queue
exist, but init() may have resolved without creating the queue causing
subsequent accesses to throw and obscure the original failure; update the logic
in the publish path (where isInitted, initPromise, init() and this.queue are
used—e.g., the block around isInitted and the code that builds error details
at/near publish()) to re-check this.isInitted after awaiting this.initPromise
and bail out with a clear error (or return) if still not initted, and when
constructing error details guard against this.queue being undefined (use
explicit checks/optional chaining) so you never dereference this.queue after a
failed init.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7f2da178-11d3-4e4e-aa43-69dff4b5dc27

📥 Commits

Reviewing files that changed from the base of the PR and between af93454 and cb48a59.

📒 Files selected for processing (13)
  • packages/sqs/lib/fakes/TestSqsPublisher.ts
  • packages/sqs/lib/index.ts
  • packages/sqs/lib/sqs/AbstractSqsConsumer.ts
  • packages/sqs/lib/sqs/AbstractSqsPublisher.ts
  • packages/sqs/lib/sqs/AbstractSqsService.ts
  • packages/sqs/test/consumers/SqsEventBridgeConsumer.spec.ts
  • packages/sqs/test/consumers/SqsEventBridgeConsumer.ts
  • packages/sqs/test/consumers/SqsPermissionConsumer.payloadOffloading.spec.ts
  • packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts
  • packages/sqs/test/consumers/SqsPermissionConsumer.ts
  • packages/sqs/test/consumers/SqsPermissionConsumerFifo.ts
  • packages/sqs/test/publishers/SqsPermissionPublisher.ts
  • packages/sqs/test/publishers/SqsPermissionPublisherFifo.ts

@CarlosGamero
CarlosGamero requested a review from kibertoad May 25, 2026 17:10
@CarlosGamero
CarlosGamero force-pushed the chore/protected_dead_letter_queue_props_getter branch from 92f80b1 to d5e9768 Compare May 25, 2026 20:28
@CarlosGamero
CarlosGamero merged commit 61ef2fb into main May 26, 2026
19 checks passed
@CarlosGamero
CarlosGamero deleted the chore/protected_dead_letter_queue_props_getter branch May 26, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants