Steel row data and validation
Steel does not provide a separate computed-row store. Scripted targets are ordinary table columns whose values are submitted by the client and verified by the server.
Insert behavior
Section titled “Insert behavior”PostTableData converts the incoming protobuf values to a row snapshot, loads all scripts for the table, and executes every script before inserting.
For each scripted target:
- The target must exist in
data. - The server prepares current-row, linked-row, and aggregate inputs.
- The stored transformed script runs in a request-scoped Steel context.
- The final result is converted for the target type.
- The submitted value is compared with the result.
- A mismatch rejects the complete insert.
The server never trusts a computed value merely because it came from the client’s Steel runtime.
Type-aware equality
Section titled “Type-aware equality”| Target | Comparison |
|---|---|
| Decimal | Parsed as exact decimals and compared numerically |
| Money | Parsed exactly, checked in the target currency, then compared with configured rounding |
| Integer | Parsed as integers and compared numerically |
| Boolean | Parsed as booleans |
| Other supported targets | Compared as returned strings |
An empty script result matches only an empty submitted value.
Update behavior
Section titled “Update behavior”PutTableData merges the supplied fields over the stored row and evaluates all scripts against that proposed final snapshot.
There are two cases for each scripted target:
Target in data? |
Server behavior |
|---|---|
| Yes | The submitted target must equal the newly calculated value |
| No | The newly calculated value must equal the currently stored target |
This design prevents an update from making a computed field stale without silently adding fields the caller did not request.
For example, changing quantity from 2 to 3 changes line_total. A request containing only quantity is rejected. Send both the changed input and the newly calculated line_total.
Read behavior
Section titled “Read behavior”GetTableData and GetTableDataByPosition return the values already stored in the row. They do not run scripts and do not recalculate stale data.
A client preparing Steel for an editable row generally performs:
GetTableDatato load the persisted current-row values.GetTableScriptsto load the table’s current scripts.HydrateScriptDependenciesto load external inputs.- Local Steel execution whenever form data changes.
PutTableDatawith the changed inputs and changed computed targets.
Steel validation errors
Section titled “Steel validation errors”When a submitted computed value differs from the Steel result, the server returns:
| Detail | Value |
|---|---|
| gRPC code | FAILED_PRECONDITION |
| Metadata | komp-ac-error-reason: computed-value-mismatch |
Clients should branch on this metadata reason instead of parsing the human-readable comparison message. Script parsing, type, missing-input, and runtime failures are reported separately, usually as INVALID_ARGUMENT or FAILED_PRECONDITION according to the Steel failure.
Bulk inserts
Section titled “Bulk inserts”Each PostTableDataBulk row uses the same Steel execution, computed-value comparison, and quantity-effect path as a single insert. The RPC stops at the first row that fails Steel validation and identifies its row index.
Quantity-ledger effects
Section titled “Quantity-ledger effects”quantity-add and quantity-subtract are the only controlled effects a script may emit. They require:
- a link from the source row to the target row;
- a target column marked as quantity-ledger backed;
- a positive numeric or money amount;
- compatible quantity type, unit, and currency metadata.
(quantity-subtract "product" "stock" (steel_get_column "sale_line" "quantity"))The function records an effect while also returning its amount as the expression value. After the source row is inserted or updated, the server reconciles effects by (source row, script, effect index):
- a new effect applies its signed amount;
- a changed amount applies only the difference;
- a changed target reverses the old contribution and applies the new one;
- a removed effect reverses its previous contribution;
- deleting the source row reverses all of its recorded contributions.
This avoids repeatedly adding the full amount every time the source row is edited.
Circular dependencies
Section titled “Circular dependencies”Circular relationships in dependency metadata are allowed. Saving one row does not recursively execute scripts on every table in the graph. Each data request executes the scripts belonging to the row being written, using a bounded snapshot of declared inputs.
Consequently, callers remain responsible for submitting any directly affected target columns in the row they update. Steel is not a general cross-table trigger engine.