Skip to content

Commit d03af62

Browse files
committed
Address code review comments
1 parent fcada62 commit d03af62

3 files changed

Lines changed: 133 additions & 9 deletions

File tree

README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,80 @@ import { NO_TIMEOUT } from '@message-queue-toolkit/core'
354354
}
355355
```
356356

357+
### Non-Blocking Mode
358+
359+
When `nonBlocking: true` is set, `init()` returns immediately if the resource is not available on the first check. Polling continues in the background, and you can use callbacks to be notified when resources become available.
360+
361+
**Important behaviors:**
362+
- If the resource IS immediately available, `init()` returns normally with the resource ready
363+
- If the resource is NOT immediately available, `init()` returns with `resourcesReady: false` (for SNS/SQS) or `queueArn: undefined` (for SQS)
364+
- Background polling continues and invokes the callback when the resource becomes available
365+
- For SNS+SQS consumers, the callback is only invoked when BOTH topic and queue are available
366+
367+
```typescript
368+
// SQS non-blocking with callback
369+
import { initSqs } from '@message-queue-toolkit/sqs'
370+
371+
const result = await initSqs(
372+
sqsClient,
373+
{
374+
queueUrl: '...',
375+
startupResourcePolling: {
376+
enabled: true,
377+
timeoutMs: 5 * 60 * 1000,
378+
nonBlocking: true,
379+
},
380+
},
381+
undefined,
382+
undefined,
383+
{
384+
onQueueReady: ({ queueArn }) => {
385+
console.log(`Queue is now available: ${queueArn}`)
386+
// Start consuming messages, update health checks, etc.
387+
},
388+
},
389+
)
390+
391+
if (result.queueArn) {
392+
console.log('Queue was immediately available')
393+
} else {
394+
console.log('Queue not yet available, will be notified via callback')
395+
}
396+
397+
// SNS+SQS non-blocking with callback
398+
import { initSnsSqs } from '@message-queue-toolkit/sns'
399+
400+
const result = await initSnsSqs(
401+
sqsClient,
402+
snsClient,
403+
stsClient,
404+
{
405+
topicArn: '...',
406+
queueUrl: '...',
407+
subscriptionArn: '...',
408+
startupResourcePolling: {
409+
enabled: true,
410+
timeoutMs: 5 * 60 * 1000,
411+
nonBlocking: true,
412+
},
413+
},
414+
undefined,
415+
undefined,
416+
{
417+
onResourcesReady: ({ topicArn, queueUrl }) => {
418+
// Called only when BOTH topic and queue are available
419+
console.log(`Resources ready: topic=${topicArn}, queue=${queueUrl}`)
420+
},
421+
},
422+
)
423+
424+
if (result.resourcesReady) {
425+
console.log('All resources were immediately available')
426+
} else {
427+
console.log('Resources not yet available, will be notified via callback')
428+
}
429+
```
430+
357431
### Error Handling
358432

359433
When `timeoutMs` is configured and the resource doesn't become available within the specified time, a `StartupResourcePollingTimeoutError` is thrown:

packages/sns/lib/utils/snsInitter.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,17 +122,25 @@ export async function initSnsSqs(
122122
if (isStartupResourcePollingEnabled(startupResourcePolling)) {
123123
const nonBlocking = startupResourcePolling.nonBlocking === true
124124

125+
// Track availability for non-blocking mode coordination
126+
let topicAvailable = false
127+
let queueAvailable = false
128+
129+
const notifyIfBothReady = () => {
130+
if (nonBlocking && topicAvailable && queueAvailable) {
131+
extraParams?.onResourcesReady?.({ topicArn: subscriptionTopicArn, queueUrl })
132+
}
133+
}
134+
125135
// Wait for topic to become available
126136
const topicResult = await waitForResource({
127137
config: startupResourcePolling,
128138
resourceName: `SNS topic ${subscriptionTopicArn}`,
129139
logger: extraParams?.logger,
130140
errorReporter: extraParams?.errorReporter,
131141
onResourceAvailable: () => {
132-
// In non-blocking mode, when topic becomes available, notify caller
133-
if (nonBlocking) {
134-
extraParams?.onResourcesReady?.({ topicArn: subscriptionTopicArn, queueUrl })
135-
}
142+
topicAvailable = true
143+
notifyIfBothReady()
136144
},
137145
checkFn: async () => {
138146
const result = await getTopicAttributes(snsClient, subscriptionTopicArn)
@@ -143,12 +151,43 @@ export async function initSnsSqs(
143151
},
144152
})
145153

154+
// If topic was immediately available, mark it
155+
if (topicResult !== undefined) {
156+
topicAvailable = true
157+
}
158+
146159
// If non-blocking and topic wasn't immediately available, return early
160+
// Background polling will continue and call notifyIfBothReady when topic is available
147161
if (nonBlocking && topicResult === undefined) {
148162
const splitUrl = queueUrl.split('/')
149163
// biome-ignore lint/style/noNonNullAssertion: It's ok
150164
const queueName = splitUrl[splitUrl.length - 1]!
151165

166+
// Also start polling for queue in background so we can notify when both are ready
167+
waitForResource({
168+
config: startupResourcePolling,
169+
resourceName: `SQS queue ${queueUrl}`,
170+
logger: extraParams?.logger,
171+
errorReporter: extraParams?.errorReporter,
172+
onResourceAvailable: () => {
173+
queueAvailable = true
174+
notifyIfBothReady()
175+
},
176+
checkFn: async () => {
177+
const result = await getQueueAttributes(sqsClient, queueUrl)
178+
if (result.error === 'not_found') {
179+
return { isAvailable: false }
180+
}
181+
return { isAvailable: true, result: result.result }
182+
},
183+
}).catch((error) => {
184+
extraParams?.logger?.error({
185+
message: 'Background polling for SQS queue failed',
186+
queueUrl,
187+
error,
188+
})
189+
})
190+
152191
return {
153192
subscriptionArn: locatorConfig.subscriptionArn,
154193
topicArn: subscriptionTopicArn,
@@ -165,10 +204,8 @@ export async function initSnsSqs(
165204
logger: extraParams?.logger,
166205
errorReporter: extraParams?.errorReporter,
167206
onResourceAvailable: () => {
168-
// In non-blocking mode, when queue becomes available, notify caller
169-
if (nonBlocking) {
170-
extraParams?.onResourcesReady?.({ topicArn: subscriptionTopicArn, queueUrl })
171-
}
207+
queueAvailable = true
208+
notifyIfBothReady()
172209
},
173210
checkFn: async () => {
174211
const result = await getQueueAttributes(sqsClient, queueUrl)
@@ -179,6 +216,11 @@ export async function initSnsSqs(
179216
},
180217
})
181218

219+
// If queue was immediately available, mark it
220+
if (queueResult !== undefined) {
221+
queueAvailable = true
222+
}
223+
182224
// If non-blocking and queue wasn't immediately available, return early
183225
if (nonBlocking && queueResult === undefined) {
184226
const splitUrl = queueUrl.split('/')

packages/sqs/test/consumers/SqsPermissionConsumer.startupResourcePolling.spec.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,15 @@ describe('SqsPermissionConsumer - startupResourcePollingConfig', () => {
226226
// Start init without queue existing
227227
const initPromise = initSqs(
228228
sqsClient,
229-
{ queueUrl, startupResourcePolling: { enabled: true, pollingIntervalMs: 50, timeoutMs: 5000, nonBlocking: true } },
229+
{
230+
queueUrl,
231+
startupResourcePolling: {
232+
enabled: true,
233+
pollingIntervalMs: 50,
234+
timeoutMs: 5000,
235+
nonBlocking: true,
236+
},
237+
},
230238
undefined,
231239
undefined,
232240
{

0 commit comments

Comments
 (0)