tcomb: Runtime Type Checking & DDD
tcomb is a JavaScript library for runtime type checking and Domain-Driven Design (DDD), suitable for both Node.js and browser environments. It provides a concise syntax for defining and validating data structures, enhancing code safety during development. The current stable version is 3.2.29, with a recent cadence focused on bug fixes and TypeScript definition improvements. A key differentiator is its lightweight nature (3KB gzipped, no dependencies) and its foundation in set theory for type definition. It supports various type combinators (structs, lists, enums, refinements, unions), immutability helpers compatible with Facebook's Immutability Helpers, and runtime type introspection. Crucially, tcomb is designed to be *disabled in production* (its checks are stripped out for performance), with `io-ts` or `tcomb-validation` recommended for production-grade type validation.
Common errors
-
[tcomb] Invalid value "s" supplied to Number
cause Attempting to pass a value of an incorrect type (e.g., a string) to a tcomb type checker that expects a different type (e.g., Number).fixEnsure the value passed to the tcomb type check (e.g., `t.Number(value)`) matches the expected type. In TypeScript, this might indicate a mismatch between static and runtime types. -
[tcomb] Invalid value undefined supplied to Person/name: String
cause A required field in a tcomb struct was omitted or explicitly supplied with `undefined` when a non-optional type (e.g., `t.String` instead of `t.maybe(t.String)`) was expected.fixProvide a valid value for all required fields in the struct's constructor. For fields that can be `null` or `undefined`, define them using `t.maybe(Type)`.
Warnings
- breaking tcomb is explicitly designed to be disabled in production environments. All type checks and `Object.freeze` calls are stripped out when `process.env.NODE_ENV` is set to `'production'`, rendering it ineffective for production validation and potentially leading to unexpected behavior if relied upon.
- gotcha While tcomb ships with TypeScript definitions, its runtime checks are distinct from compile-time TypeScript checks. Using `babel-plugin-tcomb` allows for a more integrated experience with type annotations, but without it, runtime checks must be explicitly called (e.g., `t.Number(a)`).
- gotcha Instances created with tcomb structs are immutable by default via `Object.freeze` in development. Direct modification attempts on these objects will fail silently in non-strict mode or throw errors in strict mode.
Install
-
npm install tcomb -
yarn add tcomb -
pnpm add tcomb
Imports
- t (default export)
const t = require('tcomb');import t from 'tcomb';
- Type definitions (TypeScript)
import t from 'tcomb'; // Or for type inference: type MyStructType = t.TypeOf<typeof MyStructDefinition>;
- Specific combinator (e.g., struct)
import { struct } from 'tcomb';import t from 'tcomb'; const MyType = t.struct({ /* ... */ });
Quickstart
import t from 'tcomb';
// A type-checked function demonstrating basic validation
function sum(a, b) {
t.Number(a); // Runtime check: 'a' must be a Number
t.Number(b); // Runtime check: 'b' must be a Number
return a + b;
}
try {
sum(1, 's'); // This will throw an error
} catch (e) {
console.error(`Caught error for sum(1, 's'): ${e.message}`);
}
// Define a custom refined type (Integer)
const Integer = t.refinement(t.Number, (n) => n % 1 === 0, 'Integer');
// Define a structured object (a 'struct') for a Person
const Person = t.struct({
name: t.String, // required string
surname: t.maybe(t.String), // optional string (can be null or undefined)
age: Integer, // required integer (using our custom Integer type)
tags: t.list(t.String) // a list of strings
}, 'Person');
// Add a method to the struct's prototype (as you would with a class)
Person.prototype.getFullName = function () {
return `${this.name} ${this.surname ?? ''}`.trim();
};
let personInstance;
try {
personInstance = Person({
name: 'Guido',
age: 42,
tags: ['developer', 'js']
});
console.log(`Created person: ${personInstance.getFullName()}`);
} catch (e) {
console.error(`Caught error creating person: ${e.message}`);
}
// Attempt to create a person with missing required fields
try {
Person({
surname: 'Canti'
}); // This will throw an error for missing 'name' and 'age'
} catch (e) {
console.error(`Caught error for invalid person creation: ${e.message}`);
}
// Update an immutable instance using t.update
const updatedPerson = Person.update(personInstance, {
name: { $set: 'Giuliano' }, // Change name
age: { $set: 43 }, // Change age
tags: { $push: ['opensource'] } // Add a tag
});
console.log(`Updated person: ${updatedPerson.getFullName()}, Age: ${updatedPerson.age}, Tags: ${updatedPerson.tags.join(', ')}`);