Skip to main content

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:

StepWhat it checks
typecheckThe package and every sample type-check.
test:covnode:test + c8 with 100% statements, branches, functions, and lines enforced on the package source.
complexity:checkESLint + SonarJS cognitive-complexity threshold of 15 per source function.
complexity:reportGenerates the per-function complexity summary.
release:checkREADME link validation, sample-version sync, and package tarball validation.
security:auditA high-severity supply-chain audit.
ci:sampleRuns 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.

LegInstallsWhy this version
11 floor11.0.0, pinned exactlyThe 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.0The 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.