Traversal blocks — doing something per related position

Do something per related position by following a path with a block: any position-valued path followed by a block runs the block once per position the path yields. Two attachments → the block runs twice; none → it doesn't run at all. That is the language's only repetition — there is no loop construct.

blocks #

msg-[a:Attachments]-> {
  write drive-[:file]-> {
    Name: a.Name
    Data: a.Data
  }
}

A handle is already a position — "continue from the company I just wrote" needs no construct: expressions rooted at the handle read its own fields directly, and traversing further edges onward from it walks just like any other position (co-[n:Notes]-> { … }, "${co-[:Notes]->.Content}").

query-a-graph #

A constructed instance can root a traversal directly. The first hop names a collection, and the bracket takes the full query surface: WHERE to filter, ORDER BY to sort by a field of the hop target, LIMIT to take the top N.

# The ten most recent Customer companies in the CRM, one block run each:
crm-[c:Companies WHERE c.Categories == "Customer" ORDER BY c.`Created At` DESC LIMIT 10]-> {
  …
}

# The same surface works in expressions and conditions:
Message: "Top company: ${crm-[c:Companies ORDER BY c.`Team Size` DESC LIMIT 1]->.Name}"
if (exists(crm-[c:Companies WHERE c.Domains CONTAINS m.sender_domain]->)) { … }

# Sort by something one hop along — the bracket's alias names the record being ranked:
crm-[p:People ORDER BY p-[:Company]->.Name]-> { … }

record-order #

# Some relationships are ordered by nature — a channel hands its messages back
# oldest-first, an email its attachments in the order it carried them.
Message: "${Text.join(ch-[m:Messages LIMIT 20]->.Text, "
")}"

# Everywhere else, say what you mean.
Message: "${Text.join(crm-[c:Companies ORDER BY c.`Created At` DESC LIMIT 5]->.Name, ", ")}"

# One record, however they come back.
channel = only(chat-[ch:Channels WHERE ch.Name == "alerts"]->)

A relationship either hands its records back in an order that means something or it doesn't, and the difference decides which aggregates may read it.

Which relationships are ordered is the source's own fact, and each one says: chat messages and thread replies by time, a curated list's entries by when they were added, a document's sections and an email's attachments by their place in it, answers and callback fires by when they landed.

what-a-source-can-filter #

Expect the filter surface to differ per source. A CRM might filter companies by a categorised field at its query API but not by a long free-text one; a record's handful of related rows can be filtered on anything, because the set is already small. Each source declares — per relationship and per field — what it can do:

What you can write against a source is the overlap of the language and that source's own abilities; you learn where that overlap ends while authoring, not in production.

what-a-block-hands-back #

Hand a value out of a block with return, and bind the block to collect what its iterations returned:

names = mentioned-[c:company]-> {
  return c.name
}

Message: "Logged ${Text.join(names, ", ")}"

return runs once per position the head yields, so the binding is all of them together. What that collection is follows from what you returned:

orgs = mentioned-[c:company]-> {
  return write crm-[:Companies]-> { unique by (`Name`), Name: c.name }
}

Message: "Logged ${count(orgs)} companies"

Pitfalls #