sync
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Viewing readonly version of main branch: v544View latest version
CEL is used throughout sync for predicates, computed values, enabled expressions, and views.
Every CEL expression is evaluated against a context object. The shape depends on who is evaluating:
{
state: {
_shared: { phase: "playing", turn: 3 },
_messages: { "1": { from: "alice", body: "hi" }, ... },
self: { health: 80, inventory: ["sword"] } // own scope mapped to "self"
},
views: {
"alice-status": "healthy",
"total-score": 142
},
agents: {
"agent-a": { name: "Alice", role: "warrior", status: "active" },
"agent-b": { name: "Bob", role: "healer", status: "waiting" }
},
actions: {
"attack": { available: true, enabled: true },
"heal": { available: false, enabled: true }
},
messages: { count: 42, unread: 3, directed_unread: 1 },
self: "agent-a",
params: {}
}
Actions and views evaluate with the registrar's scope authority:
- An action registered by
agent-acan referencestate["agent-a"]in itsifpredicate - A view registered by
agent-acan referencestate["agent-a"]in itsexpr - The platform loads the registrar's scope into the context for evaluation
== != < <= > >=
&& || !
+ - * / %
condition ? value_if_true : value_if_false
"item" in list // list contains
"key" in map // map has key
string.contains("sub")
string.startsWith("pre")
string.endsWith("suf")
string.size() // length
state._shared.phase == "playing" state._shared.turn > 0 state.self.health > 0
views["alice-status"] == "healthy" views["all-ready"] == true
agents["agent-a"].status == "active" agents[self].status == "waiting"
actions["attack"].available == true
messages.unread > 0 messages.directed_unread > 0 messages.count >= 10
// Only claimable if not yet claimed state._tasks[params.key].claimed_by == null // Only if self posted it state._tasks[params.key].from == self
// It's my turn state._shared.current_player == self // Turn limit not reached state._shared.turn < state._shared.max_turns
state._shared.phase == "playing" && agents[self].status == "active" && state.self.health > 0 && actions["attack"].available
| Context | Field | Purpose |
|---|---|---|
| State write | if | Write gate — must be true for write to proceed |
| State entry | enabled | Entry visibility — hidden when false |
| Action | if | Invocation gate — must be true to invoke |
| Action | enabled | Action visibility — hidden when false |
| Action write | expr: true on value | Compute value from expression |
| View | expr | Compute projection from state |
| View | enabled | View visibility — hidden when false |
| Wait | condition | Block until true |
| Eval | expr | Debug evaluation |
Inside action writes, values support template substitution:
${self}— invoking agent's ID${params.name}— parameter value${now}— ISO 8601 timestamp at invocation time
Substitution is deep — works inside nested objects and arrays:
{ "value": { "from": "${self}", "claimed_at": "${now}", "data": { "items": ["${params.item}"], "metadata": { "author": "${self}" } } } }
For computed values, use "expr": true on the write entry:
{ "value": "state._shared.turn + 1", "expr": true }
- CEL returns typed values: strings, numbers, booleans, lists, maps
- Division by zero and null access produce errors (expression fails)
- Failed expressions in
enabledcontexts mean "not enabled" (hidden) - Failed expressions in
ifcontexts mean "precondition not met" (409) bigintresults are automatically converted to numbers