{"id":28868243,"url":"https://github.com/marcbachmann/cel-js","last_synced_at":"2026-02-16T19:19:16.865Z","repository":{"id":298838433,"uuid":"1001277097","full_name":"marcbachmann/cel-js","owner":"marcbachmann","description":"A lightweight, zero-dependency implementation of the Common Expression Language (CEL) in JavaScript.","archived":false,"fork":false,"pushed_at":"2026-01-25T21:17:16.000Z","size":796,"stargazers_count":97,"open_issues_count":1,"forks_count":9,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-26T13:25:30.492Z","etag":null,"topics":["cel","cel-go","cel-js","common","common-expression-language","expression","javascript","language"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@marcbachmann/cel-js","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marcbachmann.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-13T05:44:49.000Z","updated_at":"2026-01-26T05:41:21.000Z","dependencies_parsed_at":"2025-12-31T07:00:12.831Z","dependency_job_id":null,"html_url":"https://github.com/marcbachmann/cel-js","commit_stats":null,"previous_names":["marcbachmann/cel-js"],"tags_count":76,"template":false,"template_full_name":null,"purl":"pkg:github/marcbachmann/cel-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcbachmann%2Fcel-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcbachmann%2Fcel-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcbachmann%2Fcel-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcbachmann%2Fcel-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcbachmann","download_url":"https://codeload.github.com/marcbachmann/cel-js/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcbachmann%2Fcel-js/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28857091,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-28T22:56:21.783Z","status":"ssl_error","status_checked_at":"2026-01-28T22:56:00.861Z","response_time":57,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cel","cel-go","cel-js","common","common-expression-language","expression","javascript","language"],"created_at":"2025-06-20T12:36:51.376Z","updated_at":"2026-02-16T19:19:16.852Z","avatar_url":"https://github.com/marcbachmann.png","language":"JavaScript","readme":"# @marcbachmann/cel-js [![npm version](https://img.shields.io/npm/v/@marcbachmann/cel-js.svg)](https://www.npmjs.com/package/@marcbachmann/cel-js) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA high-performance, zero-dependency implementation of the [Common Expression Language (CEL)](https://github.com/google/cel-spec) in JavaScript.\n\n🚀 Use the [CEL JS Playground](https://playceljs.sksop.in/) to test expressions.\n\n## Overview\n\nCEL (Common Expression Language) is a non-Turing complete language designed for simplicity, speed, safety, and portability. This JavaScript implementation provides a fast, lightweight CEL evaluator perfect for policy evaluation, configuration, and embedded expressions.\n\n## Features\n\n- 🚀 **Zero Dependencies** - No external packages required\n- ⚡ **High Performance** - Up to 22x faster evaluation, 3x faster parsing than alternatives\n- 📦 **ES Modules** - Modern ESM with full tree-shaking support\n- 🔒 **Type Safe** - Environment API with type checking for variables, custom types and functions\n- 🎯 **Most of the CEL Spec** - Including macros, custom functions and types, optional chaining, input variables, and all operators\n- 📘 **TypeScript Support** - Full type definitions included\n\n## Installation\n\n```bash\nnpm install @marcbachmann/cel-js\n```\n\n## Quick Start\n\n```javascript\nimport {evaluate} from '@marcbachmann/cel-js'\n\n// Simple evaluation\nevaluate('1 + 2 * 3') // 7n\n\n// With context\nconst allowed = evaluate(\n  'user.age \u003e= 18 \u0026\u0026 \"admin\" in user.roles',\n  {user: {age: 30, roles: ['admin', 'user']}}\n)\n// true\n```\n\n## API\n\n### Simple Evaluation\n\n```javascript\nimport {evaluate, parse} from '@marcbachmann/cel-js'\n\n// Direct evaluation\nevaluate('1 + 2') // 3n\n\n// With variables\nevaluate('name + \"!\"', {name: 'Alice'}) // \"Alice!\"\n\n// Parse once, evaluate multiple times for better performance\nconst expr = parse('user.age \u003e= minAge')\nexpr({user: {age: 25}, minAge: 18}) // true\nexpr({user: {age: 16}, minAge: 18}) // false\n\n// Access parsed AST and type checking\nconsole.log(expr.ast)        // AST representation\nconst typeCheck = expr.check() // Type check without evaluation\n```\n\n### Environment API (Recommended)\n\nFor type-safe expressions with custom functions and operators:\n\n```javascript\nimport {Environment} from '@marcbachmann/cel-js'\n\nconst env = new Environment()\n  .registerVariable('skipAgeCheck', 'bool')\n  .registerVariable('user', {\n    schema: {\n      email: 'string',\n      age: 'int',\n    }\n  })\n  .registerConstant('minAge', 'int', 18n)\n  .registerFunction('isAdult(int): bool', age =\u003e age \u003e= 18n)\n  .registerOperator('string * int', (str, n) =\u003e str.repeat(Number(n)))\n\n// Type-checked evaluation with constant\nenv.evaluate('isAdult(user.age) \u0026\u0026 (user.age \u003e= minAge || skipAgeCheck)', {\n  user: {age: 25n}, skipAgeCheck: true\n})\n\n// Custom operators\nenv.evaluate('\"Hi\" * 3') // \"HiHiHi\"\n```\n\n#### Register constants\n\nUse `registerConstant` to expose shared configuration without passing it through every evaluation context.\n\n```javascript\nimport {Environment} from '@marcbachmann/cel-js'\n\nconst env = new Environment()\n  .registerConstant('minAge', 'int', 18n)\n\nenv.evaluate('user.age \u003e= minAge', {user: {age: 20n}}) // true\n```\n\nSupported signatures:\n\n```javascript\nenv.registerConstant('minAge', 'int', 18n)\nenv.registerConstant({name: 'minAge', type: 'int', value: 18n, description: 'Minimum age'})\n```\n\n#### Environment Options\n\n```javascript\nnew Environment({\n  // Treat undeclared variables as dynamic type\n  unlistedVariablesAreDyn: false,\n  // Require list/map literals to stay strictly homogeneous (default: true)\n  homogeneousAggregateLiterals: true,\n  // Enable .?key/.[?key] optional chaining and optional.* helpers (default: false)\n  enableOptionalTypes: true,\n  // Optional structural limits (parse time)\n  limits: {\n    maxAstNodes: 100000,\n    maxDepth: 250,\n    maxListElements: 1000,\n    maxMapEntries: 1000,\n    maxCallArguments: 32\n  }\n})\n```\n\n- Set `homogeneousAggregateLiterals` to `false` if you need aggregate literals to accept mixed element/key/value types without wrapping everything in `dyn(...)`.\n- Set `enableOptionalTypes` to `true` to activate optional chaining.\n\n#### Environment Methods\n\n- **`registerVariable(name, type)`** - Declare a variable with type checking\n- **`registerType(typename, constructor)`** - Register custom types\n- **`registerFunction(signature, handler)`** - Add custom functions\n- **`registerOperator(signature, handler)`** - Add custom operators\n- **`registerConstant(name, type, value)`** - Provide immutable values without passing them in context\n- **`clone()`** - Create an isolated copy. Call that stops the parent from registering more entries.\n- **`hasVariable(name)`** - Check if variable is registered\n- **`parse(expression)`** - Parse expression for reuse\n- **`evaluate(expression, context)`** - Evaluate with context\n- **`check(expression)`** - Validate expression types without evaluation\n- **`getDefinitions()`** - Returns all registered variables and functions with their types, signatures, and descriptions\n\n#### `registerVariable` signatures\n\n```javascript\nenv.registerVariable('user', 'map')\nenv.registerVariable('user', 'map', {description: 'The current user'})\nenv.registerVariable('user', {type: 'map', description: 'The current user'})\nenv.registerVariable({name: 'user', type: 'map', description: 'The current user'})\n\n// Passing a schema property will implicitly create a custom type and\n// convert objects/maps to a new class instance. Those values then behave similar like\n// explicit types that are created using a constructor.\nenv.registerVariable({\n  name: 'user',\n  schema: {\n    email: 'string',\n    age: 'int',\n    profile: {\n      tags: 'list\u003cstring\u003e',\n      avatar: 'string'\n    }\n  }\n})\n```\n\nThe `type` can be a type string (e.g. `'int'`, `'map'`, `'list\u003cstring\u003e'`) or a `TypeDeclaration` obtained from another environment.\n\n#### `registerType` signatures\n\n```javascript\n// Name + constructor class\n// when fields are not provided, own properties are accessible automatically\nenv.registerType('Vector', Vector)\n\n// Name + object with constructor and field types\nenv.registerType('Vector', {ctor: Vector, fields: {x: 'double', y: 'double'}})\n\n// Name + object with fields only (auto-generates a wrapper class and convert function)\nenv.registerType('Vector', {fields: {x: 'double', y: 'double'}})\n\n// Name + object with nested schema (registers nested types automatically)\nenv.registerType('Vector', {schema: {x: 'double', y: 'double'}})\n\n// Single object with name and schema\nenv.registerType({name: 'Vector', schema: {x: 'double', y: 'double'}})\n\n// Single object with constructor (name inferred from constructor)\nenv.registerType({ctor: Vector, fields: {x: 'double', y: 'double'}})\n```\n\nWhen `fields` or `schema` is provided without a `ctor`, an internal wrapper class is auto-generated and plain objects are automatically converted at runtime. A custom `convert` function can be passed to override this default conversion.\n\nWhen using the `schema` declaration, we're creating a new Map instance for the specific type when retrieving the values by variable from a context object.\n\n#### `registerFunction` signatures\n\n```javascript\n// Signature string + handler\nenv.registerFunction('greet(string): string', (name) =\u003e `Hello, ${name}!`)\nenv.registerFunction('greet(string): string', handler, {description: 'Greets someone'})\nenv.registerFunction('greet(string): string', {handler, description: 'Greets someone'})\n\n// Single object with signature string\nenv.registerFunction({signature: 'add(int, int): int', handler, description: 'Adds two integers'})\n\n// Single object with signature string and named params\nenv.registerFunction({\n  signature: 'formatDate(int, string): string',\n  handler,\n  description: 'Formats a timestamp',\n  params: [\n    {name: 'timestamp', description: 'Unix timestamp in seconds'},\n    {name: 'format', description: 'Date format string'}\n  ]\n})\n\n// Single object without signature string\nenv.registerFunction({\n  name: 'multiply',\n  returnType: 'int',\n  handler: (a, b) =\u003e a * b,\n  description: 'Multiplies two integers',\n  params: [\n    {name: 'a', type: 'int', description: 'First number'},\n    {name: 'b', type: 'int', description: 'Second number'}\n  ]\n})\n\n// Receiver method (called as 'hello'.shout())\nenv.registerFunction({\n  name: 'shout',\n  receiverType: 'string',\n  returnType: 'string',\n  handler: (str) =\u003e str.toUpperCase() + '!',\n  params: []\n})\n```\n\n#### `registerFunction` (sync \u0026 async)\n\n`registerFunction(signature, handler)` accepts both synchronous and async handlers. When an async function (or a macro predicate/transform that uses async functions) participates in an expression, `env.evaluate()` returns a `Promise` that resolves with the final value. Consumers should `await` those evaluations when they register async behavior:\n\n```javascript\nconst env = new Environment()\n  .registerFunction('fetchUser(string): map', async (id) =\u003e {\n    const res = await fetch(`/users/${id}`)\n    return res.json()\n  })\n\nconst user = await env.evaluate('fetchUser(userId)', {userId: '42'})\n```\n\nAsync handlers are primarily intended for latency-sensitive lookups (e.g., cache fetches, lightweight RPC). CEL’s goal is still deterministic, predictable evaluation, so avoid building expressions that trigger unbounded async work (like nested loops within macros or large fan-out requests) even though the engine will await those results.\n\n#### Environment Cloning\n\n```javascript\nimport assert from 'node:assert/strict'\n\nconst parent = new Environment().registerVariable('user', 'map')\nconst child = parent.clone()\n\n// Parent registries is frozen once cloned\nassert.throws(() =\u003e parent.registerVariable('foo', 'dyn'))\n\n// Child stays fully extensible without deep-copy overhead\nchild\n  .registerFunction('isAdult(map): bool', (u) =\u003e u.age \u003e= 18n)\n  .registerVariable('minAge', 'int')\n\nchild.evaluate('isAdult(user) \u0026\u0026 user.age \u003e= minAge', {\n  user: {age: 20n},\n  minAge: 18n\n})\n```\n\n**Supported Types:** `int`, `uint`, `double`, `string`, `bool`, `bytes`, `list`, `map`, `timestamp`, `duration`, `null_type`, `type`, `dyn`, or custom types\n\n### Type Checking\n\nValidate expressions before evaluation to catch type errors early:\n\n```javascript\nimport {Environment, TypeError} from '@marcbachmann/cel-js'\n\nconst env = new Environment()\n  .registerVariable('age', 'int')\n  .registerVariable('name', 'string')\n\n// Check expression validity\nconst result = env.check('age \u003e= 18 \u0026\u0026 name.startsWith(\"A\")')\n\nif (result.valid) {\n  console.log(`Expression is valid, returns: ${result.type}`) // bool\n  // Safe to evaluate\n  const value = env.evaluate('age \u003e= 18 \u0026\u0026 name.startsWith(\"A\")', {\n    age: 25n,\n    name: 'Alice'\n  })\n} else {\n  console.error(`Type error: ${result.error.message}`)\n}\n\n// Detect errors without evaluation\nconst invalid = env.check('age + name') // Invalid: can't add int + string\nconsole.log(invalid.valid) // false\nconsole.log(invalid.error.message) // \"Operator '+' not defined for types 'int' and 'string'\"\n```\n\n**Benefits:**\n- Catch type mismatches before runtime\n- Validate user-provided expressions safely\n- Get inferred return types for expressions\n- Better error messages with source location\n\n## Language Features\n\n### Operators\n\n```javascript\n// Arithmetic\nevaluate('10 + 5 - 3')     // 12n\nevaluate('10 * 5 / 2')     // 25n\nevaluate('10 % 3')         // 1n\n\n// Comparison\nevaluate('5 \u003e 3')          // true\nevaluate('5 \u003e= 5')         // true\nevaluate('5 == 5')         // true\nevaluate('5 != 4')         // true\n\n// Logical\nevaluate('true \u0026\u0026 false')  // false\nevaluate('true || false')  // true\nevaluate('!false')         // true\n\n// Ternary\nevaluate('5 \u003e 3 ? \"yes\" : \"no\"')  // \"yes\"\n\n// Membership\nevaluate('2 in [1, 2, 3]')        // true\nevaluate('\"ell\" in \"hello\"')      // true\n```\n\n### Data Types\n\n```javascript\n// Numbers (default to BigInt)\nevaluate('42')           // 42n\nevaluate('3.14')         // 3.14\nevaluate('0xFF')         // 255n\n\n// Strings\nevaluate('\"hello\"')      // \"hello\"\nevaluate('r\"\\\\n\"')       // \"\\\\n\" (raw string)\nevaluate('\"\"\"multi\\nline\"\"\"')  // \"multi\\nline\\n\"\n\n// Bytes\nevaluate('b\"hello\"')     // Uint8Array\nevaluate('b\"\\\\xFF\"')     // Uint8Array [255]\n\n// Collections\nevaluate('[1, 2, 3]')           // [1n, 2n, 3n]\nevaluate('{name: \"Alice\"}')     // {name: \"Alice\"}\n\n// Other\nevaluate('true')         // true\nevaluate('null')         // null\n```\n\n### Built-in Functions\n\n```javascript\n// Type conversion\nevaluate('string(123)')           // \"123\"\nevaluate('int(\"42\")')             // 42n\nevaluate('double(\"3.14\")')        // 3.14\nevaluate('bytes(\"hello\")')        // Uint8Array\nevaluate('dyn(42)')               // Converts to dynamic type\n\n// Collections\nevaluate('size([1, 2, 3])')       // 3n\nevaluate('size(\"hello\")')         // 5n\nevaluate('size({a: 1, b: 2})')    // 2n\n\n// Time\nevaluate('timestamp(\"2024-01-01T00:00:00Z\")')  // Date\n\n// Type checking\nevaluate('type(42)')              // int\nevaluate('type(\"hello\")')         // string\n```\n\n### String Methods\n\n```javascript\nevaluate('\"hello\".contains(\"ell\")')         // true\nevaluate('\"hello\".startsWith(\"he\")')        // true\nevaluate('\"hello\".endsWith(\"lo\")')          // true\nevaluate('\"hello\".matches(\"h.*o\")')         // true\nevaluate('\"hello\".size()')                  // 5n\nevaluate('\"hello\".indexOf(\"ll\")')           // 2n\nevaluate('\"hello world\".indexOf(\"o\", 5)')   // 7n (search from index 5)\nevaluate('\"hello\".lastIndexOf(\"l\")')        // 3n\nevaluate('\"hello\".substring(1)')            // \"ello\"\nevaluate('\"hello\".substring(1, 4)')         // \"ell\"\n```\n\n### List Methods\n\n```javascript\nevaluate('[1, 2, 3].size()')                // 3n\nevaluate('[\"a\", \"b\", \"c\"].join()')          // \"abc\"\nevaluate('[\"a\", \"b\", \"c\"].join(\", \")')      // \"a, b, c\"\n```\n\n### Bytes Methods\n\n```javascript\nevaluate('b\"hello\".size()')                 // 5n\nevaluate('b\"hello\".string()')               // \"hello\"\nevaluate('b\"hello\".hex()')                  // \"68656c6c6f\"\nevaluate('b\"hello\".base64()')               // \"aGVsbG8=\"\nevaluate('b\"{\\\\\"x\\\\\": 42}\".json()')         // {x: 42n}\nevaluate('b\"hello\".at(0)')                  // 104n (byte value at index)\n```\n\n### Timestamp Methods\n\nAll timestamp methods support an optional timezone parameter (e.g., `\"America/New_York\"`, `\"UTC\"`):\n\n```javascript\nconst ctx = {t: new Date('2024-01-15T14:30:45.123Z')}\n\nevaluate('t.getFullYear()', ctx)            // 2024n\nevaluate('t.getMonth()', ctx)               // 0n (January, 0-indexed)\nevaluate('t.getDayOfMonth()', ctx)          // 15n\nevaluate('t.getDayOfWeek()', ctx)           // 1n (Monday, 0=Sunday)\nevaluate('t.getDayOfYear()', ctx)           // 15n\nevaluate('t.getHours()', ctx)               // 14n\nevaluate('t.getMinutes()', ctx)             // 30n\nevaluate('t.getSeconds()', ctx)             // 45n\nevaluate('t.getMilliseconds()', ctx)        // 123n\n\n// With timezone\nevaluate('t.getHours(\"America/New_York\")', ctx)  // 9n (UTC-5)\n```\n\n### Macros\n\n```javascript\nconst ctx = {\n  numbers: [1, 2, 3, 4, 5],\n  users: [\n    {name: 'Alice', admin: true},\n    {name: 'Bob', admin: false}\n  ]\n}\n\n// Check property exists\nevaluate('has(user.email)', {user: {}})  // false\n\n// All elements match\nevaluate('numbers.all(n, n \u003e 0)', ctx)   // true\n\n// Any element matches\nevaluate('numbers.exists(n, n \u003e 3)', ctx)  // true\n\n// Exactly one matches\nevaluate('numbers.exists_one(n, n == 3)', ctx)  // true\n\n// Transform\nevaluate('numbers.map(n, n * 2)', ctx)\n// [2n, 4n, 6n, 8n, 10n]\n\n// Filter\nevaluate('numbers.filter(n, n \u003e 2)', ctx)\n// [3n, 4n, 5n]\n\n// Filter + Transform\nevaluate('users.filter(u, u.admin).map(u, u.name)', ctx)\n\n// Bind a temporary value within the expression\nevaluate('cel.bind(total, users.map(u, u.admin, u.score).sum(), total \u003e= 90)', ctx)\n\n// Or using three arg form of .map\nevaluate('users.map(u, u.admin, u.name)', ctx)\n// [\"Alice\"]\n```\n\n#### Custom macros\nYou can register your own macros by declaring overloads that accept `ast` arguments. The macro handler executes at parse time and must return an object that provides both `typeCheck` and `evaluate` hooks; these hooks are invoked later during `env.check()` and `env.evaluate()` so the macro lines up with the regular type-checker/evaluator pipeline.\n\n```javascript\nimport {Environment} from '@marcbachmann/cel-js'\n\nconst env = new Environment()\nenv.registerFunction('macro(ast): dyn', ({ast, args}) =\u003e {\n  // Any parameter on this object are available as\n  // the `macro` parameter within the `typeCheck` and `evaluate` functions below.\n  return {\n    // e.g. you can precompute values during parse time\n    firstArgument: args[0],\n    // Mandatory: called when the expression is type-checked\n    typeCheck(checker, macro, ctx) {\n      return checker.check(macro.firstArgument, ctx)\n    },\n    // Mandatory: called when the expression is evaluated\n    evaluate(evaluator, macro, ctx) {\n      return evaluator.eval(macro.firstArgument, ctx)\n    }\n  }\n})\n```\n\n### Custom Types\n\n```javascript\nimport {Environment} from '@marcbachmann/cel-js'\n\nclass Vector {\n  constructor(x, y) {\n    this.x = x\n    this.y = y\n  }\n  add(other) {\n    return new Vector(this.x + other.x, this.y + other.y)\n  }\n}\n\nconst env = new Environment()\n  .registerType('Vector', Vector)\n  .registerVariable('v1', 'Vector')\n  .registerVariable('v2', 'Vector')\n  .registerOperator('Vector + Vector', (a, b) =\u003e a.add(b))\n  .registerFunction('magnitude(Vector): double', (v) =\u003e\n    Math.sqrt(v.x * v.x + v.y * v.y)\n  )\n\nconst result = env.evaluate('magnitude(v1 + v2)', {\n  v1: new Vector(3, 4),\n  v2: new Vector(1, 2)\n})\n// 7.211102550927978\n```\n\n## Performance\n\nBenchmark results comparing against the `cel-js` package on Node.js v24.8.0 (Apple Silicon):\n\n### Parsing Performance\n- **Average: 3.1x faster** (range: 0.76x - 14.8x)\n- Simple expressions: **7-15x faster**\n- Array/Map creation: **8-10x faster**\n\n### Evaluation Performance\n- **Average: 22x faster** (range: 5.5x - 111x)\n- Simple values: **64-111x faster**\n- Collections: **46-58x faster**\n- Complex logic: **5-14x faster**\n\n### Highlights\n\n| Operation | Parsing | Evaluation |\n|-----------|---------|------------|\n| Simple number | 7.3x | 111x |\n| Array creation | 10.1x | 57.9x |\n| Map creation | 8.6x | 46x |\n| Complex authorization | 1.3x | 5.5x |\n\nRun benchmarks: `npm run benchmark`\n\n## Error Handling\n\n```javascript\nimport {evaluate, ParseError, EvaluationError, TypeError} from '@marcbachmann/cel-js'\n\ntry {\n  evaluate('invalid + + syntax')\n} catch (error) {\n  if (error instanceof ParseError) {\n    console.error('Syntax error:', error.message)\n  } else if (error instanceof EvaluationError) {\n    console.error('Runtime error:', error.message)\n  }\n}\n\n// Type checking returns errors without throwing\nconst env = new Environment().registerVariable('x', 'int')\nconst result = env.check('x + \"string\"')\nif (!result.valid \u0026\u0026 result.error instanceof TypeError) {\n  console.error('Type error:', result.error.message)\n}\n```\n\n## Examples\n\n### Authorization Rules\n\n```javascript\nimport {Environment} from '@marcbachmann/cel-js'\n\n// Instantiating an environment is expensive, please do that outside hot code paths\nconst authEnv = new Environment()\n  .registerVariable('user', 'map')\n  .registerVariable('resource', 'map')\n\nconst canEdit = authEnv.parse(`\n  user.isActive \u0026\u0026\n  (user.role == \"admin\" ||\n  user.id == resource.ownerId)\n`)\n\ncanEdit({\n  user: {id: 123, role: 'user', isActive: true},\n  resource: {ownerId: 123}\n}) // true\n```\n\n### Data Validation\n\n```javascript\nimport {Environment} from '@marcbachmann/cel-js'\n\n// Instantiating an environment is expensive, please do that outside hot code paths\nconst validator = new Environment()\n  .registerVariable('email', 'string')\n  .registerVariable('age', 'int')\n  .registerFunction('isValidEmail(string): bool',\n    email =\u003e /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)\n  )\n\nconst valid = validator.evaluate(\n  'isValidEmail(email) \u0026\u0026 age \u003e= 18 \u0026\u0026 age \u003c 120',\n  {email: 'user@example.com', age: 25n}\n)\n```\n\n### Feature Flags\n\n```javascript\nimport {parse} from '@marcbachmann/cel-js'\n\nconst flags = {\n  'new-dashboard': parse(\n    'user.betaUser || user.id in allowedUserIds'\n  ),\n  'premium-features': parse(\n    'user.subscription == \"pro\" \u0026\u0026 !user.trialExpired'\n  )\n}\n\nfunction isEnabled(feature, context) {\n  return flags[feature]?.(context) ?? false\n}\n```\n\n## TypeScript\n\nFull TypeScript support included:\n\n```typescript\nimport {Environment, evaluate, ParseError} from '@marcbachmann/cel-js'\n\n// Instantiating an environment is expensive, please do that outside hot code paths\nconst env = new Environment()\n  .registerVariable('count', 'int')\n  .registerFunction('multiplyByTwo(int): int', (x) =\u003e x * 2n)\n\nconst result: any = env.evaluate('multiplyByTwo(count)', {count: 21n})\n```\n\n## Contributing\n\nContributions welcome! Please open an issue before submitting major changes.\n\n```bash\n# Run tests\nnpm test\n\n# Run benchmarks\nnpm run benchmark\n\n# Run in watch mode\nnpm run test:watch\n```\n\n## License\n\nMIT © Marc Bachmann\n\n## See Also\n\n- [CEL Specification](https://github.com/google/cel-spec)\n- [CEL Go Implementation](https://github.com/google/cel-go)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcbachmann%2Fcel-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcbachmann%2Fcel-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcbachmann%2Fcel-js/lists"}