Quality and CI
The package ships with the same quality bar as the rest of the nest-native
family. npm run ci runs the whole gate locally. CI runs the same steps as
separate jobs, every one of them on Node 22; only the package build and
typecheck job additionally runs on Node 24.
The Gate
npm run ci chains:
| Step | What it checks |
|---|---|
typecheck | The package and every sample type-check. |
test:cov | node:test + c8 with 100% statements, branches, functions, and lines enforced on the package source. |
complexity:check | ESLint + SonarJS cognitive-complexity threshold of 15 per source function. |
complexity:report | Generates the per-function complexity summary. |
release:check | README link validation, sample-version sync, and package tarball validation. |
security:audit | A high-severity supply-chain audit. |
ci:sample | Runs every sample (typecheck + smoke) against the in-memory broker. |
Coverage
Coverage is enforced at 100% on all four metrics. Every branch — every ??, every
option path, every error path — has a test. The CI posts a sticky coverage comment
on each pull request.
Cognitive Complexity
SonarJS enforces a cognitive-complexity threshold of 15 per source function.
Complexity is never reduced by weakening the Nest-native architecture, the public
API clarity, rebalance safety, or test coverage.
Release Version Synchronization
Version drift between packages/kafka and sample/* is a release blocker. When
the package version bumps, every sample/*/package.json entry for
@nest-native/kafka updates in the same change, package-lock.json is
regenerated, and release:check validates the sync. See the Release Guide.
NestJS Compatibility Matrix
The published peer range is ^11.0.0 || ^12.0.0, but the devDependencies and
the lockfile stay on an 11.x in the middle of it, so the default suite tests
neither end. The nestjs-compat job is a matrix with one leg per end. Each leg
installs @nestjs/common, @nestjs/core, @nestjs/microservices, and
@nestjs/testing at that end on top of the lockfile
(npm install --no-save --workspaces --include-workspace-root, so the samples
move too instead of keeping a nested 11) and then runs the unit suite, the
package build, and the sample matrix.
| Leg | Installs | Why this version |
|---|---|---|
11 floor | 11.0.0, pinned exactly | The oldest graph the range can produce. Nothing this package uses was added by a later 11.x: every @nestjs/core internal it deep-imports exists with the same signature at 11.0.0. An exact pin means a downgrade that silently no-ops fails instead of passing as "still 11". |
12 | ^12.0.0 | The newest end; floats so a new 12.x patch is tested on the next run. |
Before a leg runs anything, scripts/check-nestjs-resolution.mjs proves the
tree is the one it claims to test, because npm makes it easy to end up with
another one:
- it resolves the framework packages from inside every workspace and requires exactly the leg's version, from the hoisted root copy — a nested copy fails even when its version is right;
- it checks every peer range in the NestJS ecosystem — every installed package
at any depth that is
@nestjs/*or peers on one, this package's own published range included — against the tree the suite will run on.
That final-tree check is the gate because npm's own signal is not one: a peer
conflict npm can override produces npm warn ERESOLVE overriding peer dependency and exit 0, neither npm ls nor --strict-peer-deps reports it
afterwards, and the same warning appears for transitional states that end
coherent, so grepping the install log for it is a false-positive class rather
than a gate.
The script also runs with no argument as part of release:check, against the
lockfile, so a lockfile that drifts from what the workspaces declare is a
release blocker. Both ends of the range are tested claims; the floor is an
install-graph fact (the oldest versions npm can actually produce together),
which is why it is pinned with its reason next to it rather than inferred from
the peer string.
NestJS 12 is ESM-only with an exports map, under which a deep import of a
directory inside @nestjs/* no longer resolves. The unit suite includes a
guard (packages/kafka/test/nestjs-deep-imports.spec.ts) that scans every
@nestjs/<pkg>/<subpath> import in the package and requires the subpath to be
a file, so the trap cannot come back on the 11.x install where it is invisible.
Driver-Backed Integration
A dedicated integration CI job stands up a single-node KRaft Kafka
(apache/kafka), once per end of the tested broker range (a 3.x and a 4.x
release; the exact versions are in the Support Policy),
and runs npm run test:integration against it with
KAFKA_BROKERS=localhost:9092. That suite (packages/kafka/test/kafka.integration.spec.ts)
opens a real connection through createConfluentDriver and the native
@confluentinc/kafka-javascript client to prove the behaviour the in-memory
broker cannot: a real produce → consume round-trip, a transactional commit via
KafkaProducerService.transactional, per-topic concurrency with durable
offset commits (a fresh consumer in the same group is not redelivered
already-committed messages), redelivery of a batch whose handler failed
with 'retry', a graceful shutdown in the middle of a stream after which
the next member of the group receives every record the first one did not
process, a retry backoff whose delay grows while a sibling partition keeps
flowing on the same worker, a dead letter whose binary kafka_dlt-*
headers decode after the round trip, a pattern subscription that picks
up a matching topic created while the application runs, and a health
indicator that reports a frozen broker down within its timeout and up once it
answers again — the in-memory broker has no commit log, never
redelivers and never pauses, so only a real broker can show any of it. Every topic and group name is unique per run.
The suite is gated on KAFKA_BROKERS: it is skipped when the variable is
unset, so it never runs during npm run test:cov and the 100% coverage gate is
unaffected. npm run test:integration is separate from the ci script and is
not part of the standard local gate — the in-memory broker covers the same
transport logic without native dependencies, so the rest of the suite runs
anywhere.
@confluentinc/kafka-javascript stays an optional peer (the published
package keeps "dependencies": {}), so the integration job installs it
on-demand with npm i --no-save and never persists it to package.json or the
lockfile.
Supply Chain
The published package's "dependencies" block must stay empty; runtime
requirements are peers and build tools are devDependencies. Every dependency
change is reviewed for legitimacy, and install/lifecycle scripts are inspected.
Unpinned Git/URL dependencies are flagged. See Contributing.