Skip to content

Implement polling for topics#388

Merged
kibertoad merged 11 commits into
mainfrom
feat/polling
Jan 7, 2026
Merged

Implement polling for topics#388
kibertoad merged 11 commits into
mainfrom
feat/polling

Conversation

@kibertoad

@kibertoad kibertoad commented Jan 7, 2026

Copy link
Copy Markdown
Owner

Original problem statement:

  • Cross dependency between services

    • CTENG requires a presence of AP topic in order to create a queue for it (queue: cteng-export_process-autopilot, topic: autopilot-export_process). AP requires the same from CTENG side. This creates a cross dependency and doesn't easily allow to deploy services independently, especially on PREnvs, when services are created from scratch. One workaround for that is to disable AP-related consumers on CTENG side, deploy CTENG, then AP, then enable consumers on CTENG side and redeploy. As you see, this introduces some manual effort.
  • Lack of granularity when enabling consumers on AP side

    • If AWS config is supplied, AP enables all consumers, meaning it requires all of the resources (mostly topics) to be present on AWS. We often face a situation when we only want to have a connection between a few of the services only (example AP and CTENG, but not Expert). Currently, the only workaround is to find all of the unnecessary consumers and disable them manually (via amending the code).

    • In CTENG, we have an env flag that allows to enable/disable specific consumers (for example AP-related ones), which gives us more flexibility. I know it's been a case of AP previously, but some time ago it got changed and now works as described in 2.a. Wondering if we should also provide more granular control over the consumers.

Summary by CodeRabbit

  • New Features

    • Optional startup resource polling for eventual consistency: configurable interval, timeout (including indefinite), non-blocking mode, background callback when resources become available, and a resourcesReady flag on init results.
  • Documentation

    • Added README section with examples, environment guidance, configuration options, and error-handling notes.
  • Bug Fixes

    • More robust SNS not-found detection during initialization.
  • Tests

    • Extensive tests covering polling, non-blocking behavior, timeouts, and indefinite polling.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 7, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds startup resource polling support: a core polling utility, new types/constants, wiring into SNS/SQS init flows (blocking and non-blocking modes with callbacks), type and API surface extensions, tests, and README documentation (duplicated section present).

Changes

Cohort / File(s) Change summary
Core: polling utility & API
packages/core/lib/utils/startupResourcePollingUtils.ts, packages/core/lib/utils/startupResourcePollingUtils.spec.ts, packages/core/lib/index.ts
New startup polling implementation: waitForResource, isStartupResourcePollingEnabled, StartupResourcePollingTimeoutError, related types (WaitForResourceOptions, StartupResourcePollingCheckResult), tests, and re-exports added to core index.
Core: types
packages/core/lib/types/queueOptionsTypes.ts, packages/core/lib/types/MessageQueueTypes.ts
Add NO_TIMEOUT constant and StartupResourcePollingConfig type; extend locator types to include polling config; add optional errorReporter to ExtraParams.
SQS init & service
packages/sqs/lib/utils/sqsInitter.ts, packages/sqs/lib/index.ts, packages/sqs/lib/sqs/AbstractSqsService.ts, packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts
initSqs gains extraParams (logger, errorReporter, onQueueReady), new InitSqs* types and result shape (may return undefined queueArn when non-blocking), passes logger from AbstractSqsService, tests added for polling/non-blocking/timeout/NO_TIMEOUT.
SNS init & service
packages/sns/lib/utils/snsInitter.ts, packages/sns/lib/index.ts, packages/sns/lib/sns/AbstractSnsService.ts, packages/sns/lib/utils/snsUtils.ts, packages/sns/test/consumers/SnsSqsPermissionConsumer.startupResourcePolling.spec.ts
initSns/initSnsSqs accept extraParams (onTopicReady/onResourcesReady), use waitForResource when polling enabled (supports non-blocking callbacks and resourcesReady flag), adjust SNS not-found error checks, and add tests.
Docs
README.md
Added "Startup Resource Polling (Eventual Consistency Mode)" documentation with examples, config options, error handling; the new section appears duplicated in two places.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Consumer as Consumer / Service init
  participant InitUtil as initSns / initSqs
  participant PollUtil as waitForResource (core)
  participant AWS as AWS SNS/SQS
  participant Logger as Logger / ErrorReporter

  Consumer->>InitUtil: start init(locatorConfig, extraParams)
  alt startupResourcePolling enabled
    InitUtil->>PollUtil: waitForResource(checkFn, config, resourceName, callbacks)
    loop polling
      PollUtil->>AWS: getAttributes / head call
      AWS-->>PollUtil: not_found / found / error
      alt found
        PollUtil-->>InitUtil: resource result
        alt non-blocking
          InitUtil-->>Consumer: return resourcesReady:false
          PollUtil->>InitUtil: invoke onResourceAvailable -> trigger on*Ready callback
        else blocking
          InitUtil-->>Consumer: return resourcesReady:true
        end
      else not_found
        PollUtil-->>Logger: log progress/status
      end
    end
    alt timeout
      PollUtil-->>Logger: throw or report StartupResourcePollingTimeoutError
      PollUtil--xInitUtil: throw or continue based on throwOnTimeout
    end
  else polling disabled
    InitUtil->>AWS: single getAttributes
    AWS-->>InitUtil: found / not found (throw)
    InitUtil-->>Consumer: return resourcesReady:true
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • kjamrog
  • CarlosGamero

Poem

"I hopped through code at break of dawn,
Polling for queues on dewy lawn.
If topics hide, I'll wait and prance,
Callbacks jingling — I start to dance.
ARNs appear — a joyful hop! 🐰"

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Implement polling for topics' is partially related to the changeset but does not reflect the main scope. The PR implements comprehensive startup resource polling across SNS, SQS, and core utilities for eventual consistency, not just topic polling. Consider a more accurate title like 'Implement startup resource polling for eventual consistency' to better capture the full scope of the changeset across topics, queues, and initialization utilities.
Docstring Coverage ⚠️ Warning Docstring coverage is 38.89% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcada62 and f88b9e5.

📒 Files selected for processing (4)
  • README.md
  • packages/sns/lib/utils/snsInitter.ts
  • packages/sns/test/consumers/SnsSqsPermissionConsumer.startupResourcePolling.spec.ts
  • packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: general (22.x, @message-queue-toolkit/outbox-core) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/core) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/amqp) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/core) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/s3-payload-store) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/gcs-payload-store) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/gcs-payload-store) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/redis-message-deduplication-store) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: kafka (24.x) / build
  • GitHub Check: kafka (22.x) / build
🔇 Additional comments (8)
README.md (1)

247-445: Excellent documentation for a complex feature!

The Startup Resource Polling documentation is comprehensive and well-structured. It effectively covers:

  • Clear use case explanation for cross-service dependencies
  • Complete configuration options with types and defaults
  • Practical environment-specific examples (production, development, non-blocking)
  • Detailed non-blocking mode behavior with callback patterns
  • Proper error handling guidance

The code examples are syntactically correct and demonstrate real-world usage patterns. The progression from basic configuration to advanced non-blocking scenarios helps users understand the feature incrementally.

packages/sns/lib/utils/snsInitter.ts (4)

25-39: Well-designed type definitions for non-blocking callbacks.

The callback types are clearly documented and type-safe. The comments explicitly state that callbacks are only invoked in non-blocking mode when resources were not immediately available, which helps prevent misuse.


122-246: Robust polling coordination for dual-resource availability.

The coordination logic correctly handles the complexity of waiting for two resources (topic and queue) in both blocking and non-blocking modes:

  • notifyIfBothReady ensures the callback is invoked exactly once when both resources are available
  • Availability flags (topicAvailable, queueAvailable) properly track state across async operations
  • Background polling for the queue (lines 167-198) correctly handles immediate availability via .then() and errors via .catch()
  • Early return in non-blocking mode (lines 200-206) allows the service to start while polling continues

The fix for duplicate callback invocations (from past review) is implemented correctly.


363-416: Clean implementation of topic-only polling.

The initSnsWithLocator function follows the same polling patterns as the combined SNS+SQS initialization but appropriately simplified for a single resource. The non-blocking mode correctly returns early and invokes the onTopicReady callback when the resource becomes available.


418-443: LGTM! Clean delegation pattern.

The function appropriately delegates to initSnsWithLocator for the polling path and handles topic creation for the non-locator path. The return type correctly includes resourcesReady to indicate initialization state.

packages/sns/test/consumers/SnsSqsPermissionConsumer.startupResourcePolling.spec.ts (3)

1-93: Solid test infrastructure setup.

The test consumer class provides a clean abstraction for testing startup polling behavior. The resource cleanup hooks ensure proper isolation between tests, and the subscriptionProps getter appropriately exposes internal state for verification without breaking encapsulation.


95-222: Comprehensive coverage of blocking mode polling scenarios.

The test cases thoroughly validate blocking mode behavior:

  • ✅ Polling for topic availability
  • ✅ Polling for queue availability
  • ✅ Timeout error when resources don't appear
  • ✅ Indefinite polling with NO_TIMEOUT (added per previous review)

The tests appropriately use setTimeout to simulate delayed resource creation and verify correct initialization state.


224-405: Excellent test coverage for non-blocking and disabled modes.

The test suite comprehensively validates:

Non-blocking mode:

  • Immediate return when resources are not ready
  • Correct resourcesReady flag values
  • Callback invocation using vi.waitFor (more reliable than fixed delays)
  • Direct testing of initSnsSqs with onResourcesReady callback

Disabled polling:

  • Fail-fast behavior when resources don't exist
  • Both topic and queue missing scenarios

The use of vi.waitFor for callback verification (lines 349-354) is a best practice for async callback testing, avoiding brittle fixed-delay assertions.


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.

Comment thread packages/core/lib/types/queueOptionsTypes.ts Outdated
@kibertoad kibertoad added the minor label Jan 7, 2026

@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: 6

🤖 Fix all issues with AI agents
In @packages/core/lib/types/queueOptionsTypes.ts:
- Around line 170-191: The JSDoc for ResourceAvailabilityConfig.enabled claims a
default of false but the type requires callers to provide it; make the type and
runtime consistent by changing enabled to optional (enabled?: boolean) and
ensure any code that constructs/consumes ResourceAvailabilityConfig applies a
false default when undefined, or alternatively remove the "Default: false" text
from the JSDoc; update ResourceAvailabilityConfig and the consuming
initialization logic (places that read enabled) so the type and documentation
match.

In @packages/sns/lib/utils/snsInitter.ts:
- Around line 145-147: The error thrown when topicCheckResult?.error ===
'not_found' uses the wrong variable locatorConfig.topicArn which can be
undefined when the topic was resolved by name; update the throw to reference the
resolved subscriptionTopicArn (the variable set around lines where
subscriptionTopicArn is computed) so the error message always contains the
actual ARN and gives correct context.
- Around line 282-284: The error throw in the checkResult.error === 'not_found'
branch uses the wrong variable: replace locatorConfig.topicArn with the resolved
topicArn variable (the one computed on lines ~260–261) so the thrown message
reads the actual resolved ARN; mirror the same fix applied in initSnsSqs and
update the template string in that throw (refer to checkResult,
locatorConfig.topicArn, and topicArn).

In @packages/sns/lib/utils/snsUtils.ts:
- Around line 41-43: Remove the incorrect comment about "NotFoundException" and
update the inline comment to state that SNS returns 'NotFound' for non-existent
topics; then simplify the conditional to only check err.name === 'NotFound'
(replace the current check that also tests 'NotFoundException') while keeping
the surrounding error-handling logic intact (look for the err.name check in
snsUtils.ts).

In @packages/sqs/lib/utils/sqsInitter.ts:
- Around line 106-108: The throw uses locatorConfig.queueUrl which can be
undefined if the queue was resolved by name; replace that with the resolved
queueUrl variable (the one assigned around line 81) so the error message always
shows the actual URL. Update the throw in the block checking checkResult.error
=== 'not_found' to reference queueUrl (not locatorConfig.queueUrl) and keep the
same message format to stay consistent with the waiting-enabled path.

In @README.md:
- Around line 247-322: The README contains a duplicate "Resource Availability
Polling (Eventual Consistency Mode)" section; locate the repeated block that
documents resourceAvailabilityConfig, the example consumer init usage (e.g.,
consumer.init()), and the ResourceAvailabilityTimeoutError example, then remove
the redundant copy so only one comprehensive section describing
resourceAvailabilityConfig, pollingIntervalMs, timeoutMs, environment examples,
and the ResourceAvailabilityTimeoutError handling remains.
🧹 Nitpick comments (2)
packages/sns/test/consumers/SnsSqsPermissionConsumer.resourceAvailability.spec.ts (2)

19-71: Consider using imported types for test consumer options.

The test consumer duplicates the structure of locatorConfig, creationConfig, and resourceAvailabilityConfig rather than importing the actual types. This could lead to drift if the real types change.

♻️ Suggested improvement
+import type { ResourceAvailabilityConfig } from '@message-queue-toolkit/core'
+import type { SNSSQSCreationConfig, SNSSQSQueueLocatorType } from '../../lib/sns/AbstractSnsSqsConsumer.ts'

 class TestResourceAvailabilityConsumer extends AbstractSnsSqsConsumer<
   PERMISSIONS_ADD_MESSAGE_TYPE,
   undefined,
   undefined
 > {
   constructor(
     dependencies: SNSSQSConsumerDependencies,
     options: {
-      locatorConfig?: {
-        topicArn?: string
-        topicName?: string
-        queueUrl?: string
-        queueName?: string
-        subscriptionArn?: string
-      }
-      creationConfig?: {
-        queue?: { QueueName: string }
-        topic?: { Name: string }
-      }
-      resourceAvailabilityConfig?: {
-        enabled: boolean
-        timeoutMs?: number
-        pollingIntervalMs?: number
-      }
+      locatorConfig?: SNSSQSQueueLocatorType
+      creationConfig?: SNSSQSCreationConfig
+      resourceAvailabilityConfig?: ResourceAvailabilityConfig
     },
   ) {

107-140: Test timing may be fragile in slow environments.

The test waits 300ms before creating the topic, relying on specific timing. In slow CI environments or under load, this could cause flakiness if the 300ms delay is insufficient for the polling loop to start.

Consider either:

  • Increasing the delay to be more conservative (e.g., 500ms or 1000ms)
  • Adding logging or assertions to verify polling occurred multiple times before the resource appeared
  • Using a more deterministic synchronization mechanism if the test framework supports it

However, the current approach is reasonable for integration tests with external services.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 691cd73 and 1ec0378.

📒 Files selected for processing (15)
  • README.md
  • packages/core/lib/index.ts
  • packages/core/lib/queues/AbstractQueueService.ts
  • packages/core/lib/types/queueOptionsTypes.ts
  • packages/core/lib/utils/resourceAvailabilityUtils.spec.ts
  • packages/core/lib/utils/resourceAvailabilityUtils.ts
  • packages/sns/lib/index.ts
  • packages/sns/lib/sns/AbstractSnsSqsConsumer.ts
  • packages/sns/lib/utils/snsInitter.ts
  • packages/sns/lib/utils/snsUtils.ts
  • packages/sns/test/consumers/SnsSqsPermissionConsumer.resourceAvailability.spec.ts
  • packages/sqs/lib/index.ts
  • packages/sqs/lib/sqs/AbstractSqsService.ts
  • packages/sqs/lib/utils/sqsInitter.ts
  • packages/sqs/test/consumers/SqsPermissionConsumer.resourceAvailability.spec.ts
🧰 Additional context used
🧬 Code graph analysis (4)
packages/core/lib/queues/AbstractQueueService.ts (1)
packages/core/lib/types/queueOptionsTypes.ts (1)
  • ResourceAvailabilityConfig (170-191)
packages/sns/test/consumers/SnsSqsPermissionConsumer.resourceAvailability.spec.ts (3)
packages/sns/lib/sns/AbstractSnsSqsConsumer.ts (1)
  • SNSSQSConsumerDependencies (16-19)
packages/sns/lib/utils/snsUtils.ts (2)
  • deleteTopic (144-158)
  • assertTopic (79-142)
packages/core/lib/utils/resourceAvailabilityUtils.ts (1)
  • ResourceAvailabilityTimeoutError (40-53)
packages/core/lib/utils/resourceAvailabilityUtils.ts (1)
packages/core/lib/types/queueOptionsTypes.ts (1)
  • ResourceAvailabilityConfig (170-191)
packages/sqs/lib/utils/sqsInitter.ts (3)
packages/core/lib/types/queueOptionsTypes.ts (1)
  • ResourceAvailabilityConfig (170-191)
packages/sqs/lib/utils/sqsUtils.ts (2)
  • resolveQueueUrlFromLocatorConfig (90-109)
  • getQueueAttributes (62-88)
packages/core/lib/utils/resourceAvailabilityUtils.ts (2)
  • isResourceAvailabilityWaitingEnabled (160-164)
  • waitForResource (101-155)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: general (20.x, @message-queue-toolkit/metrics) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/metrics) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/outbox-core) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/gcs-payload-store) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/redis-message-deduplication-store) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/core) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/gcs-payload-store) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/s3-payload-store) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: kafka (24.x) / build
  • GitHub Check: kafka (22.x) / build
🔇 Additional comments (17)
packages/sqs/lib/index.ts (1)

38-43: LGTM!

The new exports for initSqs and InitSqsExtraParams properly expose the resource availability polling functionality through the public API.

packages/sns/lib/sns/AbstractSnsSqsConsumer.ts (1)

105-108: LGTM!

The addition of resourceAvailabilityConfig to the initSnsSqs options enables eventual consistency polling during SNS+SQS initialization, which aligns with the PR's goal of handling cross-service deployment dependencies.

packages/sns/lib/utils/snsUtils.ts (1)

68-70: Same verification needed for subscription error handling.

This location has the same dual error name check as the topic attributes function above. The verification requested for Line 41-43 applies here as well.

packages/sqs/lib/sqs/AbstractSqsService.ts (1)

111-114: LGTM!

The extraParams object properly passes resourceAvailabilityConfig and logger to initSqs, enabling resource availability polling with appropriate logging during queue initialization.

packages/sns/lib/index.ts (1)

28-33: LGTM! Clean type export extension.

The addition of InitSnsExtraParams and InitSnsSqsExtraParams to the public API is well-structured, using inline type modifiers appropriately.

packages/core/lib/types/queueOptionsTypes.ts (1)

121-127: LGTM! Well-documented optional configuration.

The addition of resourceAvailabilityConfig to CommonQueueOptions is backward-compatible and clearly documents the eventual consistency use case for handling cross-service deployment dependencies.

packages/core/lib/queues/AbstractQueueService.ts (1)

49-49: LGTM! Consistent configuration plumbing.

The resourceAvailabilityConfig field is properly imported, declared as protected readonly, and initialized from options. This follows the same pattern as other configuration fields in AbstractQueueService.

Also applies to: 129-129, 165-165

packages/core/lib/index.ts (1)

108-114: LGTM! Appropriate public API extension.

The resource availability utilities are cleanly exported with correct type annotations, making the polling functionality available to package consumers.

packages/sns/test/consumers/SnsSqsPermissionConsumer.resourceAvailability.spec.ts (2)

90-98: Defensive cleanup pattern is solid.

Both beforeEach and afterEach delete the queue and topic, ensuring test isolation even if a test fails mid-execution. This redundancy is intentional and prevents state leakage between tests.


200-242: LGTM! Backward compatibility tests are solid.

These tests verify that when resourceAvailabilityConfig.enabled = false, the consumer fails immediately rather than polling. This ensures backward compatibility with existing behavior.

The error message assertions using /does not exist/ are appropriate for integration tests where exact error text may vary.

packages/core/lib/utils/resourceAvailabilityUtils.spec.ts (1)

1-180: LGTM! Comprehensive test coverage for the resource availability utilities.

The test suite thoroughly covers:

  • isResourceAvailabilityWaitingEnabled with all config states
  • waitForResource success, polling, timeout, error propagation, and logging paths
  • Indefinite polling scenarios (both timeoutMs undefined and timeoutMs: 0)
  • ResourceAvailabilityTimeoutError message composition
packages/sqs/test/consumers/SqsPermissionConsumer.resourceAvailability.spec.ts (1)

1-181: LGTM! Well-structured integration tests for SQS resource availability.

The tests effectively validate:

  • Polling until queue becomes available
  • Timeout behavior with ResourceAvailabilityTimeoutError
  • Indefinite polling when timeoutMs is not set
  • Backward compatibility (immediate failure when config is disabled or not provided)

The TestResourceAvailabilityConsumer subclass cleanly exposes queueProps for assertions.

packages/sqs/lib/utils/sqsInitter.ts (1)

17-20: Clean integration of resource availability polling.

The InitSqsExtraParams type and conditional polling logic are well-implemented. The checkFn correctly handles the not_found error from getQueueAttributes and extracts the QueueArn from successful results.

Also applies to: 72-101

packages/sns/lib/utils/snsInitter.ts (1)

21-27: Clean integration of resource availability polling for SNS.

The new types and conditional polling logic are well-implemented. Sequential waiting for topic and queue (rather than parallel) is reasonable as it provides clearer error messages and resource-specific logging.

Also applies to: 103-130, 266-278

packages/core/lib/utils/resourceAvailabilityUtils.ts (3)

117-154: Well-implemented polling loop with proper termination conditions.

The while(true) loop safely terminates via:

  1. Return when resource is available (line 126)
  2. Throw ResourceAvailabilityTimeoutError when timeout exceeded (line 119 → line 74)
  3. Rethrow unexpected errors from checkFn (line 149)

The logging strategy (first attempt + every ~minute at 5s intervals) is pragmatic for long-running waits.


40-53: Well-designed error class with actionable message.

The ResourceAvailabilityTimeoutError includes both resourceName and timeoutMs as properties, making it easy for consumers to handle programmatically. The error message is clear and suggests potential causes.


6-13: Clean type definitions with proper type guard.

The ResourceAvailabilityCheckResult<T> discriminated union and WaitForResourceOptions<T> generic type are well-designed. The isResourceAvailabilityWaitingEnabled type guard properly narrows the config type for downstream usage.

Also applies to: 15-38, 160-163

Comment thread packages/core/lib/types/queueOptionsTypes.ts Outdated
Comment thread packages/sns/lib/utils/snsInitter.ts
Comment thread packages/sns/lib/utils/snsInitter.ts Outdated
Comment thread packages/sns/lib/utils/snsUtils.ts
Comment thread packages/sqs/lib/utils/sqsInitter.ts
Comment thread README.md 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.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @packages/sns/lib/utils/snsInitter.ts:
- Around line 153-154: The thrown error in snsInitter.ts references
locatorConfig.topicArn which may be undefined when the topic was resolved by
name; change the error to include the resolved subscriptionTopicArn variable
instead (use subscriptionTopicArn in the message), so the thrown Error uses the
actual ARN that was checked (reference symbols: topicCheckResult,
locatorConfig.topicArn, subscriptionTopicArn).
- Around line 290-291: The thrown Error uses locatorConfig.topicArn which may be
undefined when the topic was resolved by name; change the throw in the
checkResult.error === 'not_found' branch to use the resolved topicArn variable
(the one set earlier when locating the topic) instead of locatorConfig.topicArn
so the error message always contains the actual ARN value.

In @packages/sqs/lib/utils/sqsInitter.ts:
- Around line 110-111: The throw in the not-found branch of initSqs/whatever in
sqsInitter.ts uses locatorConfig.queueUrl which can be undefined when the queue
was resolved by queueName; replace that reference with the resolved queueUrl
variable (the one assigned around line 85) so the Error message reads the actual
URL (e.g., throw new Error(`Queue with queueUrl ${queueUrl} does not exist.`) in
the block where checkResult.error === 'not_found').
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ec0378 and 2b833a8.

📒 Files selected for processing (6)
  • packages/core/lib/utils/resourceAvailabilityUtils.ts
  • packages/sns/lib/index.ts
  • packages/sns/lib/utils/snsInitter.ts
  • packages/sns/test/consumers/SnsSqsPermissionConsumer.resourceAvailability.spec.ts
  • packages/sqs/lib/index.ts
  • packages/sqs/lib/utils/sqsInitter.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/sqs/lib/index.ts
  • packages/core/lib/utils/resourceAvailabilityUtils.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/sqs/lib/utils/sqsInitter.ts (3)
packages/core/lib/types/queueOptionsTypes.ts (1)
  • ResourceAvailabilityConfig (170-191)
packages/sqs/lib/utils/sqsUtils.ts (2)
  • resolveQueueUrlFromLocatorConfig (90-109)
  • getQueueAttributes (62-88)
packages/core/lib/utils/resourceAvailabilityUtils.ts (2)
  • isResourceAvailabilityWaitingEnabled (161-165)
  • waitForResource (102-156)
🔇 Additional comments (13)
packages/sqs/lib/utils/sqsInitter.ts (3)

21-24: LGTM!

The InitSqsExtraParams type definition is clean and follows the same pattern as the SNS initter types.


92-105: LGTM!

The waiting-enabled path correctly uses waitForResource with a checkFn that polls for queue availability via getQueueAttributes. The implementation is consistent with the SNS initter pattern.


81-81: LGTM!

The optional extraParams parameter extends the public API while maintaining backward compatibility.

packages/sns/lib/index.ts (1)

28-33: LGTM!

The new type exports (InitSnsExtraParams and InitSnsSqsExtraParams) appropriately extend the public API surface to support resourceAvailabilityConfig configuration.

packages/sns/test/consumers/SnsSqsPermissionConsumer.resourceAvailability.spec.ts (5)

19-71: LGTM!

The TestResourceAvailabilityConsumer is well-designed for testing resource availability behavior. The subscriptionProps getter provides convenient access to initialization results for assertions.


83-104: LGTM!

The test setup and teardown logic properly manages resource lifecycle, ensuring clean state for each test and proper disposal of the DI container.


107-140: LGTM!

This test effectively validates that the consumer waits for topic availability when polling is enabled. The pattern of starting initialization in the background and creating the resource after a delay properly exercises the polling logic.


174-197: LGTM!

The timeout test correctly validates that ResourceAvailabilityTimeoutError is thrown when resources don't become available within the configured timeout period.


200-242: LGTM!

The disabled configuration tests properly validate fail-fast behavior, ensuring that when polling is disabled, missing resources cause immediate failures.

packages/sns/lib/utils/snsInitter.ts (4)

29-35: LGTM!

The InitSnsSqsExtraParams and InitSnsExtraParams type definitions are consistent with the pattern established in the SQS initter and properly extend the base ExtraParams type.


108-124: LGTM!

The topic waiting logic correctly uses waitForResource to poll for SNS topic availability when resource availability config is enabled.


126-138: LGTM!

The queue waiting logic properly polls for SQS queue availability after the topic check completes, maintaining consistency with the resource availability pattern.


274-286: LGTM!

The waiting-enabled path for initSns correctly polls for topic availability using the same pattern as the other init functions.

Comment thread packages/sns/lib/utils/snsInitter.ts Outdated
Comment thread packages/sns/lib/utils/snsInitter.ts Outdated
Comment thread packages/sqs/lib/utils/sqsInitter.ts Outdated
Comment thread README.md Outdated
Comment thread packages/core/lib/utils/resourceAvailabilityUtils.ts Outdated
Comment thread README.md Outdated
Comment thread packages/core/lib/utils/resourceAvailabilityUtils.ts Outdated
Comment on lines +112 to +124
// Wait for topic to become available
await waitForResource({
config: resourceAvailabilityConfig,
resourceName: `SNS topic ${subscriptionTopicArn}`,
logger: extraParams?.logger,
checkFn: async () => {
const result = await getTopicAttributes(snsClient, subscriptionTopicArn)
if (result.error === 'not_found') {
return { isAvailable: false }
}
return { isAvailable: true, result: result.result }
},
})

@CarlosGamero CarlosGamero Jan 7, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If the resource is not available within the given timeout, we throw a ResourceAvailabilityTimeoutError. What is the expected behaviour of the app in this case? Should it fail and restart until the resource becomes available?

Wondering if we could let the app continue running and report the error via the ErrorReporter for visibility, but not entering in a crash loop

@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

🤖 Fix all issues with AI agents
In
@packages/sns/test/consumers/SnsSqsPermissionConsumer.startupResourcePolling.spec.ts:
- Around line 94-186: Add an integration test that covers NO_TIMEOUT indefinite
polling: in SnsSqsPermissionConsumer.startupResourcePolling.spec.ts add a new
it() similar to the provided suggested test that creates the queue first,
constructs TestStartupResourcePollingConsumer with
locatorConfig.startupResourcePolling.timeoutMs set to NO_TIMEOUT (and
pollingIntervalMs small), starts init() in background, waits briefly, then
creates the topic and awaits initPromise; assert subscriptionProps.topicArn and
queueName as in other tests; also import NO_TIMEOUT from
'@message-queue-toolkit/core' alongside MessageHandlerConfigBuilder and
StartupResourcePollingTimeoutError.
🧹 Nitpick comments (3)
packages/sns/test/consumers/SnsSqsPermissionConsumer.startupResourcePolling.spec.ts (1)

103-104: Consider extracting the hard-coded subscription ARN.

The subscription ARN 'arn:aws:sns:eu-west-1:000000000000:dummy:bdf640a2-bedf-475a-98b8-758b88c87395' is repeated throughout the test file. Extracting it as a constant would improve maintainability.

♻️ Suggested refactor

Add near the top of the test suite (after line 64):

 const queueUrl = `http://sqs.eu-west-1.localstack:4566/000000000000/${queueName}`
+const testSubscriptionArn = 'arn:aws:sns:eu-west-1:000000000000:dummy:bdf640a2-bedf-475a-98b8-758b88c87395'

Then replace all occurrences:

       subscriptionArn:
-        'arn:aws:sns:eu-west-1:000000000000:dummy:bdf640a2-bedf-475a-98b8-758b88c87395',
+        testSubscriptionArn,

Also applies to: 138-139, 170-171, 199-200, 220-221

packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts (1)

119-143: Consider adding a timeout guard for the NO_TIMEOUT test.

While the test verifies indefinite polling works, if the queue creation fails or the test regresses, this test could hang indefinitely in CI environments.

♻️ Optional improvement

Consider wrapping the test with Vitest's timeout option to prevent CI hangs:

-    it('polls indefinitely when NO_TIMEOUT is used', async () => {
+    it('polls indefinitely when NO_TIMEOUT is used', { timeout: 10000 }, async () => {
packages/sns/lib/utils/snsInitter.ts (1)

100-148: Consider parallelizing resource polling for faster startup.

Currently, the topic and queue are polled sequentially. Since they are independent resources, polling them in parallel could reduce startup time when both resources become available at different times.

♻️ Proposed parallel polling
   if (isStartupResourcePollingEnabled(startupResourcePolling)) {
-    // Wait for topic to become available
-    await waitForResource({
+    // Wait for both resources in parallel
+    await Promise.all([
+      waitForResource({
         config: startupResourcePolling,
         resourceName: `SNS topic ${subscriptionTopicArn}`,
         logger: extraParams?.logger,
         checkFn: async () => {
           const result = await getTopicAttributes(snsClient, subscriptionTopicArn)
           if (result.error === 'not_found') {
             return { isAvailable: false }
           }
           return { isAvailable: true, result: result.result }
         },
-      })
-
-    // Wait for queue to become available
-    await waitForResource({
+      }),
+      waitForResource({
         config: startupResourcePolling,
         resourceName: `SQS queue ${queueUrl}`,
         logger: extraParams?.logger,
         checkFn: async () => {
           const result = await getQueueAttributes(sqsClient, queueUrl)
           if (result.error === 'not_found') {
             return { isAvailable: false }
           }
           return { isAvailable: true, result: result.result }
         },
-      })
+      }),
+    ])
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbc10e1 and 8970771.

📒 Files selected for processing (11)
  • README.md
  • packages/core/lib/index.ts
  • packages/core/lib/types/queueOptionsTypes.ts
  • packages/core/lib/utils/startupResourcePollingUtils.spec.ts
  • packages/core/lib/utils/startupResourcePollingUtils.ts
  • packages/sns/lib/sns/AbstractSnsService.ts
  • packages/sns/lib/utils/snsInitter.ts
  • packages/sns/test/consumers/SnsSqsPermissionConsumer.startupResourcePolling.spec.ts
  • packages/sqs/lib/sqs/AbstractSqsService.ts
  • packages/sqs/lib/utils/sqsInitter.ts
  • packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/lib/index.ts
🧰 Additional context used
🧬 Code graph analysis (3)
packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts (5)
packages/sqs/lib/index.ts (1)
  • AbstractSqsConsumer (5-5)
packages/core/lib/index.ts (2)
  • MessageHandlerConfigBuilder (63-63)
  • StartupResourcePollingTimeoutError (111-111)
examples/sns-sqs/lib/common/Dependencies.ts (1)
  • sqsClient (17-17)
packages/core/lib/utils/startupResourcePollingUtils.ts (1)
  • StartupResourcePollingTimeoutError (41-54)
packages/core/lib/types/queueOptionsTypes.ts (1)
  • NO_TIMEOUT (148-148)
packages/sqs/lib/sqs/AbstractSqsService.ts (1)
packages/core/lib/types/queueOptionsTypes.ts (1)
  • BaseQueueLocatorType (222-229)
packages/sns/lib/sns/AbstractSnsService.ts (2)
packages/sns/lib/index.ts (1)
  • SNSTopicLocatorType (9-9)
packages/core/lib/types/queueOptionsTypes.ts (1)
  • BaseQueueLocatorType (222-229)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: general (20.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/redis-message-deduplication-store) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/schemas) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/metrics) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/amqp) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/schemas) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/outbox-core) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/schemas) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/core) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/redis-message-deduplication-store) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: kafka (24.x) / build
  • GitHub Check: kafka (22.x) / build
🔇 Additional comments (28)
packages/sns/test/consumers/SnsSqsPermissionConsumer.startupResourcePolling.spec.ts (3)

1-59: LGTM! Clean test setup.

The test consumer class is well-structured and minimal, exposing only the necessary properties for validation. The imports are appropriate for the polling integration tests.


61-93: LGTM! Proper test lifecycle management.

The test setup correctly manages resource lifecycle with appropriate cleanup in beforeEach/afterEach hooks, and proper container disposal in afterAll.


188-232: LGTM! Disabled polling behavior is correctly tested.

The tests properly verify that when startupResourcePolling.enabled is false, the consumer fails immediately rather than polling for resource availability.

README.md (1)

247-343: LGTM! Comprehensive and well-structured documentation.

The documentation clearly explains:

  • The motivation and use case for startup resource polling
  • Configuration options with appropriate defaults
  • Environment-specific patterns (production with timeout, dev/staging with NO_TIMEOUT)
  • Error handling with concrete code examples

The integration into locatorConfig addresses the past review feedback, and the section is well-positioned in the README.

packages/sqs/lib/sqs/AbstractSqsService.ts (2)

2-6: LGTM! Proper type integration.

The SQSQueueLocatorType correctly extends BaseQueueLocatorType while preserving the existing discriminated union for queueUrl/queueName. This enables startup resource polling configuration without breaking existing code.

Also applies to: 45-55


117-117: LGTM! Logger propagation supports polling progress logging.

Passing the logger to initSqs enables progress logging during startup resource polling, consistent with the pattern used in AbstractSnsService.

packages/core/lib/utils/startupResourcePollingUtils.spec.ts (3)

10-29: LGTM! Complete coverage of enabled state checking.

The tests verify all relevant scenarios: enabled true/false, missing enabled field, and undefined config. This ensures the utility function behaves correctly in all cases.


31-159: LGTM! Thorough test coverage of polling behavior.

The test suite comprehensively covers:

  • Immediate resource availability
  • Multi-attempt polling until success
  • Timeout behavior with proper error
  • Error propagation from check function
  • Default configuration values
  • Logger integration
  • NO_TIMEOUT indefinite polling

All scenarios are well-tested with appropriate assertions.


161-171: LGTM! Proper error structure validation.

The test verifies that StartupResourcePollingTimeoutError includes all necessary information (resource name, timeout) in both the message and as properties, enabling effective debugging and error handling.

packages/sns/lib/sns/AbstractSnsService.ts (2)

3-7: LGTM! Consistent type integration with SQS.

The SNSTopicLocatorType properly extends BaseQueueLocatorType, enabling startup resource polling for SNS topics. The pattern is consistent with the SQS implementation.

Also applies to: 47-50


89-89: LGTM! Logger propagation matches SQS pattern.

Passing the logger to initSns enables startup resource polling progress logging, maintaining consistency with the SQS implementation.

packages/core/lib/types/queueOptionsTypes.ts (3)

133-148: LGTM! Well-documented NO_TIMEOUT symbol.

The symbol provides a clear, type-safe way to indicate indefinite polling. The JSDoc example demonstrates proper usage.


150-216: LGTM! Comprehensive type definition with excellent documentation.

The StartupResourcePollingConfig type is well-designed:

  • enabled is now optional (per past review feedback)
  • timeoutMs is required, ensuring callers explicitly choose between a timeout or NO_TIMEOUT
  • pollingIntervalMs is optional with a sensible default
  • Multiple examples cover common use cases

218-229: LGTM! Clean base type for locator composition.

BaseQueueLocatorType provides a clean extension point for protocol-specific locator types to inherit startup polling configuration.

packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts (4)

1-55: LGTM! Well-structured test consumer and imports.

The TestStartupResourcePollingConsumer class provides a minimal test harness that properly exposes queue properties for assertions.


57-74: LGTM! Proper test setup and teardown.

The test setup correctly:

  • Registers DI dependencies
  • Cleans up the queue before and after each test
  • Disposes the container properly

76-117: LGTM! Good coverage of enabled polling scenarios.

Tests correctly verify:

  • Successful initialization after delayed queue creation
  • StartupResourcePollingTimeoutError when timeout is reached
  • Proper timing of queue creation vs. init promise resolution

145-173: LGTM! Backward compatibility tests are essential.

These tests ensure the default behavior (immediate failure) is preserved when startupResourcePolling is disabled or not provided, maintaining backward compatibility.

packages/sqs/lib/utils/sqsInitter.ts (2)

21-23: LGTM! Clean type definition.

InitSqsExtraParams provides a clear extension point for passing logger context to the initialization flow.


75-114: LGTM! Well-structured polling integration.

The implementation cleanly separates the polling and non-polling paths:

  • Uses isStartupResourcePollingEnabled for type-safe guard
  • waitForResource receives properly configured checkFn that returns QueueArn
  • Error message now correctly uses the resolved queueUrl variable (addressing past review)
  • Both paths properly assign queueArn for subsequent validation
packages/sns/lib/utils/snsInitter.ts (2)

25-27: LGTM! Type aliases maintain consistency.

Using type aliases for InitSnsSqsExtraParams and InitSnsExtraParams keeps the API surface clean while allowing future extension.


246-285: LGTM! Clean polling integration for SNS-only path.

The initSns function properly integrates polling with a type-safe guard and correct error handling. The error message correctly uses the resolved topicArn variable.

packages/core/lib/utils/startupResourcePollingUtils.ts (6)

1-5: LGTM! Clean imports and sensible default.

The 5-second default polling interval is a reasonable balance between responsiveness and avoiding excessive API calls.


7-39: LGTM! Well-designed type definitions.

The discriminated union for StartupResourcePollingCheckResult provides type-safe handling of both available and unavailable states. The WaitForResourceOptions type is thoroughly documented.


41-54: LGTM! Proper custom error implementation.

The StartupResourcePollingTimeoutError correctly:

  • Extends Error
  • Sets this.name for proper error identification
  • Includes contextual properties (resourceName, timeoutMs)
  • Provides a descriptive message

56-92: LGTM! Clean helper functions.

The helper functions properly encapsulate timeout checking and success logging, keeping the main polling loop focused.


118-155: Timeout check timing allows one extra check after deadline.

The timeout is checked at the start of each iteration, meaning if a check takes a long time, the actual elapsed time could exceed timeoutMs before the timeout is triggered. This is generally acceptable for startup polling, but worth noting.

Additionally, the throttled logging (attemptCount % 12 === 0) assumes the default 5s interval for the "every minute" comment, but if a custom interval is used, the logging frequency will differ.

These are minor behavioral notes rather than issues:

  1. The timeout behavior (check-before-poll) is standard and acceptable
  2. The throttled logging comment could be clarified to note it's based on the default interval

No changes required unless you prefer to document or adjust this behavior.


158-165: LGTM! Type guard enables safe config access.

The isStartupResourcePollingEnabled function is a proper type guard that narrows config to StartupResourcePollingConfig when it returns true, ensuring type-safe access in the polling branches.

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
packages/sns/lib/utils/snsInitter.ts (1)

42-50: Return type signature missing resourcesReady field.

The return type on line 50 specifies { subscriptionArn: string; topicArn: string; queueUrl: string; queueName: string }, but the actual returns at lines 103, 151, 187, and 224 all include an additional resourcesReady: boolean field. This causes a type mismatch.

🐛 Proposed fix
 export async function initSnsSqs(
   sqsClient: SQSClient,
   snsClient: SNSClient,
   stsClient: STSClient,
   locatorConfig?: SNSSQSQueueLocatorType,
   creationConfig?: SNSCreationConfig & SQSCreationConfig,
   subscriptionConfig?: SNSSubscriptionOptions,
   extraParams?: InitSnsSqsExtraParams,
-): Promise<{ subscriptionArn: string; topicArn: string; queueUrl: string; queueName: string }> {
+): Promise<{ subscriptionArn: string; topicArn: string; queueUrl: string; queueName: string; resourcesReady: boolean }> {
🧹 Nitpick comments (1)
packages/core/lib/utils/startupResourcePollingUtils.ts (1)

297-309: Consider adding error handler to outer promise chain.

In non-blocking mode, the background polling is initiated with setTimeout().then(...). While the inner pollForResource has a .catch() handler (lines 302-308), the outer setTimeout promise chain itself lacks one. Though setTimeout rarely fails, adding a catch handler would be more defensive.

♻️ Suggested improvement
     // Fire and forget - start polling in background
     setTimeout(pollingIntervalMs).then(() => {
       pollForResource(options, pollingIntervalMs, hasTimeout, timeoutMs, throwOnTimeout, 1)
         .then((result) => {
           onResourceAvailable?.(result)
         })
         .catch((error) => {
           logger?.error({
             message: `Background polling for resource "${resourceName}" failed`,
             resourceName,
             error,
           })
         })
-    })
+    }).catch((error) => {
+      logger?.error({
+        message: `Failed to schedule background polling for resource "${resourceName}"`,
+        resourceName,
+        error,
+      })
+    })
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf023cc and bbed226.

📒 Files selected for processing (7)
  • README.md
  • packages/core/lib/types/MessageQueueTypes.ts
  • packages/core/lib/types/queueOptionsTypes.ts
  • packages/core/lib/utils/startupResourcePollingUtils.spec.ts
  • packages/core/lib/utils/startupResourcePollingUtils.ts
  • packages/sns/lib/utils/snsInitter.ts
  • packages/sqs/lib/utils/sqsInitter.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/core/lib/utils/startupResourcePollingUtils.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md
🧰 Additional context used
🧬 Code graph analysis (1)
packages/core/lib/utils/startupResourcePollingUtils.ts (2)
packages/core/lib/index.ts (5)
  • StartupResourcePollingCheckResult (110-110)
  • WaitForResourceOptions (112-112)
  • StartupResourcePollingTimeoutError (111-111)
  • waitForResource (113-113)
  • isStartupResourcePollingEnabled (109-109)
packages/core/lib/types/queueOptionsTypes.ts (2)
  • StartupResourcePollingConfig (198-238)
  • NO_TIMEOUT (148-148)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: general (20.x, @message-queue-toolkit/amqp) / build
  • GitHub Check: kafka (22.x) / build
  • GitHub Check: kafka (24.x) / build
🔇 Additional comments (5)
packages/core/lib/types/MessageQueueTypes.ts (1)

1-5: LGTM - Clean addition of error reporting support.

The addition of ErrorReporter to ExtraParams cleanly enables error reporting throughout the startup resource polling flows without breaking existing code.

Also applies to: 45-45

packages/core/lib/types/queueOptionsTypes.ts (1)

133-251: LGTM - Well-documented polling configuration types.

The new types (NO_TIMEOUT, StartupResourcePollingConfig, BaseQueueLocatorType) are well-designed with comprehensive documentation and clear examples. The use of a Symbol for NO_TIMEOUT is appropriate for a sentinel value, and the optional enabled field correctly reflects the JSDoc description.

packages/sqs/lib/utils/sqsInitter.ts (1)

87-148: LGTM - Polling logic correctly integrated.

The startup resource polling implementation is correct:

  • The polling flow properly uses waitForResource when enabled
  • Non-blocking mode is handled correctly with the onQueueReady callback
  • Fallback to original immediate-check behavior is preserved
  • Error messages correctly use the resolved queueUrl variable
  • Return type matches the actual returns
packages/core/lib/utils/startupResourcePollingUtils.ts (1)

170-241: LGTM - Solid polling implementation.

The core polling logic is well-structured:

  • First synchronous check optimizes for immediate availability
  • Polling loop correctly handles timeout, logging, and error propagation
  • Non-blocking mode properly returns undefined and schedules background polling
  • Timeout reset (line 206) correctly supports the "report and continue" mode
  • Error handling appropriately catches, logs, and rethrows unexpected errors from checkFn

Also applies to: 252-316

packages/sns/lib/utils/snsInitter.ts (1)

116-226: LGTM - Polling logic correctly implemented.

The startup resource polling implementation follows the same pattern as SQS and is correctly structured:

  • Sequential polling for topic and queue in initSnsSqs
  • Non-blocking mode returns early with resourcesReady: false
  • Callbacks (onResourcesReady, onTopicReady) are invoked when resources become available
  • Fallback to immediate checks when polling is disabled
  • Error messages use resolved ARN/URL variables

Also applies to: 326-360

@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: 2

🤖 Fix all issues with AI agents
In @packages/sns/lib/utils/snsInitter.ts:
- Around line 122-195: The onResourcesReady callback is being called twice from
the per-resource onResourceAvailable handlers (when nonBlocking is true) even
though it should fire only once after both topic and queue are available; modify
the logic around waitForResource (the topic and queue wait blocks using
getTopicAttributes/getQueueAttributes) to stop invoking
extraParams?.onResourcesReady inside each onResourceAvailable, and instead track
availability with local flags (e.g., topicAvailable and queueAvailable) and
invoke extraParams?.onResourcesReady({ topicArn: subscriptionTopicArn, queueUrl
}) exactly once when both topicResult and queueResult indicate availability (and
only when nonBlocking), making sure you still return early with resourcesReady:
false if a non-blocking wait returned undefined for either resource.

In @packages/sqs/lib/sqs/AbstractSqsService.ts:
- Line 98: The field queueArn in AbstractSqsService was changed to string |
undefined causing potential undefined access when non-blocking startup (initSqs)
returns undefined; update any code that reads this.queueArn (including
subclasses and methods in AbstractSqsService) to guard against undefined before
use (e.g., conditional checks or early returns) and add appropriate
fallback/error logging that includes the undefined case so initialization and
callbacks don’t throw; specifically audit initSqs, any constructors,
initialization callbacks, and methods referencing queueArn to ensure they handle
the undefined value safely.
🧹 Nitpick comments (1)
packages/sns/lib/utils/snsInitter.ts (1)

148-150: Consider extracting queue name extraction logic.

The logic to extract the queue name from the queue URL is duplicated three times in this function (lines 148-150, 184-186, and 216-219). Consider extracting this into a helper function to improve maintainability.

♻️ Proposed refactor
function extractQueueNameFromUrl(queueUrl: string): string {
  const splitUrl = queueUrl.split('/')
  // biome-ignore lint/style/noNonNullAssertion: It's ok
  return splitUrl[splitUrl.length - 1]!
}

Then replace the duplicated code:

     // If non-blocking and topic wasn't immediately available, return early
     if (nonBlocking && topicResult === undefined) {
-      const splitUrl = queueUrl.split('/')
-      // biome-ignore lint/style/noNonNullAssertion: It's ok
-      const queueName = splitUrl[splitUrl.length - 1]!
+      const queueName = extractQueueNameFromUrl(queueUrl)

       return {
         subscriptionArn: locatorConfig.subscriptionArn,
         topicArn: subscriptionTopicArn,
         queueUrl,
         queueName,
         resourcesReady: false,
       }
     }

(And similar changes for the other occurrences.)

Also applies to: 184-186, 216-219

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbed226 and d24e84b.

📒 Files selected for processing (3)
  • README.md
  • packages/sns/lib/utils/snsInitter.ts
  • packages/sqs/lib/sqs/AbstractSqsService.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: general (20.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/schemas) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/redis-message-deduplication-store) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/gcs-payload-store) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/metrics) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/outbox-core) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/amqp) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/redis-message-deduplication-store) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/schemas) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/metrics) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/sns) / build
  • GitHub Check: kafka (24.x) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: kafka (22.x) / build
🔇 Additional comments (6)
packages/sqs/lib/sqs/AbstractSqsService.ts (3)

2-6: LGTM!

The addition of BaseQueueLocatorType to the imports is necessary for the updated SQSQueueLocatorType definition and aligns with the polling feature implementation.


116-116: Good addition for observability.

Passing the logger context to initSqs enables better debugging and monitoring during queue initialization, which is especially valuable for the new polling feature.


45-55: No breaking changes introduced by this intersection.

The BaseQueueLocatorType adds only an optional startupResourcePolling field. Existing consumers can continue to construct SQSQueueLocatorType with just queueUrl or queueName without providing this field, so no modifications are required.

packages/sns/lib/utils/snsInitter.ts (3)

25-39: LGTM!

The type definitions are well-documented and clearly express when the callbacks will be invoked. The optional nature of the callbacks allows backward compatibility.


312-365: LGTM!

The polling logic for the single topic resource is correctly implemented. The callback is invoked appropriately when the topic becomes available in non-blocking mode, and early returns are handled properly.


122-144: Verify the crash-loop behavior is intentional.

Responding to CarlosGamero's question: When resources aren't available within the configured timeout, waitForResource throws a ResourceAvailabilityTimeoutError, which will cause the application to crash and restart (crash-loop behavior).

This may be the intended design (fail-fast), but it's worth considering whether a more resilient approach would be better for production deployments:

  • Continue running and report the error via ErrorReporter for visibility
  • Allow the application to remain available for other operations
  • Retry resource checks periodically without full restart overhead

Please confirm whether the crash-loop behavior is the desired failure mode, or whether the error should be reported but allow the application to continue.

Comment thread packages/sns/lib/utils/snsInitter.ts
protected queueUrl: string
// @ts-expect-error
protected queueArn: string
protected queueArn: string | undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Find the directory structure
echo "=== Finding AbstractSqsService and related files ==="
find . -name "AbstractSqsService.ts" -o -name "*SqsService*.ts" | head -20

echo ""
echo "=== Finding all classes extending AbstractSqsService ==="
rg -l "extends AbstractSqsService" --type ts | grep -v node_modules

echo ""
echo "=== Checking subclasses and their queueArn usage ==="
rg "extends AbstractSqsService" --type ts -A 50 | grep -E "(class|queueArn)"

echo ""
echo "=== All references to queueArn in codebase ==="
rg "queueArn" --type ts -B 2 -A 2 | grep -v node_modules | grep -v ".test.ts"

echo ""
echo "=== Checking AbstractSqsService.ts for queueArn definition and usage ==="
cat packages/sqs/lib/sqs/AbstractSqsService.ts | head -150

Repository: kibertoad/message-queue-toolkit

Length of output: 20066


Type signature change makes queueArn nullable in non-blocking startup mode.

The change from string to string | undefined is intentional: initSqs() explicitly returns undefined when using non-blocking resource polling. While this is a type-level breaking change requiring null checks in strict TypeScript code, the actual runtime impact appears limited since current usage occurs primarily in error logging and non-critical paths.

If non-blocking startup polling is enabled in your setup, verify that any subclass logic accessing this.queueArn can tolerate undefined values, particularly in initialization or callback sequences.

🤖 Prompt for AI Agents
In @packages/sqs/lib/sqs/AbstractSqsService.ts at line 98, The field queueArn in
AbstractSqsService was changed to string | undefined causing potential undefined
access when non-blocking startup (initSqs) returns undefined; update any code
that reads this.queueArn (including subclasses and methods in
AbstractSqsService) to guard against undefined before use (e.g., conditional
checks or early returns) and add appropriate fallback/error logging that
includes the undefined case so initialization and callbacks don’t throw;
specifically audit initSqs, any constructors, initialization callbacks, and
methods referencing queueArn to ensure they handle the undefined value safely.

@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

🤖 Fix all issues with AI agents
In
@packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts:
- Around line 77-101: Tests use fixed setTimeout delays (e.g., in
SqsPermissionConsumer.startupResourcePolling.spec.ts around the
TestStartupResourcePollingConsumer init flow) which can be flaky; replace the
ad-hoc sleeps with a deterministic wait helper that polls the resource until
available (e.g., poll assertQueue or describeQueue until success with a short
interval and overall timeout) and use that helper before awaiting
consumer.init(), or at minimum increase timeouts; update all occurrences
referencing setTimeout(300/500/200) and the
TestStartupResourcePollingConsumer.init() test flows (also lines 119-142 and
218-252) to use the polling helper or retries instead of fixed delays.
🧹 Nitpick comments (1)
packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts (1)

218-252: Testing internal implementation directly.

This test imports and calls the internal initSqs function directly to verify the onQueueReady callback mechanism. While the comment acknowledges that AbstractSqsConsumer doesn't expose this callback, testing internal implementation details can make tests brittle and tightly coupled to the implementation.

Consider whether the callback mechanism is important enough to expose as a protected or public API on the consumer, which would allow testing through the intended abstraction.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d24e84b and fcada62.

📒 Files selected for processing (1)
  • packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: general (22.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/metrics) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/schemas) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/core) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/outbox-core) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/s3-payload-store) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/gcs-payload-store) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/metrics) / build
  • GitHub Check: general (22.x, @message-queue-toolkit/amqp) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/sqs) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/redis-message-deduplication-store) / build
  • GitHub Check: general (24.x, @message-queue-toolkit/sns) / build
  • GitHub Check: general (20.x, @message-queue-toolkit/gcp-pubsub) / build
  • GitHub Check: kafka (22.x) / build
  • GitHub Check: kafka (24.x) / build

Comment on lines +77 to +101
it('waits for queue to become available and initializes successfully', async () => {
const consumer = new TestStartupResourcePollingConsumer(diContainer.cradle, {
locatorConfig: {
queueUrl,
startupResourcePolling: {
enabled: true,
pollingIntervalMs: 100,
timeoutMs: 5000,
},
},
})

// Start init in background
const initPromise = consumer.init()

// Wait a bit then create the queue
await setTimeout(300)
await assertQueue(sqsClient, { QueueName: queueName })

// Init should complete successfully
await initPromise

expect(consumer.queueProps.url).toBe(queueUrl)
expect(consumer.queueProps.name).toBe(queueName)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Tests use fixed delays that may be timing-sensitive.

Multiple tests use setTimeout with fixed delays (300ms, 500ms, 200ms) to simulate asynchronous queue creation. While these delays are reasonable for LocalStack integration tests, they could potentially be flaky on slow CI systems or under heavy load.

If these tests become flaky in CI, consider:

  • Increasing the delays slightly
  • Using more robust polling/waiting patterns
  • Adding retry logic for transient failures

For now, the current approach is acceptable for LocalStack-based integration tests.

Also applies to: 119-142, 218-252

🤖 Prompt for AI Agents
In
@packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts
around lines 77 - 101, Tests use fixed setTimeout delays (e.g., in
SqsPermissionConsumer.startupResourcePolling.spec.ts around the
TestStartupResourcePollingConsumer init flow) which can be flaky; replace the
ad-hoc sleeps with a deterministic wait helper that polls the resource until
available (e.g., poll assertQueue or describeQueue until success with a short
interval and overall timeout) and use that helper before awaiting
consumer.init(), or at minimum increase timeouts; update all occurrences
referencing setTimeout(300/500/200) and the
TestStartupResourcePollingConsumer.init() test flows (also lines 119-142 and
218-252) to use the polling helper or retries instead of fixed delays.

@kibertoad
kibertoad merged commit fd5d482 into main Jan 7, 2026
38 checks passed
@kibertoad
kibertoad deleted the feat/polling branch January 7, 2026 14:56
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.

3 participants