Skip to content

Combinators & templates

Combinators glue values together — chain a transform pipeline, pick a value by case, merge several reactive inputs, or build up an object. They’re the plumbing you reach for when a single query or state key isn’t quite the shape a block wants. See the expression reference for the exact inputs.

@expr/pipeline

The backbone of client-side data shaping: a source expression followed by a sequence of transform operators, each receiving the previous result. Reach for it whenever you’d otherwise nest transforms inside transforms — it reads top-to-bottom instead of inside-out.

{
"@expr/pipeline": [
{ "@expr/query": "SELECT StatusCode, Duration FROM spans" },
{ "@expr/filter": { "where": "StatusCode > 0" } },
{ "@expr/derive": { "duration_ms": "Duration / 1000000" } }
]
}

@expr/case

Picks a value by matching a context value against a set of cases (with a default) — a lookup table. Use it to map a status to a colour, a mode to a label, an enum to a human string, without a chain of conditionals.

{ "@expr/case": { "status": { "ok": "green", "error": "red", "default": "gray" } } }

@expr/combine_latest and @expr/concat

Merge several reactive inputs. @expr/combine_latest emits an array of the latest values of its inputs, re-emitting whenever any one changes — use it to feed a block something that depends on several state keys or queries at once. @expr/concat flattens several array-valued expressions into one list — handy for stitching together option lists or rows from more than one source.

{
"@expr/combine_latest": [{ "@expr/get_context": "userName" }, { "@expr/get_context": "userRole" }]
}

@expr/object, @expr/spread and @expr/get

Small value plumbing. @expr/get reads a value out of context and walks a dot path into it ("totals.TotalCost") — pull a single field out of a one-row query, or a nested property out of a state object.

{ "@expr/get": "totals.TotalCost" }

@expr/object builds a one-property object from a { key, value } pair. Both positions take an expression, so the key can be computed too.

{ "@expr/object": { "key": "env", "value": { "@expr/get_context": "env" } } }
{ "@expr/object": { "key": { "@expr/get_context": "field" }, "value": 1 } }

A many-property object is a merge of pairs, which is what @expr/spread is for.

@expr/spread merges objects left to right — literal ones, expressions resolving to one, or @expr/object pairs — so a later part’s keys win. Reach for it to assemble a map from pieces computed separately: a base set of values plus a conditional override.

{
"@expr/spread": [
{ "service": "checkout" },
{ "@expr/object": { "key": "env", "value": { "@expr/get_context": "env" } } }
]
}

Both are checked against the position they sit in: where a slot’s type says what its values are, each value is validated against it rather than accepted as anything.

@expr/resolve

Deep-substitutes any embedded @expr/* expressions inside a JSON-like value, leaving the surrounding shape intact. Reach for it to template a structured object — say, a block’s config — where only some fields are dynamic.

{
"@expr/resolve": {
"title": "Dashboard",
"user": { "@expr/get_context": "userName" },
"tags": ["static", { "@expr/get_context": "currentTag" }]
}
}