diff --git a/content/docs/data-modeling/field-types.mdx b/content/docs/data-modeling/field-types.mdx index 304128c1298..58cf6664b25 100644 --- a/content/docs/data-modeling/field-types.mdx +++ b/content/docs/data-modeling/field-types.mdx @@ -415,6 +415,15 @@ Calculated field using a CEL expression (see [Expressions](/docs/data-modeling/f { name: 'total', label: 'Total', type: 'formula', expression: 'record.quantity * record.unit_price', returnType: 'number' } ``` +#### `expression` on a stored field + +`expression` is not only a `formula` key. On a field of any other type, the engine computes it from the row each write stores and **stores** the result in the field's column, so a `summary` can roll the column up. It is computed on every insert and every update by id (the stored row merged with the patch). On a predicate update it is computed only when the patch moves an input: one value is stored for every matched row, and the update is refused (`VALIDATION_FAILED`, an `invalid_value` error on the field) when the rows would compute different values. Update those rows by id instead. A value a client sends for the field never stands: it is overwritten by the computed one, with no refusal and no dropped-field report. The full contract is the `expression` row of [`FieldSchema`](/docs/references/data/field). + +```typescript +// Stored, so a parent `summary` can sum it. A client-sent amount is overwritten. +Field.currency({ label: 'Amount', expression: 'record.quantity * record.unit_price' }) +``` + ### `summary` Roll-up summary from child records. diff --git a/content/docs/data-modeling/validation-rules.mdx b/content/docs/data-modeling/validation-rules.mdx index ea7c0487700..e82ddbd7be8 100644 --- a/content/docs/data-modeling/validation-rules.mdx +++ b/content/docs/data-modeling/validation-rules.mdx @@ -365,6 +365,8 @@ application-level emptiness check is needed. } ``` +**`expression` on a stored field.** A field of any other type may declare `expression` too, and it validates differently from a `formula`. The engine computes the value on every insert and every update by id, and stores it. A value a client sends for the field is not refused and not reported as dropped: it is overwritten by the computed one. The one refusal is a predicate update that moves an input: when the matched rows would compute different values, the update is refused with `VALIDATION_FAILED` and an `invalid_value` error naming the field, and nothing is written. See [`expression` on a stored field](/docs/data-modeling/field-types#expression-on-a-stored-field) and the `expression` row of [`FieldSchema`](/docs/references/data/field). + ### `summary` | Property | Type | Default | Validation Behavior |