Branching — if, else, and type tests

Branching — if, else, and type tests #

Act only under a condition with a plain if over an ordinary boolean expression — a field value, x != null, a combination with &&/||.

conditions #

function `Inbound Intake`(msg: <inbox-[:Emails]->>) {
  if (Text.includes(msg.Subject, "order")) {
    write crm-[:Companies]-> { … }
  }
}

No else means the other cases simply do nothing. A condition may read a value that might not be there — if (o.viable) { … } on an extracted <boolean>, or o.stage == "Seed" — and a missing one counts as false, in &&, ||, ! and ? : alike. To choose between two values inside an expression, write cond ? a : b.

narrowing #

Narrow a source that may be several kinds with is, before you traverse it:

function `Route Change`(ev: <crm-[:CompanyChange]->>) {
  if (ev is <crm-[x:CompanyChange WHERE x.Kind == "created"]->>) {
    ev-[rec:Company]-> {
      write rec { Description: "logged on creation" }
    }
  }
}

A change stands for a creation, an update or a deletion; inside the arm ev is a created change, and the arm's code reads it as one. is is valid anywhere a boolean is; inside a walk's WHERE it is IS. If a record arrives without the field a test names, the run stops with that reason rather than picking a branch, so no arm ever runs against a record it can't identify.

A hop narrows from inside its WHERE the same way: testing a member's label (x.listName == "Deal Pipeline") picks that member for the whole hop — see blocks in the traversal chapter.

An else narrows too: reaching it proves the record isn't what the arms above tested, so the else sees the kinds that are left, and an else if chain keeps eliminating down to one. Handle every kind and the final else has nothing left to reach — reading a field there is refused, which is how you find a branch that can't run.

A test can also name a declared structure instead of a kind: it holds when the record carries every field that structure declares, with a compatible type. A nested node never gates the test — a record with none of that relationship carries an empty set of them, and fits. Carrying more than the declaration is fine. It narrows exactly like the tests above: the arm keeps the kinds that fit, the else keeps the rest.

node Contact {
  Name:  <text>
  Email: <text>
}

  if (rec is <Contact>) {
    write chat-[:Messages]-> { Message: "${rec.Name} <${rec.Email}>" }
  }

Presence narrows the same way: == null / != null settles whether a value that might be missing is there — see values that may not be there in the expressions chapter, and the guard below.

guard-with-error #

Stop a run with a reason instead of writing bad data — and settle what follows in the same line:

function `Notify Company Change`(evt: <crm-[:CompanyChange]->>) {
  channel = only(chat-[ch:Channels WHERE ch.Name == "automation-alerts"]->)
  if (channel == null) { fail("channel not found") }

  # `channel` is a present record from here on — no further guard needed
  write channel-[:Messages]-> { Message: "…" }
}

fail inside a traversal block ends the whole run, not that iteration — and writes already made stay made, in this run and earlier iterations. Guard before you write, not after.

Pitfalls #