# Vendored cel-js

Vendored from `@marcbachmann/cel-js` — a JavaScript implementation of Google's Common Expression Language (CEL).

See `VENDORED` for commit hash and date.

When modifying this library, keep both `CLAUDE.md` and `PROMPT.md` in sync with your changes. `CLAUDE.md` is the developer reference for future agents working on the code; `PROMPT.md` is the user-facing guide for agents writing CEL expressions.

## Key Modifications

`int` and `uint` types have been removed in favor of a unified `number` type backed by `Decimal` (arbitrary-precision arithmetic via BigInt). All numeric literals, arithmetic results, and size/index returns are `Decimal` instances. The `int()`, `uint()`, and `double()` conversion functions still exist as aliases but all return `number`.

`google.protobuf.Timestamp` and `google.protobuf.Duration` have been removed. Replaced with a `Date` type backed by `TZDate` from `@date-fns/tz`, constructed via `date(string)` or `date(number)` (a Unix timestamp — the seconds-vs-milliseconds unit is auto-detected by magnitude: values `< 1e11` are read as seconds, the rest as milliseconds). Date manipulation uses date-fns functions (`startOf`, `endOf`, `offset`, `set`, `format`).

A `range()` built-in has been added (not from upstream) — `range(number, number)`, `range(number, number, number)` and `range(Date, Date, string)`. It builds an end-exclusive list; the direction follows the operands (counts down when `start > end`); `step` is a positive magnitude; the date unit is `year` / `month` / `day` / `hour` / `minute`. Output length is capped at 100000.

`evaluate` now returns `Effect<T, ParseError | EvaluationError | TypeError>` instead of a plain value. The entire evaluation pipeline is Effect-based — every AST node's `evaluate` function returns an Effect, and sub-expressions are composed via `Effect.gen` / `yield*`. Promise-based async handling has been removed. User-registered functions may return `Effect` values, which are automatically flatMapped into the evaluation pipeline. Import `Effect` from `effect` and use `Effect.runSync` or `Effect.runPromise` to execute the returned Effect.

The `homogeneousAggregateLiterals` and `enableOptionalTypes` environment options have been removed. Aggregate data structures (lists, maps) are always heterogeneous — mixed-type literals are inferred as `list<dyn>` or `map<dyn, dyn>`. Optional types (`.?`, `[?]`, `optional.*` helpers) are always enabled.

The custom `Optional` class has been replaced with Effect's `Option` type (`import { Option } from 'effect'`). Internal helpers `optionalOf(value)` and `optionalValue(opt)` in `optional.js` handle the CEL-specific semantics (treating `undefined` as `None`, throwing `EvaluationError` on missing value access).

An `http` built-in has been added (`http-builtins.ts`, `http-builder.ts`) — not from upstream. It registers the `http` constant and the `http` / `HttpRequest` types on `globalRegistry`, so `http.get(url).header(...).body(...)` expressions type-check and evaluate in every `Environment`. A CEL expression only builds an `HttpRequestBuilder` — a pure description of a request; it never issues one. Both terminal methods, `.json()` and `.file()`, are dispatched by the host on the returned builder (neither is a CEL method), so expression evaluation stays free of IO. Endpoints must be absolute URLs — there is no base-URL resolution. Depends on `fx-fetch`. The host can register process-wide default headers via `HttpRequestBuilder.setDefaultHeader(name, valueOrGetter)` / `clearDefaultHeader(name)`; they are merged in at `#buildRequest()` time, with case-insensitive precedence to an explicit `.header(...)` on the builder. A getter that returns `null` / `undefined` / `''` skips the header for that request, so the host can source values from live refs (e.g. the active i18n locale for `Accept-Language`) without baking in stale snapshots.

A `form(dyn): FormData` built-in has been added (`form-builtins.ts`) — not from upstream. It registers the `FormData` type on `globalRegistry` and a global `form` function that turns a CEL map into a native `FormData`, so authors can write `http.post(url).body(form({"file": myFile, "name": "Alice"}))` (`.body()` already passes `FormData` through verbatim and `fetch` sets the multipart boundary). Value coercion mirrors `.query()`'s flat-record semantics: `null` / `undefined` / `Option.None` skip the key; `Option.Some(x)` recurses on `x`; `File` / `Blob` pass through (preserves the `File` filename); `Decimal` → `.toString()` (preserves precision past safe-integer); `Date` (`TZDate`) → `.toISOString()`; `bool` → `'true'` / `'false'`; arrays append one entry per element; nested objects throw (`multipart/form-data` is flat — same rule as `.query(dyn)`). Top-level input must be a map literal / record (Map or plain object); passing an array or scalar throws.
The `__proto__` / `constructor` / `prototype` keys are skipped on the top-level record, mirroring `safeFromEntries`.

A `JSON` built-in namespace has been added (`json-builtins.ts`) — not from upstream. It registers a `JSON` brand class and a same-named constant on `globalRegistry`, so `JSON.parse(string): dyn` and `JSON.stringify(dyn): string` resolve in every `Environment`. The CEL-side surface mirrors the JS globals deliberately. `JSON.parse` is a thin wrapper over `globalThis.JSON.parse` — numbers come back as plain JS `number` (the evaluator coerces via `Decimal.from` at use time). `JSON.stringify` pre-walks the value via `normalizeForJSON` before handing it to `globalThis.JSON.stringify`: `Decimal` → JS number (via `.toNumber()` — loses precision past safe-integer range, deliberate trade-off for idiomatic JSON output), `Option.Some(x)` → `x`, `Option.None` → `null`, `Map` → plain object, `Date`/`TZDate` passes through so its built-in `toJSON` emits ISO 8601. The pre-walk is required because Effect's `Option` defines its own `toJSON()` that runs before any replacer would see it.

Spread syntax (`...expr`) inside list and map literals — not from upstream. A new `ELLIPSIS` token (3-char lookahead in the lexer) and a `spread` AST op let `[1, ...a, 4]` and `{...a, "x": 1, ...b}` desugar inside the parent literal. The spread node's own `check`/`evaluate` are defensive stubs — `parsePrimary` never produces one, so `...` outside a list/map is an "unexpected token" parse error. List `args` shape is unchanged (`IASTNode[]`, with spread elements detected via `op === 'spread'`); map `args` becomes `([IASTNode, IASTNode] | [IASTNode])[]` — a length-1 tuple represents a spread entry, preserving positional ordering for override semantics (later writes win). The fast path is preserved: the `list`/`map` `check` swaps `evaluate` meta to `evaluateSpreadList`/`evaluateSpreadMap` only when a spread child is present; literals without spreads run the original `resolveAstArray`/`safeFromEntries` paths byte-for-byte. Spread map sources may be plain objects, `Map` instances, or registered message types (typed as `map<string, dyn>`); the `__proto__`/`constructor`/`prototype` skip from `safeFromEntries` is applied to spread sources too. Runtime errors fire on non-list/non-map sources (including `null`). `maxListElements` / `maxMapEntries` count a spread as one entry — runtime expansion bypasses those caps (deliberate trade-off). Call-argument spread (`f(...args)`) is **not** supported.

## Architecture

```
lib/
├── index.ts          — Public exports
├── parser.ts         — Lexer + recursive descent parser
├── evaluator.ts      — Expression evaluation engine + Environment class
├── type-checker.ts   — Static type analysis
├── registry.ts       — Type system, operator/function registration
├── operators.ts      — Binary/unary operator implementations
├── overloads.ts      — Operator overload definitions
├── functions.ts      — Built-in functions + Date type (TZDate)
├── macros.ts         — map, filter, all, exists, exists_one, find, has, cel.bind
├── http-builtins.ts  — `http` constant + http/HttpRequest types (local addition)
├── http-builder.ts   — `HttpRequestBuilder` — what an http.* expression returns
├── json-builtins.ts  — `JSON` constant + `JSON.parse` / `JSON.stringify` (local addition)
├── form-builtins.ts  — `form(map): FormData` global + `FormData` type (local addition)
├── decimal.ts        — Decimal class (arbitrary precision)
├── optional.ts       — Optional type support (uses Effect Option)
├── serialize.ts      — AST back to CEL string
├── errors.ts         — ParseError, TypeError, EvaluationError
├── globals.ts        — Shared utilities
├── types.ts          — Type definitions
└── options.ts        — Configuration management
```

## Public API

```js
import { Environment, ParseError, TypeError, EvaluationError } from './lib/index.js'
```

- `new Environment(options?)` — Create a configured environment
  - `.check(expr)` — Returns `Either<string, ParseError | TypeError>` (success = type name string)
  - `.evaluate<T>(expr, ctx?)` — Returns `Effect<T, ParseError | TypeError | EvaluationError>`
  - `.registerVariable(name, type, options?)`
  - `.registerConstant(name, type, value)`
  - `.registerFunction(signature, handler, options?)`
  - `.registerOperator(signature, handler)`
  - `.registerType(typename, definition)`
  - `.clone(options?)` — Copy with overrides

Internal-only (not re-exported from `index.js`): `parse`, standalone `evaluate`/`check`, `serialize`, `Decimal`, `CelError`.

## Type System

Primitives: `string`, `bool`, `number`, `bytes`, `null_type`, `type`
Composites: `list<T>`, `map<K, V>`, `optional<T>`, `dyn`, `message`
Custom: `Date` (backed by `TZDate` from `@date-fns/tz`), `URL` (backed by the native `URL`; method `URL.param(string): string` reads a search param), `File` (native), `FormData` (native — see `form-builtins.ts`), `http` / `HttpRequest` (local addition — see `http-builtins.ts`)

## Function Signature Format

```
name(argType1, argType2): returnType     — global function
Type.method(argTypes): returnType        — method on type
```

Handler receives `(arg1, arg2, ..., ast, evaluator, context)`. Macros receive AST nodes instead of evaluated values.
