🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@marcbachmann/cel-js

Package Overview
Dependencies
Maintainers
1
Versions
87
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@marcbachmann/cel-js - npm Package Compare versions

Comparing version
7.3.1
to
7.4.0
+8
-4
lib/evaluator.js

@@ -50,4 +50,4 @@ import {createRegistry, RootContext} from './registry.js'

registerFunction(string, handler) {
this.#registry.registerFunctionOverload(string, handler)
registerFunction(signature, handler, opts) {
this.#registry.registerFunctionOverload(signature, handler, opts)
return this

@@ -66,4 +66,4 @@ }

registerVariable(name, type) {
this.#registry.registerVariable(name, type)
registerVariable(name, type, opts) {
this.#registry.registerVariable(name, type, opts)
return this

@@ -81,2 +81,6 @@ }

getDefinitions() {
return this.#registry.getDefinitions()
}
check(expression) {

@@ -83,0 +87,0 @@ try {

@@ -93,3 +93,3 @@ import {EvaluationError} from './errors.js'

get(name) {
return super.get(name) ?? (RESERVED.has(name) ? undefined : dynType)
return super.get(name) ?? (RESERVED.has(name) ? undefined : dynVariableDeclaration)
}

@@ -316,14 +316,27 @@ }

export class VariableDeclaration {
constructor({name, type, description}) {
this.name = name
this.type = type
this.description = description ?? null
objFreeze(this)
}
}
export class FunctionDeclaration {
#hasPlaceholderTypes
constructor({name, receiverType, argTypes, returnType, handler}) {
constructor({name, receiverType, returnType, handler, description, params}) {
if (typeof name !== 'string') throw new Error('name must be a string')
if (typeof handler !== 'function') throw new Error('handler must be a function')
this.name = name
this.receiverType = receiverType || null
this.argTypes = argTypes
this.receiverType = receiverType ?? null
this.returnType = returnType
this.description = description ?? null
this.params = params
this.argTypes = params.map((p) => p.type)
this.macro = this.argTypes.includes(astType)
this.macro = argTypes.includes(astType)
const receiverString = receiverType ? `${receiverType}.` : ''
this.signature = `${receiverString}${name}(${argTypes.join(', ')}): ${returnType}`
this.signature = `${receiverString}${name}(${this.argTypes.join(', ')}): ${returnType}`
this.handler = this.macro ? wrapMacroExpander(this.signature, handler) : handler

@@ -426,2 +439,3 @@

const dynType = _createDynType()
const dynVariableDeclaration = new VariableDeclaration({name: 'dyn', type: dynType})
const astType = _createPrimitiveType('ast')

@@ -545,2 +559,4 @@ const listType = _createListType(dynType)

const invalidVar = 'Invalid variable declaration:'
export class Registry {

@@ -587,6 +603,25 @@ #overloadResolutionCache = {}

registerVariable(name, type) {
if (RESERVED.has(name)) throw new Error(`Cannot register reserved variable name: ${name}`)
if (this.variables.has(name)) throw new Error(`Variable already registered: ${name}`)
this.variables.set(name, type instanceof TypeDeclaration ? type : this.getType(type))
registerVariable(name, type, opts) {
let description = opts?.description
if (
typeof name === 'string' &&
typeof type === 'object' &&
!(type instanceof TypeDeclaration)
) {
description = type.description
type = type.type
} else if (typeof name === 'object') {
type = name.type
description = name.description
name = name.name
}
if (typeof name !== 'string' || !name) throw new Error(`${invalidVar} name must be a string`)
if (RESERVED.has(name)) throw new Error(`${invalidVar} '${name}' is a reserved name`)
if (this.variables.has(name)) throw new Error(`${invalidVar} '${name}' is already registered`)
if (typeof type === 'string') type = this.getType(type)
else if (!(type instanceof TypeDeclaration)) throw new Error(`${invalidVar} type is required`)
this.variables.set(name, new VariableDeclaration({name, type, description}))
return this

@@ -596,3 +631,9 @@ }

registerConstant(name, type, value) {
this.registerVariable(name, type)
if (typeof name === 'object') {
value = name.value
this.registerVariable(name)
name = name.name
} else {
this.registerVariable(name, type)
}
this.constants.set(name, value)

@@ -697,2 +738,6 @@ return this

if (typeof typeStr !== 'string' || !typeStr.length) {
throw new Error(`Invalid type: must be a string`)
}
match = typeStr.match(/^[A-Z]$/)

@@ -973,22 +1018,44 @@ if (match) return this.#createDeclaration(_createPlaceholderType, typeStr, typeStr)

/** @param {string} signature */
#parseFunctionDeclaration(signature, handler) {
// Parse "optional<A>.value(): A" or "dyn(A): dyn<A>" or "string.indexOf(string): int"
const match = signature.match(/^(?:([a-zA-Z0-9.<>]+)\.)?(\w+)\((.*?)\):\s*(.+)$/)
if (!match) throw new Error(`Invalid signature: ${signature}`)
const [, receiverType, name, argsStr, _returnType] = match
getDefinitions() {
const variables = []
const functions = []
for (const [, varDecl] of this.variables) {
if (!varDecl) continue
variables.push({
name: varDecl.name,
description: varDecl.description || null,
type: varDecl.type.name
})
}
try {
return new FunctionDeclaration({
name: name,
receiverType: receiverType ? this.getType(receiverType) : null,
returnType: this.getType(_returnType.trim()),
argTypes: splitByComma(argsStr).map((s) => this.getFunctionType(s)),
handler
for (const [, decl] of this.#functionDeclarations) {
functions.push({
signature: decl.signature,
name: decl.name,
description: decl.description,
receiverType: decl.receiverType ? decl.receiverType.name : null,
returnType: decl.returnType.name,
params: decl.params.map((p) => ({
name: p.name,
type: p.type.name,
description: p.description
}))
})
} catch (e) {
throw new Error(`Invalid function declaration: ${signature}: ${e.message}`)
}
return {variables, functions}
}
#parseSignature(signature) {
if (typeof signature !== 'string') throw new Error('Invalid signature: must be a string')
const match = signature.match(/^(?:([a-zA-Z0-9.<>]+)\.)?(\w+)\((.*?)\):\s*(.+)$/)
if (!match) throw new Error(`Invalid signature: ${signature}`)
return {
receiverType: match[1] || null,
name: match[2],
argTypes: splitByComma(match[3]),
returnType: match[4].trim()
}
}
/**

@@ -1012,3 +1079,3 @@ * @param {FunctionDeclaration} a

const o = a.argTypes[i]
return t === o || t === astType || o === astType || t === dynType || o === dynType
return t === o || t === dynType || o === dynType
}))

@@ -1028,5 +1095,56 @@ )

registerFunctionOverload(s, _opts) {
const handler = typeof _opts === 'function' ? _opts : _opts?.handler
const dec = this.#parseFunctionDeclaration(s, handler)
#normalizeParam(i, aType, param) {
if (!param) return {type: this.getFunctionType(aType), name: `arg${i}`, description: null}
const type = param.type || aType
if (!type) throw new Error(`params[${i}].type is required`)
if (aType && type !== aType) throw new Error(`params[${i}].type not equal to signature type`)
return {
name: param.name || `arg${i}`,
type: this.getFunctionType(type),
description: param.description ?? null
}
}
registerFunctionOverload(s, handler, opts) {
if (typeof s === 'object') opts = s
else if (typeof handler === 'object') opts = handler
else if (!opts) opts = {}
const sig = typeof s === 'string' ? s : (opts.signature ?? undefined)
const parsed = sig !== undefined ? this.#parseSignature(sig) : undefined
const name = parsed?.name || opts.name
const receiverType = parsed?.receiverType || opts.receiverType
const argTypes = parsed?.argTypes
const returnType = parsed?.returnType || opts.returnType
const params = opts.params
let dec
try {
if (!name) throw new Error(`signature or name are required`)
if (!returnType) throw new Error(`must have a returnType`)
if (params) {
if (argTypes && params.length !== argTypes.length) {
throw new Error(`mismatched length in params and args in signature`)
}
} else if (!argTypes) throw new Error(`signature or params are required`)
dec = new FunctionDeclaration({
name,
receiverType: receiverType ? this.getType(receiverType) : null,
returnType: this.getType(returnType),
handler: typeof handler === 'function' ? handler : opts.handler,
description: opts.description,
params: (argTypes || params).map((_, i) =>
this.#normalizeParam(i, argTypes?.[i], params?.[i])
)
})
} catch (e) {
if (typeof sig === 'string') e.message = `Invalid function declaration '${sig}': ${e.message}`
else if (name) e.message = `Invalid function declaration '${name}': ${e.message}`
else e.message = `Invalid function declaration: ${e.message}`
throw e
}
this.#checkOverlappingSignatures(dec)

@@ -1190,3 +1308,3 @@ this.#functionDeclarations.set(dec.signature, dec)

getType(name) {
return this.#variables.get(name)
return this.#variables.get(name)?.type
}

@@ -1193,0 +1311,0 @@

@@ -31,3 +31,3 @@ import {TypeError, EvaluationError} from './errors.js'

checkAccessOnType(ast, ctx, leftType, allowMissingField = false) {
if (leftType.kind === 'dyn') return leftType
if (leftType === this.dynType) return leftType

@@ -39,6 +39,4 @@ const indexTypeName = (

if (leftType.kind === 'list') {
if (indexTypeName !== 'int' && indexTypeName !== 'dyn') {
throw new this.Error(`List index must be int, got '${indexTypeName}'`, ast)
}
return leftType.valueType
if (indexTypeName === 'int' || indexTypeName === 'dyn') return leftType.valueType
throw new this.Error(`List index must be int, got '${indexTypeName}'`, ast)
}

@@ -45,0 +43,0 @@

{
"name": "@marcbachmann/cel-js",
"version": "7.3.1",
"version": "7.4.0",
"description": "A lightweight Common Expression Language (CEL) implementation in JavaScript with zero dependencies",

@@ -72,12 +72,12 @@ "keywords": [

"service": "drone",
"commit": "d2520eb4eda8d546e4003c2aab04af524af5d46b",
"build": "233",
"buildUrl": "https://drone.livingdocs.io/marcbachmann/cel-js/233",
"commit": "74bb594614b78ef4b8b47b1b4c344f6848e9fd02",
"build": "235",
"buildUrl": "https://drone.livingdocs.io/marcbachmann/cel-js/235",
"branch": "main",
"jobUrl": "https://drone.livingdocs.io/marcbachmann/cel-js/233",
"jobUrl": "https://drone.livingdocs.io/marcbachmann/cel-js/235",
"isPr": false,
"slug": "marcbachmann/cel-js",
"root": "/drone/src",
"date": "2026-02-09T10:56:44.061Z"
"date": "2026-02-11T23:27:43.430Z"
}
}

@@ -89,3 +89,3 @@ # @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)

Use `registerConstant(name, type, value)` to expose shared configuration without passing it through every evaluation context.
Use `registerConstant` to expose shared configuration without passing it through every evaluation context.

@@ -101,2 +101,9 @@ ```javascript

Supported signatures:
```javascript
env.registerConstant('minAge', 'int', 18n)
env.registerConstant({name: 'minAge', type: 'int', value: 18n, description: 'Minimum age'})
```
#### Environment Options

@@ -138,3 +145,59 @@

- **`check(expression)`** - Validate expression types without evaluation
- **`getDefinitions()`** - Returns all registered variables and functions with their types, signatures, and descriptions
#### `registerVariable` signatures
```javascript
env.registerVariable('user', 'map')
env.registerVariable('user', 'map', {description: 'The current user'})
env.registerVariable('user', {type: 'map', description: 'The current user'})
env.registerVariable({name: 'user', type: 'map', description: 'The current user'})
```
The `type` can be a type string (e.g. `'int'`, `'map'`, `'list<string>'`) or a `TypeDeclaration` obtained from another environment.
#### `registerFunction` signatures
```javascript
// Signature string + handler
env.registerFunction('greet(string): string', (name) => `Hello, ${name}!`)
env.registerFunction('greet(string): string', handler, {description: 'Greets someone'})
env.registerFunction('greet(string): string', {handler, description: 'Greets someone'})
// Single object with signature string
env.registerFunction({signature: 'add(int, int): int', handler, description: 'Adds two integers'})
// Single object with signature string and named params
env.registerFunction({
signature: 'formatDate(int, string): string',
handler,
description: 'Formats a timestamp',
params: [
{name: 'timestamp', description: 'Unix timestamp in seconds'},
{name: 'format', description: 'Date format string'}
]
})
// Single object without signature string
env.registerFunction({
name: 'multiply',
returnType: 'int',
handler: (a, b) => a * b,
description: 'Multiplies two integers',
params: [
{name: 'a', type: 'int', description: 'First number'},
{name: 'b', type: 'int', description: 'Second number'}
]
})
// Receiver method (called as 'hello'.shout())
env.registerFunction({
name: 'shout',
receiverType: 'string',
returnType: 'string',
handler: (str) => str.toUpperCase() + '!',
params: []
})
```
#### `registerFunction` (sync & async)

@@ -141,0 +204,0 @@