Repository navigation
add @node-ts/bus-nestjs, a NestJS module for the bus - #352
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tup errors Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…alize and dispose Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Oct 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #268
Summary
Adds
@node-ts/bus-nestjs, a NestJS module (NestJS 11 and 12) that registers the bus, resolves class handlers and workflows from Nest's container, and runs the bus from Nest's lifecycle hooks. The design was agreed on the issue: #268 (comment).Background
NestJS is the most common way enterprise TypeScript teams structure services. Until now, an app had to wire the bus into Nest itself, through
withContainer()and its own lifecycle code (#180).Problem
Nest apps had no supported way to:
The bus' own SIGINT/SIGTERM listeners also clash with Nest's shutdown hooks: they stop the bus, but can leave the process running.
Approach
Registering the bus
BusModule.forRoot({ configure })andforRootAsync({ imports, inject, useFactory })are global.Loggerand has no interrupt signals. It returns it with the transport, persistence and message types.BusModulethen adds the handlers, workflows and a container backed by Nest, and builds the bus.Registering handlers and workflows
@BusHandler()/@BusWorkflow()(built onDiscoveryService.createDecorator(), and typed so onlyHandler/Workflowclasses can be decorated) or by listing them inBusModule.forFeature().forFeatureonly registers: it never makes a class a provider.defineWorkflow()workflows are registered withforFeatureAsync(), whose factory is given the providers they need.Injecting the bus
BusInstance. Named buses use@InjectBus(name)/getBusToken(name).onModuleInit, because that's the first point where everyforFeatureAsyncfactory has run. Until then, providers get a stand-in that forwards to the bus once it's built.BusNotBuilt, or rejects with it for async methods likesend(). Its help says to use the bus fromonApplicationBootstrap()or later.Request scope
nestContainer(moduleRef)usesmoduleRef.getfor singletons.moduleRef.resolvein oneContextIdper received message, with{ message, attributes }registered asREQUEST(BusRequest).configureWorkflow(). If a request-scoped workflow fails then, startup fails withWorkflowResolvedWithoutMessage.Lifecycle
onApplicationBootstrap: initialize, then start if the newBusInstance.canStart(bus-core) is true. If either fails, the bus is disposed and the error rethrown.onModuleDestroy: stop.onApplicationShutdown: dispose.BusModuleis global, so Nest runs these after the same hook of every non-global module. The bus therefore keeps handling messages during other modules'onModuleDestroy, finishes them before anybeforeApplicationShutdown, and is disposed after other modules'onApplicationShutdown.beforeApplicationShutdown/onApplicationShutdown, or to uselifecycle: 'manual'and callbus.stop()beforeapp.close().lifecycle: 'manual'leaves initializing and starting to the app.Startup errors
BusClassNotProvided,BusNotRegistered,BusAlreadyRegistered,BusFeatureNotStatic,WorkflowResolvedWithoutMessageandBusCoreVersionNotSupported(when the bus-core peer is too old to havecanStart), each naming the fix.Provisioning
createBusForProvisioning(AppModule)boots the app with each bus built but never initialized.bus provisionnow also accepts an export that is a built bus, or a function returning one.bus.mjsexits once its output is flushed, so a Nest app's open handles don't keep the process alive.Tests
test.envalready ran Jest with--experimental-vm-modules(added for mongodb in support the outbox and inbox in bus-mongodb #345), so every existing suite already passes with it; its comment now mentions Nest too.guide/nestjspage with type-checked snippets (docs/tsconfig.jsonturns onexperimentalDecoratorsfor them), and a package README.Departure from the agreed design
configure/useFactoryare given the configuration to extend, rather than creating one. This means awithLogger()in the factory overrides Nest's logger, instead of being silently replaced.BusModulewarns that Nest's logger and shutdown handling are lost.Follow-ups
Pass a per-delivery key to ContainerAdapter.get, so request scopes aren't shared across retries #353: pass a per-delivery key to
ContainerAdapter.get, so the in-memory queue's retries don't share request scopes.Read configureWorkflow() without resolving the class workflow at registration #354: read
configureWorkflow()without resolving the class workflow.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 neededDocs: updated
docs/for user-facing changes, or none needed🤖 Generated with Claude Code