refactor: flatten Microsoft skills from nested to flat directory structure
Rewrote sync_microsoft_skills.py (v4) to use each SKILL.md's frontmatter 'name' field as the flat directory name under skills/, replacing the nested skills/official/microsoft/<lang>/<category>/<service>/ hierarchy. This fixes CI failures caused by the indexing, validation, and catalog scripts expecting skills/<id>/SKILL.md (depth 1). Changes: - Rewrite scripts/sync_microsoft_skills.py for flat output with collision detection - Update scripts/tests/inspect_microsoft_repo.py for flat name mapping - Update scripts/tests/test_comprehensive_coverage.py for name uniqueness checks - Delete skills/official/ nested directory - Add 129 Microsoft skills as flat directories (e.g. skills/azure-mgmt-botservice-dotnet/) - Move attribution files to docs/ (LICENSE-MICROSOFT, microsoft-skills-attribution.json) - Rebuild skills_index.json, CATALOG.md, README.md (845 total skills)
This commit is contained in:
233
skills/azure-servicebus-ts/SKILL.md
Normal file
233
skills/azure-servicebus-ts/SKILL.md
Normal file
@@ -0,0 +1,233 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user