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.
1. The [] as iter Syntax
Section titled “1. The [] as iter Syntax”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 }}Shadow Scopes
Section titled “Shadow Scopes”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.
Loop-scoped Tool Handles
Section titled “Loop-scoped Tool Handles”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.
Aliasing Sub-fields for Readability
Section titled “Aliasing Sub-fields for Readability”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}2. Array Control Flow
Section titled “2. Array Control Flow”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.
Skipping Items (continue)
Section titled “Skipping Items (continue)”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}Halting the Array (break)
Section titled “Halting the Array (break)”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 }}3. Common Use Cases
Section titled “3. Common Use Cases”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 }}Use Case 2: Fanout (API Call Per Element)
Section titled “Use Case 2: Fanout (API Call Per Element)”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.