Steel architecture and lifecycle
Steel scripts define one computed value for one table column. They are registered with the server, fetched by the client, executed by the client for immediate form feedback, and executed again by the server whenever row data is saved.
The client calculation is a preview. The server calculation is authoritative.
What Steel is for
Section titled “What Steel is for”Use a table script when a column must be derived consistently from the current row, a linked row, or a related collection. Typical uses include:
- multiplying a quantity by a unit price;
- calculating net, VAT, and gross totals;
- copying or classifying a value with conditions;
- aggregating related lines into a document total;
- producing a controlled contribution to a quantity-ledger column.
A script is not a general server hook. It cannot run arbitrary SQL, call services, import modules, or mutate tables. The only supported write-like effect is a declared quantity-add or quantity-subtract contribution.
The complete flow
Section titled “The complete flow”script author │ │ PostTableScript ▼server validates, transforms, analyzes, and stores the script │ │ GetTableScripts ▼client registers the script and its dependency allowlist │ ├── current-row inputs come from the open form │ └── HydrateScriptDependencies supplies declared external inputs │ ▼ client evaluates for immediate feedback │ │ PostTableData or PutTableData ▼server rebuilds the context and evaluates the same stored script │ ├── matching submitted value → commit └── different or missing value → rejectThere are therefore three separate concerns:
| Concern | Owner | Purpose |
|---|---|---|
| Script definition | TableScript gRPC service |
Validate and store calculation rules |
| Interactive calculation | Client Steel runtime | Recalculate a form immediately as input changes |
| Persistent enforcement | TablesData gRPC service |
Recalculate inside the data transaction and reject incorrect values |
One script, one target
Section titled “One script, one target”PostTableScript binds a script to a table_definition_id and a target column. The last expression returned by the script is the target value.
(money-mul (steel_get_column "invoice_line" "unit_price") (steel_get_column "invoice_line" "quantity"))If this script targets line_total, callers must send line_total when inserting the row. The server does not silently add a missing computed field to the request. It verifies that the client-supplied value equals the server result.
What is stored
Section titled “What is stored”The server stores two linked records:
table_scriptsstores the target, target type, transformed script, and description;script_dependenciesstores every permitted column read, related aggregate, and quantity effect.
The script and dependencies are written in one transaction. Column aliases are translated to stable physical names for storage. GetTableScripts translates them back to current public aliases, so renaming an alias does not require rewriting every script.
Validation at registration time
Section titled “Validation at registration time”Before storing a script, the server checks:
- The table exists and is not a protected system table.
- The script is a non-empty S-expression and Steel can parse it.
- The expanded program contains only allowlisted forms and functions.
- The target column exists, is writable, and has a supported type.
- The target does not already contain data.
- Every database function uses literal names and references an existing column.
- Linked-row reads resolve through an explicit, unambiguous link.
- Related aggregates resolve through an unambiguous relationship path.
- Math, money, and quantity-ledger operations are type-safe.
- The transformed script and normalized dependencies can be committed together.
Source arithmetic such as +, *, and / is transformed to exact-decimal operations before storage. GetTableScripts returns this validated, transformed form.
Runtime isolation
Section titled “Runtime isolation”The server builds a request-scoped context containing only declared values. Registered functions such as steel_get_column read from that context rather than querying arbitrary data from inside the VM.
The synchronous Steel VM runs away from asynchronous runtime worker threads. Engines are reused from a bounded pool, request context is cleared after each run, and per-run decimal precision state is reset.
Where to continue
Section titled “Where to continue”- Table definitions documents the separate service that supplies Steel’s table IDs, columns, types, and links.
- Row data and validation explains insert, update, read, mismatch, and quantity-effect behavior.
- Dependencies and hydration explains current rows, links, aggregates, and client snapshots.
- Language reference lists the accepted functions, types, and forbidden behavior.
- Sandbox and execution explains how scripts are vetted before storage and isolated at run time.
- Quantity ledger explains how
quantity-addandquantity-subtractbuild server-owned balances.