Skip to content

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.

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.

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.

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.

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:

  1. aggregate operation;
  2. target table containing the related rows;
  3. target column;
  4. 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")
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.

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.
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.

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.