@sahina/cvt-sdk
v0.7.0
Published
Contract Validator Toolkit SDK
Readme
@sahina/cvt-sdk
The Node.js SDK for Contract Validator Toolkit (CVT) — a consumer-driven contract validation platform for OpenAPI v2/v3 specifications.
CVT validates HTTP request/response interactions against registered OpenAPI schemas using a gRPC service. This SDK provides:
- Schema registration and validation against OpenAPI v2/v3 specs
- HTTP adapters (Axios, Fetch) for automatic traffic validation
- Server-side middleware (Express, Fastify) for producer validation
- Breaking change detection between schema versions
- Consumer registry and deployment safety checks (can-i-deploy)
For full documentation, visit the CVT Documentation.
Installation
npm install @sahina/cvt-sdkUsage
1. Initialize and Register Schema
You can register a schema from a local file or a URL.
import { ContractValidator } from "@sahina/cvt-sdk";
import * as path from "path";
const validator = new ContractValidator();
// Register from local file
await validator.registerSchema(
"my-schema",
path.resolve(__dirname, "openapi.json"),
);
// OR Register from URL
await validator.registerSchema(
"petstore",
"https://petstore.swagger.io/v2/swagger.json",
);2. Validate Interactions
You can validate requests and responses. The validate method supports generic types for strong typing, or you can use it without types.
Strong Typing (Recommended)
import {
ContractValidator,
ValidationRequest,
ValidationResponse,
} from "@sahina/cvt-sdk";
interface User {
username: string;
email: string;
}
const request: ValidationRequest<User> = {
method: "POST",
path: "/users",
headers: { "content-type": "application/json" },
body: { username: "alice", email: "[email protected]" },
};
const response: ValidationResponse = {
statusCode: 201,
};
const result = await validator.validate<User>(request, response);
if (result.valid) {
console.log("✅ Valid interaction");
} else {
console.error("❌ Validation errors:", result.errors);
}Untyped Usage
const result = await validator.validate(
{
method: "GET",
path: "/pet/123",
},
{
statusCode: 200,
body: { id: 123, name: "Fluffy" },
},
);HTTP Adapter (Axios)
The SDK includes an Axios adapter for automatic HTTP traffic validation:
import axios from "axios";
import { ContractValidator } from "@sahina/cvt-sdk";
import { createAxiosAdapter } from "@sahina/cvt-sdk/adapters";
const validator = new ContractValidator();
await validator.registerSchema("petstore", "./openapi.json");
const api = axios.create({ baseURL: "https://api.example.com" });
// Auto-validate all requests
const adapter = createAxiosAdapter({
axios: api,
validator,
autoValidate: true,
excludePaths: ["/health", "/metrics"],
onValidationFailure: (result, request, response) => {
console.error("Validation failed:", result.errors);
},
});
// All requests are now automatically validated
const response = await api.post("/pets", { name: "Fluffy" });Adapter Options
autoValidate: Enable/disable automatic validation (default: true)includePaths: Array of paths/regex to includeexcludePaths: Array of paths/regex to excludeonValidationFailure: Custom error handlergetInteractions(): Retrieve captured interactionsclearInteractions(): Reset captured data
Producer Validation (Server-Side Middleware)
Validate incoming requests and outgoing responses against your OpenAPI contract on the server side.
Full documentation: See Validation Modes for detailed behavior, rollout strategy, and metrics information.
Validation Modes
| Mode | Request Violation | Response Violation | Use Case |
| ---------- | ----------------- | ------------------ | ---------------------- |
| "strict" | Reject with 400 | Log error | Production enforcement |
| "warn" | Log, continue | Log, continue | Gradual rollout |
| "shadow" | Metrics only | Metrics only | Initial deployment |
Recommended rollout: shadow → warn → strict. See Recommended Rollout Strategy.
Express Middleware
import { ContractValidator } from "@sahina/cvt-sdk";
import { createExpressMiddleware } from "@sahina/cvt-sdk/producer";
const validator = new ContractValidator();
await validator.registerSchema("my-api", "./openapi.json");
app.use(
createExpressMiddleware({
schemaId: "my-api",
validator,
mode: "strict",
excludePaths: ["/health", "/metrics"],
}),
);Fastify Plugin
import { fastifyProducerPlugin } from "@sahina/cvt-sdk/producer";
fastify.register(fastifyProducerPlugin, {
schemaId: "my-api",
validator,
mode: "strict",
});Configuration Options
| Option | Type | Description |
| ------------------- | ---------------- | ------------------------------------------------ |
| schemaId | string | Schema ID to validate against |
| validator | Validator | ContractValidator instance |
| mode | string | strict, warn, or shadow |
| validateRequest | boolean | Enable request validation (default: true) |
| validateResponse | boolean | Enable response validation (default: true) |
| excludePaths | PathFilter[] | Paths to skip validation (string or RegExp) |
| includePaths | PathFilter[] | Only validate matching paths (string or RegExp) |
| onRequestFailure | function | Called when request validation fails |
| onResponseFailure | function | Called when response validation fails |
Breaking Change Detection
Detect breaking changes between OpenAPI schema versions before deployment:
import { ContractValidator } from "@sahina/cvt-sdk";
const validator = new ContractValidator();
// Register both schema versions
await validator.registerSchemaWithVersion(
"my-api",
"./openapi-v1.json",
"1.0.0",
);
await validator.registerSchemaWithVersion(
"my-api",
"./openapi-v2.json",
"2.0.0",
);
// Compare versions
const result = await validator.compareSchemas("my-api", "1.0.0", "2.0.0");
if (!result.compatible) {
console.log("Breaking changes detected:");
result.breakingChanges.forEach((change) => {
console.log(`- [${change.type}] ${change.description}`);
if (change.path) {
console.log(` Path: ${change.method} ${change.path}`);
}
});
process.exit(1); // Fail CI build
}Breaking Change Types
| Type | Description |
| --------------------------- | ---------------------------------------------- |
| ENDPOINT_REMOVED | An endpoint was removed |
| REQUIRED_FIELD_ADDED | A required field was added to request |
| TYPE_CHANGED | A field's type was changed incompatibly |
| REQUIRED_PARAMETER_ADDED | A required query/path/header param was added |
| RESPONSE_SCHEMA_CHANGED | Response schema was changed incompatibly |
| ENUM_VALUE_REMOVED | An allowed enum value was removed |
See examples/breaking-changes.ts for a complete example.
Producer Testing
Test that your API handlers return responses matching your OpenAPI specification.
ProducerTestKit
import { ProducerTestKit } from "@sahina/cvt-sdk/producer";
const testKit = new ProducerTestKit({
schemaId: "user-api",
serverAddress: "localhost:9550",
});
// Validate handler response
const result = await testKit.validateResponse({
method: "GET",
path: "/users/123",
response: {
statusCode: 200,
body: { id: "123", name: "Alice", email: "[email protected]" },
},
});
expect(result.valid).toBe(true);
// Don't forget to close
testKit.close();Consumer Registry
Track which services depend on your API:
// Register a consumer after successful contract tests
await validator.registerConsumer({
consumerId: "order-service",
consumerVersion: "2.1.0",
schemaId: "user-api",
schemaVersion: "1.0.0",
environment: "prod",
usedEndpoints: [
{ method: "GET", path: "/users/{id}", usedFields: ["id", "email"] },
],
});
// List all consumers of a schema
const consumers = await validator.listConsumers("user-api", "prod");
// Deregister a consumer
await validator.deregisterConsumer("order-service", "user-api", "prod");Deployment Safety (can-i-deploy)
Check if a new schema version can be safely deployed:
const result = await validator.canIDeploy("user-api", "2.0.0", "prod");
if (!result.safeToDeploy) {
console.error("Cannot deploy:", result.summary);
for (const consumer of result.affectedConsumers) {
if (consumer.willBreak) {
console.error(`- ${consumer.consumerId} will break`);
}
}
process.exit(1);
}See Producer Testing Guide for complete documentation.
Security Configuration
TLS
const validator = new ContractValidator({
address: "localhost:9550",
tls: {
enabled: true,
rootCertPath: "./certs/ca.crt", // CA certificate
certPath: "./certs/client.crt", // For mTLS
keyPath: "./certs/client.key", // For mTLS
},
});API Key Authentication
const validator = new ContractValidator({
address: "localhost:9550",
apiKey: "your-api-key-here",
});Prerequisites
Ensure the CVT gRPC server is running (default: localhost:9550).
Testing
The Node.js SDK includes comprehensive tests covering:
- Client initialization and configuration
- Schema registration (local files and URLs)
- Validation requests and responses
- Error handling
- gRPC communication
Running Tests
# Install dependencies
npm install
# Run all tests
npm test
# Run tests with coverage
npm test -- --coverage
# Run specific test file
npm test -- ContractValidator.test.ts
# Run tests in watch mode
npm test -- --watchTest Structure
tests/
└── ContractValidator.test.ts # Main SDK test suiteWriting Tests
Example test using Jest:
import { ContractValidator } from "../src";
describe("ContractValidator", () => {
let validator: ContractValidator;
beforeEach(() => {
validator = new ContractValidator("localhost:9550");
});
afterEach(async () => {
await validator.close();
});
it("should validate a correct interaction", async () => {
await validator.registerSchema("test", "./openapi.json");
const result = await validator.validate(
{ method: "GET", path: "/users" },
{ statusCode: 200, body: [] },
);
expect(result.valid).toBe(true);
});
});Coverage
The SDK maintains 60%+ test coverage. Generate coverage reports with:
npm test -- --coverage
open coverage/lcov-report/index.htmlDevelopment
# Install dependencies
npm install
# Build the SDK
npm run build
# Run linter
npm run lint
# Format code
npm run format:check
# Run example
npm run example