Skip to content

add @node-ts/bus-azure-service-bus, an azure service bus transport - #357

Open
adenhertog wants to merge 3 commits into
masterfrom
issue-346-azure-service-bus
Open

adenhertog wants to merge 3 commits into
masterfrom
issue-346-azure-service-bus

Conversation

@adenhertog

Copy link
Copy Markdown
Contributor

Closes #346

Summary

Adds @node-ts/bus-azure-service-bus, an Azure Service Bus transport that passes transportTests against Microsoft's Service Bus emulator. The emulator tests run in a new CircleCI machine-executor job.

Background

Azure Service Bus is the main managed broker on Azure. The issue asked for a transport built like bus-sqs, with deploy-time provisioning (#339) and the emulator in CI. The design decisions are in this comment.

Problem

There was no transport for Azure Service Bus. Some of the issue's proposals also didn't hold up against the SDK and the emulator:

  • maxDeliveryCount can't be set high: the emulator only accepts 1-10.
  • One receiver can't run concurrent receiveMessages calls.
  • Since @azure/service-bus 7.10, the JS admin client does work against the emulator, so no stubs or config-file seeding are needed.

Approach

  • Topology: a topic per message (resolveTopicName drops a leading @, replaces disallowed characters with -, and hashes names over the limits) and a queue per endpoint. Each handled topic, and each custom handler's topic, gets a subscription named after the queue that forwards into it.

  • Receiving: subscribe() with maxConcurrentCalls = concurrency feeds readNextMessage(). Each callback is held until the bus settles the message, so the SDK keeps renewing the lock (lockDuration PT1M, auto-renewal 5 min). stop() abandons messages that were delivered but not read.

  • Retries: returnMessage schedules a copy at now + delay with failedAttempts + 1 and native id <messageId>:<attempt>, then completes the original. deliveryCount is ignored, and maxDeliveryCount (10) only catches crash loops.

  • Dead-lettering: native deadLetterMessage with bus-failure, a reason and a description. The queue and its subscriptions forward dead letters to the shared deadLetterQueueName. The forwarded copy keeps bus-failure and the headers (verified on the emulator), so the copy-then-complete fallback wasn't needed.

  • Provisioning: provision() uses the admin client and needs Manage. It throws AzureServiceBusTierNotSupported on the Basic tier and treats 409 as success. It then updates forwarding and lock settings that differ. A dry run makes no calls.

  • Runtime plan: format azure-rbac, with role assignments scoped relative to the namespace:

    • Data Receiver and Data Sender on the queue;
    • Data Sender on each topic, or on the namespace for schedulers;
    • Data Owner, only with verifySubscriptions.
  • Startup checks: initialize() peeks the queue (Listen only). The opt-in verifySubscriptions also checks the dead letter queue and the subscriptions through the admin client.

  • Auth and clients: connectionString, or fullyQualifiedNamespace + TokenCredential, with an optional injected ServiceBusClient and admin client. There's no @azure/identity dependency.

  • Errors and replies: AzureServiceBusMessageTooLarge for over-size messages, ResourcesNotProvisioned for a missing topic, and EndpointNotFound for a reply to a missing queue. Replies go straight to the queue in the same namespace.

  • Infra:

    • docker-compose.yml has the emulator and SQL Server behind the azure-service-bus profile, on host ports 5673 (AMQP) and 5300, with pinned images and an empty-namespace config.
    • The new azure-service-bus CircleCI job runs the emulator with docker compose on a machine executor and waits on /health. The build job's integration run leaves this package out, and deploy requires both jobs.
  • Docs: /transports/azure-service-bus with its snippets and sidebar entry, plus rows in the provisioning, recoverability and middleware tables, the package README and CLAUDE.md, and the root README and CLAUDE.md.

  • Tests: unit tests over typemoq-mocked clients, and integration tests against the emulator: the shared suite, missing resources, a dry run, resyncing a subscription, headers, the size limit and replies. Every entity is cleaned up.

  • Not included: sessions, the follow-up the issue names.

  • This is original work under the clean-room policy, not ported, translated or copied from another messaging framework

  • Added a changeset (pnpm changeset) for user-facing changes to published packages, or none is needed

  • Docs: updated docs/ for user-facing changes, or none needed

🤖 Generated with Claude Code

@adenhertog
adenhertog force-pushed the issue-346-azure-service-bus branch 2 times, most recently from 6ebf661 to 149a25f Compare October 9, 2026 05:28
adenhertog and others added 3 commits October 9, 2026 15:48
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…us job

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@adenhertog
adenhertog force-pushed the issue-346-azure-service-bus branch from 149a25f to 53ea8af Compare October 9, 2026 05:50

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Azure Service Bus transport

1 participant