A TypeScript binding for the Common Expression Language (CEL) using cel-rust. This project provides a Node.js native module that allows you to use CEL in your TypeScript/JavaScript projects.
CEL is a familiar C/C++/Java/Python-like language for expressing boolean conditions, calculations, and variable substitutions. It's especially useful for policy enforcement, configuration validation, and business rule evaluation.
npm install @kevinmichaelchen/cel-typescript-core
Note
You'll need Node.js 18 or later. You'll also need to install one of several platform-specific packages:
Platform | Package |
---|---|
macOS ARM64 (Apple Silicon) | @kevinmichaelchen/cel-typescript-darwin-arm64 |
macOS x64 (Intel) | @kevinmichaelchen/cel-typescript-darwin-x64 |
Linux x64 | @kevinmichaelchen/cel-typescript-linux-x64-gnu |
Linux ARM64 | @kevinmichaelchen/cel-typescript-linux-arm64-gnu |
Windows x64 | @kevinmichaelchen/cel-typescript-win32-x64-msvc |
See the full language definition for a complete overview of CEL.
There are two ways to use CEL expressions in your code:
For simple use cases where you evaluate an expression once:
import { evaluate } from "@kevinmichaelchen/cel-typescript-core";
// Basic string and numeric operations
await evaluate(
// 1️⃣ Provide a CEL expression
"size(message) > 5",
// 2️⃣ Provide a context object
{ message: "Hello World" },
); // true
// Complex object traversal and comparison
await evaluate("user.age >= 18 && user.preferences.notifications", {
user: {
age: 25,
preferences: { notifications: true },
},
}); // true
For better performance when evaluating the same expression multiple times with different contexts:
import { CelProgram } from "@kevinmichaelchen/cel-typescript-core";
// Compile the expression once
const program = await CelProgram.compile(
"items.filter(i, i.price < max_price).size() > 0",
);
// Execute multiple times with different contexts
await program.execute({
items: [
{ name: "Book", price: 15 },
{ name: "Laptop", price: 1000 },
],
max_price: 100,
}); // true
await program.execute({
items: [
{ name: "Phone", price: 800 },
{ name: "Tablet", price: 400 },
],
max_price: 500,
}); // true
// Date/time operations using timestamp() macro
const timeProgram = await CelProgram.compile(
'timestamp(event_time) < timestamp("2025-01-01T00:00:00Z")',
);
await timeProgram.execute({
event_time: "2024-12-31T23:59:59Z",
}); // true
Tip
Consider pre-compiling expressions when:
- You evaluate the same expression repeatedly with different data
- You're building a rules engine or validator that reuses expressions
- You want to amortize the compilation cost across multiple evaluations
- Performance is critical in your application
For one-off evaluations or when expressions change frequently, the convenience
of evaluate()
likely outweighs the performance benefit of pre-compilation.
Evaluations are easily sub-millisecond, more on the order of hundreds of microseconds. Compiling is a bit more expensive, but still rarely exceeds a millisecond.
For more deatils, see
benchmark-results.md
.
The underlying cel-rust
library — and, by extension, this library — is more
feature-complete than all of its peers in the JS/TS ecosystem here.
See the cel-js-evaluation repository for a comparison of the features provided by this library versus its peers.
This project consists of three main components:
-
cel-rust: The underlying Rust implementation of the CEL interpreter, created by clarkmcc. This provides the core CEL evaluation engine.
-
NAPI-RS Bindings: A thin Rust layer that bridges cel-rust with Node.js using NAPI-RS. NAPI-RS is a framework for building pre-compiled Node.js addons in Rust, providing:
- Type-safe bindings between Rust and Node.js
- Cross-platform compilation support
- Automatic TypeScript type definitions generation
-
TypeScript Wrapper: A TypeScript API that provides a clean interface to the native module, handling type conversions and providing a more idiomatic JavaScript experience.
Note
Only ESM is supported by this package.
The native module is built using NAPI-RS and provides cross-platform support:
- Platform-specific builds are named
cel-typescript.<platform>-<arch>.node
- NAPI-RS generates a platform-agnostic loader that automatically
detects the current platform and loads the appropriate
.node
file - At runtime, the TypeScript wrapper uses the NAPI-RS loader to dynamically load the correct native module
- This structure allows for seamless cross-platform distribution while maintaining platform-specific optimizations
This project uses git submodules for its Rust dependencies. To get started:
# Clone with submodules
git clone --recurse-submodules https://github.com/clarkmcc/cel-typescript.git
# Or if you've already cloned the repository
git submodule update --init --recursive
After cloning:
npm install # Install dependencies
npm run build # Build the project
npm test # Run tests
[License information here]