The json-render expression / binding dialect, scoped to this package's
supported set and pinned to @json-render/core v0.19.0 (see SUPPORT.md).
Every dynamic value in a manifest is a plain JSON object carrying a single
$-prefixed discriminant key. This module decodes the supported forms into a typed
Expr and resolves them against a host-owned state Value plus an optional
repeat scope, using RFC 6901 JSON Pointers throughout.
Fail-closed: an object carrying an unsupported $-directive (e.g. $computed)
fails the decode. json-render's own runtime is fail-open here; we are not. The
supported set is exactly the v1 subset (which now includes $cond).
A json-render condition — the predicate of a $cond, and the same grammar as
core's visible / hidden (pinned to @json-render/core v0.19.0). Evaluated to a
Bool by resolve when picking a $cond branch:
CBool b: a literal true / false.CSingle c: one $state / $item / $index comparison.CEvery cs: a bare JSON array of single conditions, an implicit AND (all true).CAnd cs / COr cs: explicit { "$and": [ … ] } / { "$or": [ … ] }, recursive.Malformed conditions (unknown keys, mixed sources, a non-true not, a non-numeric
operand for gt/gte/lt/lte) fail the decode, mirroring the rest of this module.
One comparison against a single source value. negate is core's "not": true
modifier, which inverts whatever the operator (or the bare truthiness test) yields.
The left-hand side of a single condition: $state pointer, $item field, or the
repeat $index.
The comparison operator. CmpTruthy is the no-operator case (JS Boolean(value)).
eq / neq compare against any value by JSON scalar equality; gt / gte / lt /
lte compare numbers only: a non-numeric literal operand is rejected at decode, and a
non-numeric source value yields False at eval time (per core).
A missing path (an absent $state / $item / $index source or {$state} operand) is
JS undefined, kept distinct from JSON null: it is falsy, eq-equal only to another
missing path (undefined === undefined; undefined !== null), and never orders.
The right-hand side of a comparison: either a literal JSON value or a
{ "$state": "/ptr" } reference resolved against global state at eval time (core's
resolveComparisonValue).
Decode a prop value into an Expr.
A non-object (string / number / bool / array / null) decodes to ELiteral. An object
with no $-prefixed key decodes to ELiteral. An object with exactly one supported
$-directive decodes to that form. An object with an unsupported $-directive, or
more than one $-directive, fails. This is the fail-closed boundary.
The resolution scope handed to every expression.
state: the host-owned global state Value (the single source of truth).item: the current repeat item, if inside a repeat scope.index: the current repeat index, if inside a repeat scope.basePath: the repeat item's absolute JSON Pointer (statePath ++ "/" ++ index),
used to compute $bindItem / top-level $item write-back paths.allowedOrigins: the host-provided iframe origin allowlist. The Iframe element only
renders when a resolved src's origin is an exact member of this list (fail-closed).The top-level scope: global state only, no repeat item. allowedOrigins is the
host-provided iframe origin allowlist, threaded to every expression scope.
Derive a per-item scope for a repeat element. statePath is the pointer to the
state array; index and item identify the current row. basePath becomes
statePath ++ "/" ++ index per the pinned reference.
Resolve an RFC 6901 JSON Pointer against a Value. Returns Nothing if any token
is missing. The empty pointer "" returns the whole value. Tokens are unescaped
(~1 → /, ~0 → ~). A leading / is honored; a bare token (no leading /, as
produced by $item field lookups) is tolerated as a single step.
Resolve an expression to a JSON Value. A missing pointer / item field yields
null. Two-way $bindState / $bindItem read exactly like their read-only siblings;
their write-back path is exposed separately via writeBackPath.
Resolve an expression to its display string (for Text / Badge). Scalars render
as themselves; null / missing renders as ""; objects / arrays render as "".
Resolve an expression to a boolean (for Checkbox checked). Only a JSON true
is True; everything else (including missing) is False.
The absolute JSON Pointer a two-way binding writes back to, or Nothing for
read-only expressions. $bindState writes its own pointer; $bindItem writes
basePath ++ "/" ++ field. A $cond passes through to the branch it selects at render
time (see Expr), so a $bindState / $bindItem inside the chosen branch keeps
its write-back handler.
Resolve an action's params object per the pinned resolveActionParam semantics:
{ "$item": "field" } resolves to the absolute state path
(basePath ++ "/" ++ field) so the host can target the row.{ "$index": true } resolves to the index number.$item nested in an array) resolves to the value
by deep recursion.So { "ids": [ { "$item": "id" } ] } yields { "ids": ["<id>"] }: the literal id, never
a path.
A Decoder for an action's params that validates fail-closed at decode time:
it returns the params Value unchanged, but fails if any nested object carrying a
$-directive is not a supported, well-formed Expr (e.g. an unsupported
$computed, or a malformed { "$item": 123 }). This closes the gap where params are stored
raw and only resolved at dispatch: a bad directive is rejected with the rest of the
manifest, never emitted to the host.
A decoded json-render expression. The discriminant maps 1:1 to the pinned
$-forms:ELiteral v: any value with no$-directive (scalar, array, or plain object).EState ptr:{ "$state": "/ptr" }, read global state.EItem field:{ "$item": "field" }, read the current repeat item (field""= whole item).EIndex:{ "$index": true }, the current repeat index.EBindState ptr:{ "$bindState": "/ptr" }, two-way bind to global state.EBindItem field:{ "$bindItem": "field" }, two-way bind to a repeat-item field.ETemplate tmpl:{ "$template": "…${/ptr}…${bare}…" }, string interpolation.ECond cond t e:{ "$cond": <condition>, "$then": <expr>, "$else": <expr> }, pickstwhencondholds, elsee. Both branches are themselves expressions (recursive). A two-way$bindState/$bindIteminside the selected branch keeps its write-back:writeBackPathtargets the branch chosen at render time. SeeCondition.