Chained Pipelines
Real-world queries assembled from the building blocks. Each recipe uses one small document and shows the query chain plus a sentence on what the planner does.
1. Top-N by aggregate
DOC: {"sales": [
{"region": "NA", "amount": 100},
{"region": "EU", "amount": 200},
{"region": "NA", "amount": 50},
{"region": "AS", "amount": 300},
{"region": "EU", "amount": 75}
]}
QUERY: $.sales
.group_by(@.region)
.entries()
.map(([region, rows]) => {region, total: rows.map(@.amount).sum()})
.sort(@.total)
.reverse()
.take(2)
OUT: [{"region":"AS","total":300},{"region":"EU","total":275}]
group_by and sort are barriers; take(2) after the sort doesn't help —
the sort must complete first. Push the demand earlier where possible.
2. Active users + role-based count
DOC: {"users": [
{"id":1,"role":"admin","active":true},
{"id":2,"role":"user","active":false},
{"id":3,"role":"user","active":true},
{"id":4,"role":"admin","active":true}
]}
QUERY: $.users
.filter(@.active)
.count_by(@.role)
OUT: {"admin":2,"user":1}
Streaming filter + barrier count_by. The filter passes only what's needed;
count_by buffers but with ValueNeed::Predicate (only the role key) — the
rest of the user object is never decoded.
3. Histogram of word frequency
DOC: {"text": "the quick brown fox jumps over the lazy dog the end"}
QUERY: $.text
.words()
.map(@.lower())
.count_by(@)
OUT: {"the": 3, "quick": 1, "brown": 1, ...}
4. Customer order summary
QUERY: $.orders
.group_by(@.customer_id)
.entries()
.map(([cid, orders]) => {
customer_id: cid,
total: orders.map(@.amount).sum(),
count: orders.count(),
recent: orders.sort(@.date).last().date
})
.sort_by(@.total)
.reverse()
The inner .sort(@.date).last() is wasteful: it sorts every group to grab
the last. Rewrite with max_by:
QUERY: ...
.map(([cid, orders]) => {
customer_id: cid,
total: orders.map(@.amount).sum(),
count: orders.count(),
recent: orders.max_by(@.date).date
})
5. Unique recent active sessions
QUERY: $.events
.filter(@.kind == "login" and .at >= "2026-01-01")
.map(@.user_id)
.unique()
.count()
6. Pretty-print a CSV from objects
QUERY: $.users
.filter(@.active)
.map(u => u.pick(id: id, name: full_name, email))
.sort(@.id)
.to_csv()
7. Find a needle in a deep document
QUERY: $..find(@.id == 42)
If the document was loaded from bytes (default), this hits the structural index — no full traversal.
8. Compute deltas with pairwise
DOC: {"prices": [100, 105, 102, 110, 108]}
QUERY: $.prices.pairwise().map(([a, b]) => b - a)
OUT: [5,-3,8,-2]
9. Rolling 3-point moving average
QUERY: $.measurements.rolling_avg(3)
The first two outputs are null until the window fills.
10. Build a lookup, then enrich
QUERY: let by_id = $.users.index_by(@.id) in
$.events.map(e => e.merge({user: by_id[e.user_id].name}))
index_by is a barrier that runs once; the .map streams.
11. Select rows with all required fields
QUERY: $.records.filter(r => r.missing("id", "name", "email").count() == 0)
12. Re-shape a long-format table
DOC: [
{"y":2024,"q":1,"v":10},{"y":2024,"q":2,"v":20},
{"y":2025,"q":1,"v":15},{"y":2025,"q":2,"v":25}
]
QUERY: $.pivot("y", "q", "v")
OUT: {"2024":{"1":10,"2":20},"2025":{"1":15,"2":25}}
13. Mask sensitive fields
QUERY: $.users.map(u => u.omit("password", "ssn", "token"))
14. Delta + cumulative sum
DOC: {"daily":[{"value":10},{"value":15},{"value":12},{"value":20}]}
QUERY: $.daily
.pairwise()
.map(([a, b]) => b.value - a.value)
OUT: [5,-3,8]
For a running total, use accumulate:
DOC: {"amounts":[10,12,9]}
QUERY: $.amounts.accumulate(0, (total, x) => total + x)
OUT: [10,22,31]
15. Classify rows with match
DOC: {"books": [
{"title":"Dune","year":1965,"tags":["sf"]},
{"title":"Snow Crash","year":1992,"tags":["sf","cyberpunk"]},
{"title":"Foundation","year":1951,"tags":["sf","hugo"]}
]}
QUERY: $.books
.map(book => {
title: book.title,
era: match book with {
{year: y} when y < 1970 -> f"classic {y}",
{year: y} -> f"modern {y}",
_ -> "unknown"
},
tag_count: book.tags.count()
})
OUT: [
{"title":"Dune","era":"classic 1965","tag_count":1},
{"title":"Snow Crash","era":"modern 1992","tag_count":2},
{"title":"Foundation","era":"classic 1951","tag_count":2}
]
16. Latest active rows from NDJSON
jetrocli --ndjson -i users.topic --payload-after '|' -e '
$.rows()
.reverse()
.distinct_by(@.id)
.filter(@.active)
.take(100)
.map({
id: $.id,
name: $.profile.name,
city: $.profile.address.city
})
'
On a compacted Kafka-style file, reverse rows make the newest record for each
key appear first. distinct_by(@.id) keeps that first row and discards older
duplicates as soon as the key has been seen.
17. Patch several paths in one pass
DOC: {"books":[
{"title":"Dune","year":1965,"tags":["sf"],"tmp":true},
{"title":"Snow Crash","year":1992,"tags":["sf"],"tmp":true}
]}
QUERY: $.update({
books[*].tags: @.append("catalog"),
books[*].reviewed: true,
books[*].tmp: DELETE
})
OUT: {"books":[
{"title":"Dune","year":1965,"tags":["sf","catalog"],"reviewed":true},
{"title":"Snow Crash","year":1992,"tags":["sf","catalog"],"reviewed":true}
]}
The planner can batch compatible rooted writes so shared ancestors are cloned once and all writes under that prefix are applied together.
18. Migrate a document shape
Use walk when every nested object with a matching shape must be rewritten:
QUERY:
$.walk(node =>
node.merge({type: "v2"})
.rename({old_field: "new_field"})
.omit("legacy_blob")
if node is object and node.type == "v1" else node)
For query-local rewrites on known paths, prefer update(...); for broad shape
migration, walk makes the traversal explicit.