- Wrapped unquoted @scope/pkg values in double quotes across 19 SKILL.md files. - Added 'package' to ALLOWED_FIELDS in JS validator. - Added YAML validity regression test to test suite. - Updated package-lock.json. Fixes #79 Closes #80
234 lines
6.4 KiB
Markdown
234 lines
6.4 KiB
Markdown
---
|
|
name: azure-servicebus-ts
|
|
description: Build messaging applications using Azure Service Bus SDK for JavaScript (@azure/service-bus). Use when implementing queues, topics/subscriptions, message sessions, dead-letter handling, or enterprise messaging patterns.
|
|
package: "@azure/service-bus"
|
|
---
|
|
|
|
# Azure Service Bus SDK for TypeScript
|
|
|
|
Enterprise messaging with queues, topics, and subscriptions.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
npm install @azure/service-bus @azure/identity
|
|
```
|
|
|
|
## Environment Variables
|
|
|
|
```bash
|
|
SERVICEBUS_NAMESPACE=<namespace>.servicebus.windows.net
|
|
SERVICEBUS_QUEUE_NAME=my-queue
|
|
SERVICEBUS_TOPIC_NAME=my-topic
|
|
SERVICEBUS_SUBSCRIPTION_NAME=my-subscription
|
|
```
|
|
|
|
## Authentication
|
|
|
|
```typescript
|
|
import { ServiceBusClient } from "@azure/service-bus";
|
|
import { DefaultAzureCredential } from "@azure/identity";
|
|
|
|
const fullyQualifiedNamespace = process.env.SERVICEBUS_NAMESPACE!;
|
|
const client = new ServiceBusClient(fullyQualifiedNamespace, new DefaultAzureCredential());
|
|
```
|
|
|
|
## Core Workflow
|
|
|
|
### Send Messages to Queue
|
|
|
|
```typescript
|
|
const sender = client.createSender("my-queue");
|
|
|
|
// Single message
|
|
await sender.sendMessages({
|
|
body: { orderId: "12345", amount: 99.99 },
|
|
contentType: "application/json",
|
|
});
|
|
|
|
// Batch messages
|
|
const batch = await sender.createMessageBatch();
|
|
batch.tryAddMessage({ body: "Message 1" });
|
|
batch.tryAddMessage({ body: "Message 2" });
|
|
await sender.sendMessages(batch);
|
|
|
|
await sender.close();
|
|
```
|
|
|
|
### Receive Messages from Queue
|
|
|
|
```typescript
|
|
const receiver = client.createReceiver("my-queue");
|
|
|
|
// Receive batch
|
|
const messages = await receiver.receiveMessages(10, { maxWaitTimeInMs: 5000 });
|
|
for (const message of messages) {
|
|
console.log(`Received: ${message.body}`);
|
|
await receiver.completeMessage(message);
|
|
}
|
|
|
|
await receiver.close();
|
|
```
|
|
|
|
### Subscribe to Messages (Event-Driven)
|
|
|
|
```typescript
|
|
const receiver = client.createReceiver("my-queue");
|
|
|
|
const subscription = receiver.subscribe({
|
|
processMessage: async (message) => {
|
|
console.log(`Processing: ${message.body}`);
|
|
// Message auto-completed on success
|
|
},
|
|
processError: async (args) => {
|
|
console.error(`Error: ${args.error}`);
|
|
},
|
|
});
|
|
|
|
// Stop after some time
|
|
setTimeout(async () => {
|
|
await subscription.close();
|
|
await receiver.close();
|
|
}, 60000);
|
|
```
|
|
|
|
### Topics and Subscriptions
|
|
|
|
```typescript
|
|
// Send to topic
|
|
const topicSender = client.createSender("my-topic");
|
|
await topicSender.sendMessages({
|
|
body: { event: "order.created", data: { orderId: "123" } },
|
|
applicationProperties: { eventType: "order.created" },
|
|
});
|
|
|
|
// Receive from subscription
|
|
const subscriptionReceiver = client.createReceiver("my-topic", "my-subscription");
|
|
const messages = await subscriptionReceiver.receiveMessages(10);
|
|
```
|
|
|
|
## Message Sessions
|
|
|
|
```typescript
|
|
// Send session message
|
|
const sender = client.createSender("session-queue");
|
|
await sender.sendMessages({
|
|
body: { step: 1, data: "First step" },
|
|
sessionId: "workflow-123",
|
|
});
|
|
|
|
// Receive session messages
|
|
const sessionReceiver = await client.acceptSession("session-queue", "workflow-123");
|
|
const messages = await sessionReceiver.receiveMessages(10);
|
|
|
|
// Get/set session state
|
|
const state = await sessionReceiver.getSessionState();
|
|
await sessionReceiver.setSessionState(Buffer.from(JSON.stringify({ progress: 50 })));
|
|
|
|
await sessionReceiver.close();
|
|
```
|
|
|
|
## Dead-Letter Handling
|
|
|
|
```typescript
|
|
// Move to dead-letter
|
|
await receiver.deadLetterMessage(message, {
|
|
deadLetterReason: "Validation failed",
|
|
deadLetterErrorDescription: "Missing required field: orderId",
|
|
});
|
|
|
|
// Process dead-letter queue
|
|
const dlqReceiver = client.createReceiver("my-queue", { subQueueType: "deadLetter" });
|
|
const dlqMessages = await dlqReceiver.receiveMessages(10);
|
|
for (const msg of dlqMessages) {
|
|
console.log(`DLQ Reason: ${msg.deadLetterReason}`);
|
|
// Reprocess or log
|
|
await dlqReceiver.completeMessage(msg);
|
|
}
|
|
```
|
|
|
|
## Scheduled Messages
|
|
|
|
```typescript
|
|
const sender = client.createSender("my-queue");
|
|
|
|
// Schedule for future delivery
|
|
const scheduledTime = new Date(Date.now() + 60000); // 1 minute from now
|
|
const sequenceNumber = await sender.scheduleMessages(
|
|
{ body: "Delayed message" },
|
|
scheduledTime
|
|
);
|
|
|
|
// Cancel scheduled message
|
|
await sender.cancelScheduledMessages(sequenceNumber);
|
|
```
|
|
|
|
## Message Deferral
|
|
|
|
```typescript
|
|
// Defer message for later
|
|
await receiver.deferMessage(message);
|
|
|
|
// Receive deferred message by sequence number
|
|
const deferredMessage = await receiver.receiveDeferredMessages(message.sequenceNumber!);
|
|
await receiver.completeMessage(deferredMessage[0]);
|
|
```
|
|
|
|
## Peek Messages (Non-Destructive)
|
|
|
|
```typescript
|
|
const receiver = client.createReceiver("my-queue");
|
|
|
|
// Peek without removing
|
|
const peekedMessages = await receiver.peekMessages(10);
|
|
for (const msg of peekedMessages) {
|
|
console.log(`Peeked: ${msg.body}`);
|
|
}
|
|
```
|
|
|
|
## Key Types
|
|
|
|
```typescript
|
|
import {
|
|
ServiceBusClient,
|
|
ServiceBusSender,
|
|
ServiceBusReceiver,
|
|
ServiceBusSessionReceiver,
|
|
ServiceBusMessage,
|
|
ServiceBusReceivedMessage,
|
|
ProcessMessageCallback,
|
|
ProcessErrorCallback,
|
|
} from "@azure/service-bus";
|
|
```
|
|
|
|
## Receive Modes
|
|
|
|
```typescript
|
|
// Peek-Lock (default) - message locked until completed/abandoned
|
|
const receiver = client.createReceiver("my-queue", { receiveMode: "peekLock" });
|
|
await receiver.completeMessage(message); // Remove from queue
|
|
await receiver.abandonMessage(message); // Return to queue
|
|
await receiver.deferMessage(message); // Defer for later
|
|
await receiver.deadLetterMessage(message); // Move to DLQ
|
|
|
|
// Receive-and-Delete - message removed immediately
|
|
const receiver = client.createReceiver("my-queue", { receiveMode: "receiveAndDelete" });
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Use Entra ID auth** - Avoid connection strings in production
|
|
2. **Reuse clients** - Create `ServiceBusClient` once, share across senders/receivers
|
|
3. **Close resources** - Always close senders/receivers when done
|
|
4. **Handle errors** - Implement `processError` callback for subscription receivers
|
|
5. **Use sessions for ordering** - When message order matters within a group
|
|
6. **Configure dead-letter** - Always handle DLQ messages
|
|
7. **Batch sends** - Use `createMessageBatch()` for multiple messages
|
|
|
|
## Reference Documentation
|
|
|
|
For detailed patterns, see:
|
|
|
|
- [Queues vs Topics Patterns](references/queues-topics.md) - Queue/topic patterns, sessions, receive modes, message settlement
|
|
- [Error Handling and Reliability](references/error-handling.md) - ServiceBusError codes, DLQ handling, lock renewal, graceful shutdown
|