Cookbook

Recipes that compose operations to solve common tasks. Each one is a complete request body you can send to the endpoint. If you're new to the operation language, read Operations first. Each recipe links to the operations it uses.

The one rule to remember

A parameter that's an array is evaluated as an operation; anything else is taken literally. So a scalar passes straight through, but to pass an array or object as data you have to wrap it in value, or it's read as an operation and rejected.

["run", "getUser", 1]
["run", "createUser", ["value", {"email": "ada@example.com", "roles": ["admin"]}]]

The first passes the literal 1. The second wraps the object so it's passed as data instead of being read as an operation.

Reading and reshaping data

Pick one field from a result

Run an action and pull a single field out of its result with get.

["get", "email", ["run", "getUser", 1]]
"ada@example.com"

Reach into nested data

get keys support dot paths, so you can reach deep into a result in one step.

["get", "address.city", ["run", "getUser", 1]]
"Berlin"

Provide a fallback when a field is missing

A third parameter to get is the default, returned when the key is absent (it'd otherwise be null).

["get", "nickname", ["run", "getUser", 1], ["value", "Anonymous"]]

Build a custom response object

object evaluates each value, so you can assemble the shape you want, pulling from several actions in a single request.

["object", {
  "user":  ["run", "getUser", 1],
  "posts": ["run", "getPosts", 1]
}]
{
  "user":  { "id": 1, "username": "ada" },
  "posts": [ { "id": 10, "title": "Hello" } ]
}

Lists and collections

Turn a list into a lookup map

map takes a key field and a value field and builds a {key: value} object from a list.

["map", "id", "username", ["run", "getUsers", ["value", {"role": "member"}]]]
{ "1": "ada", "2": "linus" }

Take the top N

Sort a result, then take from the front. sort with a field and direction, then take for how many.

["take", 3, ["sort", "score", "desc", ["run", "getPlayers", "season-2024"]]]

Take the last N

A negative count for take counts from the end.

["take", -5, ["run", "getEvents", "today"]]

Sort a plain array

sort with just a direction sorts a flat array. The array literal is wrapped in value.

["sort", "asc", ["value", [5, 2, 8, 1]]]
[1, 2, 5, 8]

Apply defaults to a result

merge shallow-merges arrays left to right, so later values win. Put defaults first and the real result second to fill in only what's missing.

["merge",
  ["value", {"role": "guest", "active": true}],
  ["run", "getUser", 1]
]

Logic and conditionals

Branch on a comparison

ifElse picks one of two expressions based on a check. Here the check is a compare. All three branches have to be expressions, so the literals are wrapped in value.

["ifElse",
  ["compare", ["get", "age", ["run", "getUser", 1]], ">=", ["value", 18]],
  ["value", "adult"],
  ["value", "minor"]
]

Combine several checks

and (and or) evaluate to a boolean and short-circuit.

["and", ["run", "isActive", 1], ["run", "isAdmin", 1]]
true

First value that isn't null

coalesce returns the first non-null result and stops. Unlike a get default, the alternatives can be whole actions, which is handy for a cache fallback.

["coalesce",
  ["run", "getFromCache", "user:1"],
  ["run", "getUser", 1]
]

Multi-step requests

These use sequence (evaluate each item, return the last) and var (store and recall a value) to do several things in one request.

Fetch once, use many times

Calling an action twice does the work twice. Store it in a variable instead and read it back as often as you like.

["sequence",
  ["var", "user", ["run", "getUser", 1]],
  ["object", {
    "name":  ["get", "username", ["var", "user"]],
    "email": ["get", "email", ["var", "user"]]
  }]
]

Create something, then use its id

The result of a create flows into the next step through a variable.

["sequence",
  ["var", "user", ["run", "createUser", ["value", {"email": "ada@example.com"}]]],
  ["run", "sendWelcome", ["get", "user_id", ["var", "user"]]]
]

Read a nested value from a stored variable

var reads support the same dot paths as get.

["sequence",
  ["var", "user", ["run", "getUser", 1]],
  ["var", "user.address.city"]
]

Converting types

cast converts a value to int, float, bool, or string, which is useful when an action returns a number as a string.

["cast", "int", ["get", "count", ["run", "getStats", "today"]]]

Paginating

list runs a list action and returns { "total", "result" }. Its data is wrapped in value and has to include properties.

["list", "users", ["value", {
  "properties": ["id", "username"],
  "filters":    {"role": "admin"},
  "page":       0,
  "pageSize":   20
}]]
{
  "total": 42,
  "result": [
    { "id": 1, "username": "ada" },
    { "id": 2, "username": "linus" }
  ]
}

Putting it together

A single request can do all of the above at once. This fetches a user, then builds a dashboard object (their name, their three most-liked posts, and whether they're an admin), reusing the stored user along the way.

["sequence",
  ["var", "user", ["run", "getUser", 1]],
  ["object", {
    "name":     ["get", "username", ["var", "user"]],
    "topPosts": ["take", 3, ["sort", "likes", "desc", ["run", "getPosts", 1]]],
    "isAdmin":  ["compare", ["get", "role", ["var", "user"]], "=", ["value", "admin"]]
  }]
]