Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions content/docs/data-modeling/formulas.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,37 @@ Keep them pure, dependency-free, and AI-readable.

---

## Build-time validation

`objectstack build` type-checks every expression against the object schema, so
a mistake that would silently evaluate to `null` at runtime is caught before it
ships. The same shared validator also runs when a flow is registered
(`registerFlow`), so a flow authored dynamically gets the same verdict. Findings
come at two severities: **errors** fail the build; **warnings** are advisory
(surfaced, not fatal — promote them with `--strict`).

| Check | Example | Severity |
|:---|:---|:---|
| **Qualify field references** — a formula / validation / predicate binds only the `record` namespace, so a bare `amount` resolves to nothing and evaluates to `null`. Write `record.amount`. | `amount > 100` → `record.amount > 100` | error |
| **Field must exist** — a typo'd `record.<field>` is flagged with a did-you-mean. | `record.amont` → `record.amount` | error |
| **Unknown function** — a misspelled or nonexistent stdlib call. | `isBlnk(record.x)` | error |
| **Type soundness** — a text or boolean field used with an arithmetic (`+ - * / %`) or ordering (`< > <= >=`) operator against a number faults at runtime and evaluates to `null`. Store it as a number field, or drop the arithmetic. | `record.title * 2` | warning |

> **Integer literals in arithmetic are fine.** `record.amount / 100`,
> `record.price * 2`, `record.total - fee` all work — the engine handles mixed
> `double × int` arithmetic, so you never need the `100.0` / `2.0` float-literal
> workaround. Equality against any type is also safe (`record.stage == 5` simply
> returns `false`), so only arithmetic/ordering on a genuinely text/boolean
> field is flagged.

In **flow and automation conditions** the record's fields are bound *bare*
(`stage == "won"`, not `record.stage`), so a bare reference is correct there.
Instead, a bare identifier that is a near-miss of a real field gets a
did-you-mean **warning** (a genuine flow variable is left alone), and the same
type-soundness check applies.

---

## Cookbook

### Computed full name (handle nullable parts)
Expand Down
6 changes: 6 additions & 0 deletions content/docs/data-modeling/validation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,12 @@ functions available include:
To compare against a field's previous value, reference `previous.<field>`
(e.g. `record.stage != previous.stage`).

> **Qualify your references.** A validation predicate binds only the `record`
> namespace, so a bare `status == "won"` resolves to nothing and the rule
> silently never fires — write `record.status == "won"`. `objectstack build`
> flags bare references, unknown fields, and text/boolean fields misused in
> arithmetic — see [Build-time validation](/docs/data-modeling/formulas#build-time-validation).

## Best Practices

✅ **DO:**
Expand Down