Steel dependencies and hydration
Every database-aware call in a Steel script becomes a stored dependency. Dependencies are both execution metadata and an allowlist: the runtime can see only values approved when the script was registered.
Dependency kinds
Section titled “Dependency kinds”| Kind | Created by | Meaning |
|---|---|---|
column_access |
steel_get_column |
One current-row or linked-row column |
related_aggregate |
steel_related_aggregate, steel_related_count_rows, steel_related_exists |
One aggregate over a declared relationship path |
quantity_effect |
quantity-add, quantity-subtract |
One contribution to a linked quantity-ledger column |
Database function arguments must be string literals. Indirect calls such as assigning steel_get_column to a variable are rejected, because the server must be able to discover and validate the complete dependency set before execution.
Current-row values
Section titled “Current-row values”Use the current table’s name as the first argument:
(* (steel_get_column "invoice_line" "quantity") (steel_get_column "invoice_line" "unit_price"))On the client, these values come directly from the active form. On the server, they come from the submitted insert snapshot or the stored row merged with update fields. Unsaved edits therefore participate in both client preview and server validation.
A script cannot read its own target column. has-var? checks whether a field name is present in the row snapshot:
(if (has-var? "discount") (steel_get_column "invoice" "discount") "0")Presence and emptiness are different. A key mapped to an empty value is still present.
Linked-row values
Section titled “Linked-row values”The first argument normally names the link column on the scripted table:
(steel_get_column "customer" "discount_percent")Here customer is the current row field holding the related customer ID. The server resolves that link when the script is saved and stores the exact link-column identity.
If exactly one link reaches a target table, the table name may resolve unambiguously. When two links point to the same table—such as buyer and supplier both pointing to contact—use the link name. The target table alone cannot identify which row is intended.
Hydration groups requested columns by target table and row ID, then reads them in a repeatable-read, read-only PostgreSQL transaction. A missing or deleted required related row fails hydration.
Related collections
Section titled “Related collections”Use an aggregate when one owner row needs information from a collection of related rows:
(steel_related_aggregate "sum" "invoice_line" "line_total" "invoice")The arguments are:
- aggregate operation;
- target table containing the related rows;
- target column;
- relationship anchor that disambiguates the path.
Row-only operations have shorter forms:
(steel_related_count_rows "invoice_line" "invoice")(steel_related_exists "invoice_line" "invoice")Aggregate operations
Section titled “Aggregate operations”| Operation | Valid input | Result |
|---|---|---|
sum |
INT, DECIMAL, or MONEY | Same numeric kind; empty money sum is zero in its currency |
min, max |
INT, DECIMAL, or MONEY | Same numeric kind |
count |
Any permitted column | Count of non-null values |
count_distinct |
Any permitted column | Count of distinct non-null values |
any, all |
BOOLEAN | Boolean |
count_rows |
No column | Related row count |
exists |
No column | Whether any related row exists |
The path must be reachable from the scripted table through the anchor and must be unambiguous. Paths may contain multiple foreign-key hops. One script may declare at most 16 distinct related aggregates.
What hydration returns
Section titled “What hydration returns”HydrateScriptDependencies accepts the owner table, optional persisted row ID, and complete current form snapshot. It derives the required external reads from all scripts stored for that table.
The response contains:
columns: exact linked-row values, with row ID, logical type, and optional money currency;aggregates: exact aggregate results, with operation, path anchor, type, and optional currency.
It intentionally omits:
- current-table values, because the client already has fresher form values;
- quantity effects, because those are recorded during script execution rather than fetched;
- arbitrary values requested by the caller, because the dependency records—not request parameters—define access.
New and existing rows
Section titled “New and existing rows”| Situation | row_id |
row_data |
|---|---|---|
| New unsaved row | 0 |
Complete current form, including link IDs |
| Existing row | Persisted positive ID | Complete current form, including unsaved edits and link IDs |
Aggregate evaluation uses both the owner identity and the current snapshot to apply the correct relationship semantics. Linked-column hydration skips a dependency when its link value is absent or empty; an invalid non-empty row ID returns INVALID_ARGUMENT.
Alias stability
Section titled “Alias stability”Scripts are authored with public names, but stored with stable physical column names. Dependency rows also store column identities and resolved link identities. When returning scripts or hydration data, the server maps those names back to current aliases.
This is why clients should use the fields returned by GetTableScripts instead of reconstructing names from conventions.