Skip to content

Array Mapping

When you need to return a list of items in GraphQL, you cannot write standard for or map loops. Instead, The Bridge provides a declarative syntax for mapping array elements into your expected output shape, complete with explicit control flow to filter or halt the iteration.

To map an array, you use the [] as <variableName> operator followed by a scoping block.

bridge Query.getJourneys {
with routerApi as router
with output as o
# Iterate over every element in router.journeys
o.journeys <- router.journeys[] as j {
# Map fields for each individual element
.label <- j.label
.departureTime <- j.departure
}
}

When the engine executes an array mapping block, it creates a Shadow Scope (or shadow tree) for every single element in the array.

The variable j represents the current element being processed. Because each element executes in its own isolated scope, you can nest array mappings arbitrarily deep without worrying about variable collisions.

Array mappings can introduce tool handles inside the loop body with with. Those handles are writable only inside the current loop scope.

o.items <- api.items[] as item {
with std.httpCall as fetchItem
fetchItem.value <- item.id
.detail <- fetchItem.data
}

If repeated elements can resolve to the same tool input, add memoize to the loop-scoped handle:

o.items <- api.items[] as item {
with std.httpCall as fetchItem memoize
fetchItem.value <- item.id
.detail <- fetchItem.data
}

Memoization is scoped to that declared handle. A nested loop can declare its own memoized handle without sharing the parent cache.

You can also use aliases inside loops purely for readability, without triggering any tools. If your iterator has deeply nested data, bind it to a short variable:

o.list <- api.items[] as it {
# Bind a deep sub-object to a cleaner name
alias it.metadata.authorInfo as author
.name <- author.name
.role <- author.role
}

APIs often return messy arrays containing nulls, missing IDs, or corrupt data. Instead of returning null to the frontend, you can use the explicit control flow keywords continue and break on the right side of any fallback gate (??, ||, catch) to filter the array directly.

The continue keyword instructs the engine to omit this specific item from the final array, but keep looping and processing the rest of the elements.

o.items <- billingApi.items[] as item {
# If the item is missing an ID, skip it entirely.
# The frontend will not receive a null object; the item just won't exist.
.id <- item.id ?? continue
.name <- item.name
}

The break keyword instructs the engine to stop processing the array entirely and return the items processed up to that point.

o.items <- searchApi.results[] as item {
.id <- item.id
# If we hit an item without a price, halt the entire array map.
# Returns only the valid items that came before it.
.price <- item.price ?? break
}

Multi-Level Control Flow (break N, continue N)

Section titled “Multi-Level Control Flow (break N, continue N)”

When working with deeply nested arrays (e.g., mapping categories that contain lists of products), you may want an error deep inside the inner array to skip the outer array element.

You can append a number to break or continue to specify how many loop levels the signal should pierce.

o.catalogs <- api.catalogs[] as cat {
.id <- cat.id
.products <- cat.products[] as prod {
.name <- prod.name
# If a product has a fatal data corruption, skip the ENTIRE catalog.
# 'continue 1' would just skip this product.
# 'continue 2' skips this product AND the catalog it belongs to!
.sku <- prod.sku ?? continue 2
}
}

Use Case 1: Filtering an Array Before Mapping

Section titled “Use Case 1: Filtering an Array Before Mapping”

While continue is great for dropping items based on missing structural fields, sometimes you want to pre-filter a massive array based on explicit logic before mapping it. You can do this using the built-in std.arr.filter tool, either as a tool node or by piping an array through it.

bridge Query.getActiveAdmins {
with std.arr.filter as filter
with context as ctx
with output as o
filter.role = "admin"
filter.active = true
# usage as pipe
# alias filter:ctx.users as final
# usage as tool node
filter.in <- ctx.users
alias filter as final
o <- final[] as user {
.id <- user.id
.name <- user.name
.role <- user.role
.active <- user.active
}
}

If you have an array of IDs and you need to fetch detailed information from an external API for every single item, you can perform a “Fanout.”

By combining array mapping with a loop-scoped tool handle, the engine will fire a parallel API request for every element in the array.

tool userDetails from std.httpCall {
.baseUrl = "https://jsonplaceholder.typicode.com"
.method = "GET"
}
bridge Query.getEnrichedUsers {
with context as ctx
with output as o
o <- ctx.userIds[] as id {
with userDetails as user memoize
user.path <- "/users/{id}"
.id <- id
.name <- user.name ?? continue
.email <- user.email
}
}

If duplicate inputs are common, add memoize to the loop-scoped handle so repeated IDs can reuse the earlier result within that handle’s cache.