derivations, metaDerivations, constraints
The computed half of a model: values derived from other values, the live metadata that overlays the schema, and the invariants a mutation must not break.
derivations
Computed read-only fields whose values are re-evaluated whenever their dependencies change.
{
"path": "$.total",
"expr": "$sum(items.(price * qty))",
"evaluation": "eager",
"description": "Grand total of all line items"
}
| Field | Required | Default | Description |
|---|---|---|---|
path |
yes | — | Target field path, $.-prefixed |
expr |
yes | — | JSONata expression |
evaluation |
no | "eager" |
"eager" or "lazy" |
description |
no | — | Human-readable explanation |
Evaluation modes
eager— evaluated synchronously during each mutation transaction, in topological dependency order. The derived value is written to the derivation cache and included in everyGET /stateresponse.lazy— marked stale during the transaction but not evaluated until requested. Evaluated on demand byGET /models/{id}/stateorGET /models/{id}/state/{path}.
Wildcard (per-element) derivations
A path containing [*] (e.g. $.items[*].lineTotal) evaluates the expression once for
each array element. Inside the expression, $parent is bound to the current element node;
the full merged document is always available as the root context.
{
"path": "$.items[*].lineTotal",
"expr": "$parent.price * $parent.qty"
}
Dependency tracking
The runtime automatically builds a dependency graph by extracting all JSONata identifier references from each expression. Derivations are evaluated in topological order, so a derivation can safely reference another derived field.
[
{ "path": "$.monthlyRate", "expr": "annualRatePercent / 1200" },
{ "path": "$.compoundFactor", "expr": "$power(1 + monthlyRate, termMonths)" },
{ "path": "$.monthlyPayment", "expr": "loanAmount * monthlyRate * compoundFactor / (compoundFactor - 1)" }
]
metaDerivations
Per-field metadata that is re-evaluated like derivations but stored in a separate meta cache (not the base document). The meta cache is used by the view evaluator to automatically control component visibility, interactivity, and validation.
{
"path": "$.contactEmail",
"property": "relevant",
"expr": "contactPermission = true",
"description": "Email is only needed when contact permission is granted"
}
| Field | Required | Description |
|---|---|---|
path |
yes | Target field path, $.-prefixed |
property |
yes | Which metadata property to compute (see table below) |
expr |
yes | JSONata expression evaluated against the merged document |
description |
no | Human-readable explanation |
Meta properties
property |
JSON Schema keyword | Description |
|---|---|---|
required |
required |
Whether the field is mandatory |
minimum |
minimum |
Numeric lower bound |
maximum |
maximum |
Numeric upper bound |
multipleOf |
multipleOf |
Numeric granularity (e.g. 0.01 for cent-precision) |
minLength |
minLength |
String minimum length |
maxLength |
maxLength |
String maximum length |
pattern |
pattern |
Regex pattern |
enum |
enum |
Allowed values array |
readOnly |
— | Whether the field is non-editable |
relevant |
— | Valem extension: whether the field is currently relevant/visible |
relevant has no JSON Schema equivalent. When relevant evaluates to false for a
field, the view evaluator hides the corresponding component (see View Definition).
Visibility is presentation-only — hidden fields still compute.
relevant=false(and a view component’svisible=false) affect rendering only. The field’s value remains in the document and continues to participate fully in derivations and constraints: a derivation that reads a hidden field still uses its value, and a constraint over a hidden field still fires (rolling back or flagging as configured). Valem does not treat “not relevant” as “absent from computation”. If a value should not influence results when hidden, gate that explicitly in the expression (e.g.relevantFlag ? hiddenValue : 0) rather than relying on visibility.
Meta cache key format
Meta values are stored as "$.path#property_name" where the property name is the enum
name in lowercase (e.g. "$.contactEmail#relevant", "$.score#read_only"). This format
is used internally by ViewEvaluator and by tests that assert meta state.
constraints
Boolean invariants evaluated after each mutation. A failed constraint triggers the
configured policy.
{
"id": "rating-in-range",
"path": null,
"expr": "overallRating >= 1 and overallRating <= 5",
"message": "Rating must be between 1 and 5",
"policy": "rollback"
}
| Field | Required | Description |
|---|---|---|
id |
yes | Unique constraint identifier |
path |
no | Scope path (see below) |
expr |
yes | JSONata boolean expression evaluated against the merged document |
message |
yes | Human-readable violation message |
policy |
yes | "rollback" or "flag" |
path scoping
| Value | Behavior |
|---|---|
null (omitted) |
Global constraint — evaluated once against the full merged document |
"$.items" (scalar string) |
Single-target constraint scoped to one field |
["$.price", "$.qty"] |
Multi-target constraint: path field in JSON accepts either a string or an array of strings |
"$.items[*]" |
Array-scoped (per-element) constraint evaluated once per array element |
Policies
rollback— the entire mutation transaction is rolled back and a409 Conflictresponse is returned with the constraint message.flag— the mutation commits, but the violated constraints are included in the mutation response body under"flaggedConstraints".
Which mechanism enforces a bound? (schema vs constraint vs metaDerivation)
Three mechanisms can express “this field must stay within a range”. They differ in when they run and what the client sees — pick by the behaviour you want, and avoid encoding the same bound in more than one place (the validator may warn on conflicting bounds for one field):
| Mechanism | When it runs | On violation | Use when… |
|---|---|---|---|
schema (static JSON Schema minimum/maximum/enum/required/…) |
Pre-transaction, before any write | HTTP 422, mutation never applied | The bound is constant and structural — the field can never validly hold the value. |
metaDerivations (live minimum/maximum/required/relevant/…) |
During the reactive pipeline; overlays the effective schema | Advisory — surfaces via GET /schema/{path} and drives the UI; also enforced as 422 on the next mutation against the overlaid schema |
The bound is dynamic (depends on other fields) and you want the form/effective-schema to reflect it. |
constraints (boolean invariant) |
After derivations settle, against the merged document | rollback → HTTP 409 (whole transaction reverts) or flag (commits, reported in flaggedConstraints) |
The rule spans multiple fields / cross-field relationships, or you want a commit-but-flag policy. |
Rule of thumb: constant single-field limit → schema; field-dependent single-field limit you want
reflected in the UI → metaDerivations; cross-field invariant or flag-don’t-block → constraints.