Thank you for your interest in contributing to amqp-contract!
- Node.js >= 22.19 (enforced via
engines;.node-versionpins the version used in CI) - pnpm 11.7.0 — the repo pins it via the
packageManagerfield, socorepack enablegives you the right version automatically - Docker running (not just installed) — required only for
pnpm test:integration, which spins up RabbitMQ via testcontainers
- Install dependencies:
pnpm install- Build all packages:
pnpm build- Run tests:
# Run unit tests
pnpm test
# Run integration tests (requires Docker)
pnpm test:integration# Rebuild packages on change (tsdown --watch)
pnpm dev
# Run one package's unit tests
pnpm --filter @amqp-contract/worker test
# Run a single test file (from the package directory)
cd packages/worker && pnpm vitest run --project unit src/retry.spec.ts
# Type check everything
pnpm typecheckImportant
Two gotchas worth knowing up front:
pnpm typecheckis not in the pre-commit hook. Lefthook only runsoxfmtandoxlinton commit, so runpnpm typecheck(andpnpm test) yourself before pushing.- Packages typecheck against each other's
dist/output, notsrc/. If you change a public type in package A, rebuild it (pnpm --filter @amqp-contract/<a> build) before typechecking a package that depends on it — otherwise you'll see stale, confusing type errors.
This project uses a integration-first testing approach that prioritizes testing against real RabbitMQ instances over mocked unit tests.
- Test against real RabbitMQ instances using testcontainers
- Each test runs in an isolated vhost for complete test isolation
- Located alongside source files:
src/*.integration.spec.ts - Run with:
pnpm test:integration - Preferred for testing AMQP behavior, message flow, and contract setup
Example packages with integration tests:
packages/core- 16 integration tests (AmqpClient, connection sharing)packages/client- 10 integration tests (publishing, validation, topology)packages/worker- 9 integration tests (consuming, error handling, bindings)
- Test pure logic without external dependencies
- No mocking of AMQP libraries
- Located alongside source files:
src/*.unit.spec.ts - Run with:
pnpm test - Only used for testing pure functions, utilities, and simple logic
Example packages with unit tests:
packages/core- 4 unit tests (logger utility)
✅ More Robust: Tests validate actual AMQP behavior, not mocked assumptions ✅ Catch Real Issues: Detects problems with RabbitMQ integration that unit tests miss ✅ Less Brittle: No complex mock setup that breaks with implementation changes ✅ Better Confidence: Higher assurance that code works in production
# Run all unit tests (fast, no Docker needed)
pnpm test
# Run integration tests for a specific package (requires Docker)
pnpm test:integration --filter @amqp-contract/core
pnpm test:integration --filter @amqp-contract/client
pnpm test:integration --filter @amqp-contract/worker
# Run all integration tests (requires Docker)
pnpm test:integrationFor new AMQP features:
- Write integration tests using
@amqp-contract/testing/extension - Use test fixtures:
amqpConnectionUrl,amqpChannel,publishMessage,initConsumer - Place tests next to source:
feature.integration.spec.ts
For pure utility functions:
- Write unit tests without external dependencies
- Place tests next to source:
utility.unit.spec.ts
Example integration test:
import { it } from "@amqp-contract/testing/extension";
import { defineContract, defineExchange } from "@amqp-contract/contract";
import { AmqpClient } from "@amqp-contract/core";
describe("Feature Integration", () => {
it("should setup exchange", async ({ amqpConnectionUrl, amqpChannel }) => {
// GIVEN
const contract = defineContract({
exchanges: {
test: defineExchange("test", { durable: false }),
},
});
// WHEN
const client = new AmqpClient(contract, { urls: [amqpConnectionUrl] });
(await client.waitForConnect()).unwrap();
// THEN
await expect(amqpChannel.checkExchange("test")).resolves.toBeDefined();
// CLEANUP
await client.close();
});
});packages/contract- Contract definition builderpackages/client- Type-safe AMQP clientpackages/worker- Type-safe AMQP workerpackages/asyncapi- AsyncAPI specification generatorexamples/- Example implementations
📋 Read the complete coding guidelines
This project uses AI-assisted code review with GitHub Copilot. Our guidelines document:
- TypeScript & type safety requirements
- AMQP/RabbitMQ patterns & best practices
- Code style & formatting rules
- Testing conventions
- Error handling patterns
We follow Conventional Commits:
feat:- New featuresfix:- Bug fixesdocs:- Documentation changeschore:- Maintenance taskstest:- Test changesrefactor:- Code refactoring
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass:
pnpm test - Ensure code is formatted:
pnpm format - Ensure code passes linting:
pnpm lint - Add a changeset describing your change (see below)
- Submit a pull request
This project uses Changesets for version management and publishing. Every change that affects a published package needs a changeset; the release workflow turns those into version bumps, changelogs, and npm releases.
pnpm changesetThe CLI is interactive:
- Pick the affected packages.
- Choose a bump level (
patch/minor/major) per package, following Semantic Versioning. - Write a short, user-facing summary — this becomes the changelog entry.
The result is a new file under .changeset/. Commit it alongside your code changes. PRs without a changeset (when one is needed) won't be released even if merged.
If your PR doesn't change anything published — e.g. tests, docs, repo tooling — you don't need one.
Releases are driven by GitHub Actions:
- On every push to
main: the release workflow runschangeset versionagainst the accumulated changesets. It opens (or updates) a "Version Packages" PR that bumpspackage.jsonversions and updates each package'sCHANGELOG.md. - When the Version Packages PR is merged: the same workflow runs
changeset publish, tagging the release and pushing the bumped packages to npm.
Manual steps to release locally (rarely needed):
pnpm changeset version # consume changesets, bump versions, update changelogs
pnpm release # builds and publishes via `changeset publish`Both require write access to the npm org and the appropriate RELEASE_PAT for tagging.
- Public APIs follow SemVer.
- Breaking changes to the contract type system count as
major. Be conservative. - Bug fixes that change behavior in a way users could rely on (even unintentionally) deserve at least a
minorand a changelog note explaining the change. - Internal refactors with no surface change can ship as
patch.
Feel free to open an issue for any questions or concerns.