From 5f52e9cf9870550a06a727c197dae92fb57e27f6 Mon Sep 17 00:00:00 2001 From: scottrippey Date: Wed, 28 May 2025 14:58:48 -0600 Subject: [PATCH 01/12] docs: added initial Cheat Sheet --- website/docs/API/groq-cheat-sheet-raw.md | 466 +++++++++++++++++++ website/docs/API/groq-cheat-sheet.md | 566 +++++++++++++++++++++++ 2 files changed, 1032 insertions(+) create mode 100644 website/docs/API/groq-cheat-sheet-raw.md create mode 100644 website/docs/API/groq-cheat-sheet.md diff --git a/website/docs/API/groq-cheat-sheet-raw.md b/website/docs/API/groq-cheat-sheet-raw.md new file mode 100644 index 00000000..3b316a3f --- /dev/null +++ b/website/docs/API/groq-cheat-sheet-raw.md @@ -0,0 +1,466 @@ +# Query Cheat Sheet - GROQ + +Here are some typical queries in GROQ. You can also check out [our introduction to GROQ](/docs/content-lake/how-queries-work) and [the complete reference documentation](/docs/groq). To actually run queries you can: + +- Hit your content lake's query [HTTP endpoint](/docs/http-reference/query) directly +- Use the [JavaScript](/docs/js-client) or [PHP](/docs/php-client) SDKs, or [another client](https://www.sanity.io/exchange/type=plugins/solution=apis) +- Install the [Vision plugin](/docs/content-lake/the-vision-plugin) that runs queries right inside Sanity Studio +- Go to [groq.dev](https://groq.dev) to run queries against any JSON dataset + +> [!WARNING] +> Gotcha +> If your query doesn't work as expected, it might be related to: +> +> API versioning +> +> Perspectives + +## Filters + +> [!TIP] +> Protip +> You will get null as a value on a query if the key you ask for doesn't exist. That means you can filter on key != null to check if it exists with a value or not. + +```groq +* // Everything, i.e. all documents +*[] // Everything with no filters applied, i.e. all documents +*[_type == "movie"] // All movie documents +*[_id == "abc.123"] // _id equals +*[_type in ["movie", "person"]] // _type is movie or person +*[_type == "movie" && popularity > 15 && releaseDate > "2016-04-25"] // multiple filters AND +*[_type == "movie" && (popularity > 15 || releaseDate > "2016-04-25")] // multiple filters OR +*[popularity < 15] // less than +*[popularity > 15] // greater than +*[popularity <= 15] // less than or equal +*[popularity >= 15] // greater than or equal +*[popularity == 15] +*[releaseDate != "2016-04-27"] // not equal +*[!(releaseDate == "2016-04-27")] // not equal +*[!(releaseDate != "2016-04-27")] // even equal via double negatives "not not equal" +*[dateTime(_updatedAt) > dateTime('2018-04-20T20:43:31Z')] // Use zulu-time when comparing datetimes to strings +*[dateTime(_updatedAt) > dateTime(now()) - 60*60*24*7] // Updated within the past week +*[name < "Baker"] // Records whose name precedes "Baker" alphabetically +*[awardWinner == true] // match boolean +*[awardWinner] // true if awardWinner == true +*[!awardWinner] // true if awardWinner == false +*[defined(awardWinner)] // has been assigned an award winner status (any kind of value) +*[!defined(awardWinner)] // has not been assigned an award winner status (any kind of value) +*[title == "Aliens"] +*[title in ["Aliens", "Interstellar", "Passengers"]] +*[_id in path("a.b.c.*")] // _id matches a.b.c.d but not a.b.c.d.e +*[_id in path("a.b.c.**")] // _id matches a.b.c.d, and also a.b.c.d.e.f.g, but not a.b.x.1 +*[!(_id in path("a.b.c.**"))] // _id matches anything that is not under the a.b.c path or deeper +*["yolo" in tags] // documents that have the string "yolo" in the array "tags" +*[status in ["completed", "archived"]] // the string field status is either == "completed" or "archived" +*["person_sigourney-weaver" in castMembers[].person._ref] // Any document having a castMember referencing sigourney as its person +*[slug.current == "some-slug"] // nested properties +*[count((categories[]->slug.current)[@ in ["action", "thriller"]]) > 0] // documents that reference categories with slugs of "action" or "thriller" +*[count((categories[]->slug.current)[@ in ["action", "thriller"]]) == 2] // documents that reference categories with slugs of "action" and "thriller" + // set == 2 based on the total number of items in the array +``` + +## Text matching + +> [!WARNING] +> Gotcha +> The match operator is designed for human-language text and might not do what you expect! + +```groq +// Text contains the word "word" +*[text match "word"] + +// Title contains a word starting with "wo" +*[title match "wo*"] + +// Inverse of the previous query; animal matches the start of the word "caterpillar" (perhaps animal == "cat") +*["caterpillar" match animal + "*"] + +// Title and body combined contains a word starting with "wo" and the full word "zero" +*[[title, body] match ["wo*", "zero"]] + +// Are there aliens in my rich text? +*[body[].children[].text match "aliens"] + +// Note how match operates on tokens! +"foo bar" match "fo*" // -> true +"my-pretty-pony-123.jpg" match "my*.jpg" // -> false +``` + +## Slice Operations + +> [!TIP] +> Protip +> There is no default limit, meaning that if you're not explicit about slice, you'll get everything. + +```groq +*[_type == "movie"][0] // a single movie (an object is returned, not an array) +*[_type == "movie"][0..5] // first 6 movies (inclusive) +*[_type == "movie"][0...5] // first 5 movies (non-inclusive) +*[_type == "movie"]{title}[0...10] // first 10 movie titles +*[_type == "movie"][0...10]{title} // first 10 movie titles +*[_type == "movie"][10...20]{title} // first 10 movie titles, offset by 10 +*[_type == "movie"] // no slice specified --> all movies are returned +``` + +**Also note**: The above queries don't make much sense without also specifying an order. E.g. the "first 6 movies" query only returns "first" movies in the sense that these are the first six movies the backend happens to pull out. + +## Ordering + +> [!TIP] +> Protip +> Documents are returned by default in ascending order by _id, which may not be what you're after. If you're querying for a subset of your documents, it's usually a good idea to specify an order. +> +> No matter what sort order is specified, the ascending order by _id will always remain the final tie-breaker. + +```groq +// order results +*[_type == "movie"] | order(_createdAt asc) + +// order results by multiple attributes +*[_type == "movie"] | order(releaseDate desc) | order(_createdAt asc) + +// order todo items by descending priority, +// where priority is equal, list most recently updated +// item first +*[_type == "todo"] | order(priority desc, _updatedAt desc) + +// the single, oldest document +*[_type == "movie"] | order(_createdAt asc)[0] + +// the single, newest document +*[_type == "movie"] | order(_createdAt desc)[0] + +// oldest 10 documents +*[_type == "movie"] | order(_createdAt asc)[0..9] + +// BEWARE! This selects 10 documents using the default +// ordering, and *only the selection* is ordered by +// _createdAt in ascending order +*[_type == "movie"][0..9] | order(_createdAt asc) + +// limit/offset using external params (see client documentation) +*[_type == "movie"] | order(_createdAt asc) [$start..$end] + +// order results alphabetically by a string field +// This is case sensitive, so A-Z come before a-z +*[_type == "movie"] | order(title asc) + +// order results alphabetically by a string field, +// ignoring case +*[_type == "movie"] | order(lower(title) asc) +``` + +## Joins + +```groq +// Fetch movies with title, and join with poster asset with path + url +*[_type=='movie']{title,poster{asset->{path,url}}} + +// Say castMembers is an array containing objects with character name and a reference to the person: +// We want to fetch movie with title and an attribute named "cast" which is an array of actor names +*[_type=='movie']{title,'cast': castMembers[].person->name} + +// Same query as above, except "cast" now contains objects with person._id and person.name +*[_type=='movie']{title,'cast': castMembers[].person->{_id, name}} + +// Using the ^ operator to refer to the enclosing document. Here ^._id refers to the id +// of the enclosing person record. +*[_type=="person"]{ + name, + "relatedMovies": *[_type=='movie' && references(^._id)]{ title } +} + +// Books by author.name (book.author is a reference) +*[_type == "book" && author._ref in *[_type=="author" && name=="John Doe"]._id ]{...} + +``` + +## Objects and Arrays + +```groq +// Create your own objects +// https://groq.dev/lcGV0Km6dpvYovREqq1gLS +{ + // People ordered by Nobel prize year + "peopleByPrizeYear": *[]|order(prizes[0].year desc){ + "name": firstname + " " + surname, + "orderYear": prizes[0].year, + prizes + }, + // List of all prizes ordered by year awarded + "allPrizes": *[].prizes[]|order(year desc) +} + +// Get all Nobel prizes from all root person documents +// https://groq.dev/v8T0DQawC6ihbNUf4cUeeS +*[].prizes[] + +array::join(tags, ", ") // tags = ["Rust", "Go", null, "GROQ"] => "Rust, Go, , GROQ" +array::join(["a", "b", "c"], ".") // "a.b.c" +array::join(year, ".") // year = 2024 => null (not an array) +array::join(values, 1) // values = [10, 20, 30] => null (separator must be a string) +array::compact(numbers) // numbers = [1, null, 2, null, 3] => [1, 2, 3] +array::unique(items) // items = [1, 2, 2, 3, 4, 5, 5] => [1, 2, 3, 4, 5] +array::unique(records) // records = [[1], [1]] => [[1], [1]] (arrays are not comparable) +array::intersects(firstList, secondList) // firstList = [1, 2, 3], secondList = [3, 4, 5] => true +array::intersects(tags, keywords) // tags = ["tech", "science"], keywords = ["art", "design"] => false +``` + +## Object Projections + +```groq +// return only title +*[_type == 'movie']{title} + +// return values for multiple attributes +*[_type == 'movie']{_id, _type, title} + +// explicitly name the return field for _id +*[_type == 'movie']{'renamedId': _id, _type, title} + +// Return an array of attribute values (no object wrapper) +*[_type == 'movie'].title +*[_type == 'movie']{'characterNames': castMembers[].characterName} + +// movie titled Arrival and its posterUrl +*[_type=='movie' && title == 'Arrival']{title,'posterUrl': poster.asset->url} + +// Explicitly return all attributes +*[_type == 'movie']{...} + +// Some computed attributes, then also add all attributes of the result +*[_type == 'movie']{'posterUrl': poster.asset->url, ...} + +// Default values when missing or null in document +*[_type == 'movie']{..., 'rating': coalesce(rating, 'unknown')} + +// Number of elements in array 'actors' on each movie +*[_type == 'movie']{"actorCount": count(actors)} + +// Apply a projection to every member of an array +*[_type == 'movie']{castMembers[]{characterName, person}} + +// Filter embedded objects +*[_type == 'movie']{castMembers[characterName match 'Ripley']{characterName, person}} + +// Follow every reference in an array of references +*[_type == 'book']{authors[]->{name, bio}} + +// Explicity name the outer return field +{'threeMovieTitles': *[_type=='movie'][0..2].title} + +// Combining several unrelated queries in one request +{'featuredMovie': *[_type == 'movie' && title == 'Alien'][0], 'scifiMovies': *[_type == 'movie' && 'sci-fi' in genres]} + +``` + +## Special variables + +```groq +// * +* // Everything, i.e. all documents + +// @ +*[ @["1"] ] // @ refers to the root value (document) of the scope +*[ @[$prop]._ref == $refId ] // Select reference prop from an outside variable. +*{"arraySizes": arrays[]{"size": count(@)}} // @ also works for nested scopes + +// ^ +// ^ refers to the enclosing document. Here ^._id refers to the id +// of the enclosing person record. +*[_type=="person"]{ + name, + "relatedMovies": *[_type=='movie' && references(^._id)]{ title } +} +``` + +## Conditionals + +```groq +// select() returns the first => pair whose left-hand side evaluates to true +*[_type=='movie']{..., "popularity": select( + popularity > 20 => "high", + popularity > 10 => "medium", + popularity <= 10 => "low" +)} + +// The first select() parameter without => is returned if no previous matches are found +*[_type=='movie']{..., "popularity": select( + popularity > 20 => "high", + popularity > 10 => "medium", + "low" +)} + +// Projections also have syntactic sugar for inline conditionals +*[_type=='movie']{ + ..., + releaseDate >= '2018-06-01' => { + "screenings": *[_type == 'screening' && movie._ref == ^._id], + "news": *[_type == 'news' && movie._ref == ^._id], + }, + popularity > 20 && rating > 7.0 => { + "featured": true, + "awards": *[_type == 'award' && movie._ref == ^._id], + }, +} + +// The above is exactly equivalent to: +*[_type=='movie']{ + ..., + ...select(releaseDate >= '2018-06-01' => { + "screenings": *[_type == 'screening' && movie._ref == ^._id], + "news": *[_type == 'news' && movie._ref == ^._id], + }), + ...select(popularity > 20 && rating > 7.0 => { + "featured": true, + "awards": *[_type == 'award' && movie._ref == ^._id], + }), +} + + +// Specify sets of projections for different content types in an array +content[]{ + _type == 'type1' => { + // Your selection of fields for type1 + }, + _type == 'type2' => { + // Your selection of fields for type2 + "url": file.asset->url // Use joins to get data of referenced document + } +} + +``` + +### Handling references conditionally + +In cases where an array contains both [references and non-references](https://www.sanity.io/docs/array-type#wT47gyCx), it's often desirable for a GROQ query to conditionally return the inline object (where dealing with non-references) or the referenced document (where dealing with references). This can be done by considering the `_type` of each array item and dereferencing the item (`@->`) if it's a reference or getting the whole object (`@`) if it's not a reference. + +```groq +'content': content[]{ + _type == 'reference' => @->, + _type != 'reference' => @, +} +``` + +## Functions + +```groq +// any document that references the document +// with id person_sigourney-weaver, +// return only title +*[references("person_sigourney-weaver")]{title} + +// Movies which reference ancient people +*[_type=="movie" && references(*[_type=="person" && age > 99]._id)]{title} + +*[defined(tags)] // any document that has the attribute 'tags' + +// coalesce takes a number of attribute references +// and returns the value of the first attribute +// that is non-null. In this example used to +// default back to the English language where a +// Finnish translation does not exist. +*{"title": coalesce(title.fi, title.en)} + +// count counts the number of items in a collection +count(*[_type == 'movie' && rating == 'R']) // returns number of R-rated movies + +*[_type == 'movie']{ + title, + "actorCount": count(actors) // Counts the number of elements in the array actors +} + +// round() rounds number to the nearest integer, or the given number of decimals +round(3.14) // 3 +round(3.14, 1) // 3.1 + + +// score() adds points to the score value depending +// on the use of the string "GROQ" in each post's description +// The value is then used to order the posts +*[_type == "post"] + | score(description match "GROQ") + | order(_score desc) + { _score, title } + +// boost() adds a defined boost integer to scores of items matching a condition +// Adds 1 to the score for each time $term is matched in the title field +// Adds 3 to the score if (movie > 3) is true +*[_type == "movie" && movieRating > 3] | + score( + title match $term, + boost(movieRating > 8, 3) + ) + +// Creates a scoring system where $term matching in the title +// is worth more than matching in the body +*[_type == "movie" && movieRating > 3] | score( + boost(title match $term, 4), + boost(body match $term, 1) +) + +// Returns the body Portable Text data as plain text +*[_type == "post"] + { "plaintextBody": pt::text(body) } + +// Get all versions and drafts of a document. Use with the raw perspective or a perspective stack to ensure accurate results. +*[sanity::versionOf('document-id')] + +// Get all documents that are part of a release. Use with the raw perspective to ensure accurate results. +*[sanity::partOfRelease('release-id')] +``` + +## Geolocation + +```groq +// Returns all documents that are storefronts +// within 10 miles of the user-provided currentLocation parameter +*[ + _type == 'storefront' && + geo::distance(geoPoint, $currentLocation) < 16093.4 +] + +// For a given $currentLocation geopoint and deliveryZone area +// Return stores that deliver to a user's location +*[ + _type == "storefront" && + geo::contains(deliveryZone, $currentLocation) +] + +// Creates a "marathonRoutes" array that contains +// all marathons whose routes intersect with the current neighborhood +*[_type == "neighborhood"] { + "marathonRoutes": *[_type == "marathon" && + geo::intersects(^.neighborhoodRegion, routeLine) + ] +} +``` + +## Arithmetic and Concatenation + +```groq +// Standard arithmetic operations are supported +1 + 2 // 3 (addition) +3 - 2 // 1 (subtraction) +2 * 3 // 6 (multiplication) +8 / 4 // 2 (division) +2 ** 4 // 16 (exponentiation) +8 % 3 // 2 (modulo) + +// Exponentiation can be used to take square- and cube-roots too +9 ** (1/2) // 3 (square root) +27 ** (1/3) // 3 (cube root) + +// + can also concatenate strings, arrays, and objects: +"abc" + "def" // "abcdef" +[1,2] + [3,4] // [1,2,3,4] +{"a":1,"b":2} + {"c":3} // {"a":1,"b":2,"c":3} + +// Concatenation of a string and a number requires the number be +// converted to a string. Otherwise, the operation returns null +3 + " p.m." // null +string(3) + " p.m." // "3 p.m." +``` + + + diff --git a/website/docs/API/groq-cheat-sheet.md b/website/docs/API/groq-cheat-sheet.md new file mode 100644 index 00000000..0ad268d1 --- /dev/null +++ b/website/docs/API/groq-cheat-sheet.md @@ -0,0 +1,566 @@ +# Query Cheat Sheet - GROQD + +Here are some typical queries in GroqD (TypeScript). +These examples were adapted from the [GROQ Cheat Sheet](https://www.sanity.io/docs/query-cheat-sheet) + + + +## Filters + +> [!TIP] +> Protip +> You will get null as a value on a query if the key you ask for doesn't exist. That means you can filter on key != null to check if it exists with a value or not. + +```typescript +// Everything, i.e. all documents +q.star + +// Everything with no filters applied, i.e. all documents +q.star + +// All movie documents +q.star.filterByType("movie") + +// _id equals +q.star.filter('_id == "abc.123"') + +// _type is movie or person +q.star.filter('_type in ["movie", "person"]') + +// multiple filters AND +q.star.filter('_type == "movie" && popularity > 15 && releaseDate > "2016-04-25"') + +// multiple filters OR +q.star.filter('_type == "movie" && (popularity > 15 || releaseDate > "2016-04-25")') + +// less than +q.star.filter('popularity < 15') + +// greater than +q.star.filter('popularity > 15') + +// less than or equal +q.star.filter('popularity <= 15') + +// greater than or equal +q.star.filter('popularity >= 15') + +// equal +q.star.filter('popularity == 15') + +// not equal +q.star.filter('releaseDate != "2016-04-27"') + +// not equal (alternative) +q.star.filter('!(releaseDate == "2016-04-27")') + +// even equal via double negatives "not not equal" +q.star.filter('!(releaseDate != "2016-04-27")') + +// Use zulu-time when comparing datetimes to strings +q.star.filter('dateTime(_updatedAt) > dateTime("2018-04-20T20:43:31Z")') + +// Updated within the past week +q.star.filter('dateTime(_updatedAt) > dateTime(now()) - 60*60*24*7') + +// Records whose name precedes "Baker" alphabetically +q.star.filter('name < "Baker"') + +// match boolean +q.star.filter('awardWinner == true') + +// true if awardWinner == true +q.star.filter('awardWinner') + +// true if awardWinner == false +q.star.filter('!awardWinner') + +// has been assigned an award winner status (any kind of value) +q.star.filter('defined(awardWinner)') + +// has not been assigned an award winner status (any kind of value) +q.star.filter('!defined(awardWinner)') + +// title equals +q.star.filter('title == "Aliens"') + +// title in list +q.star.filter('title in ["Aliens", "Interstellar", "Passengers"]') + +// _id matches a.b.c.d but not a.b.c.d.e +q.star.filter('_id in path("a.b.c.*")') + +// _id matches a.b.c.d, and also a.b.c.d.e.f.g, but not a.b.x.1 +q.star.filter('_id in path("a.b.c.**")') + +// _id matches anything that is not under the a.b.c path or deeper +q.star.filter('!(_id in path("a.b.c.**"))') + +// documents that have the string "yolo" in the array "tags" +q.star.filter('"yolo" in tags') + +// the string field status is either == "completed" or "archived" +q.star.filter('status in ["completed", "archived"]') + +// Any document having a castMember referencing sigourney as its person +q.star.filter('"person_sigourney-weaver" in castMembers[].person._ref') + +// nested properties +q.star.filter('slug.current == "some-slug"') + +// documents that reference categories with slugs of "action" or "thriller" +q.star.filter('count((categories[]->slug.current)[@ in ["action", "thriller"]]) > 0') + +// documents that reference categories with slugs of "action" and "thriller" +q.star.filter('count((categories[]->slug.current)[@ in ["action", "thriller"]]) == 2') +``` + +## Text matching + +> [!WARNING] +> Gotcha +> The match operator is designed for human-language text and might not do what you expect! + +```typescript +// Text contains the word "word" +q.star.filter('text match "word"') + +// Title contains a word starting with "wo" +q.star.filter('title match "wo*"') + +// Inverse of the previous query; animal matches the start of the word "caterpillar" +q.star.filter('"caterpillar" match animal + "*"') + +// Title and body combined contains a word starting with "wo" and the full word "zero" +q.star.filter('[title, body] match ["wo*", "zero"]') + +// Are there aliens in my rich text? +q.star.filter('body[].children[].text match "aliens"') + +// Note: match operates on tokens! +``` + +## Slice Operations + +> [!TIP] +> Protip +> There is no default limit, meaning that if you're not explicit about slice, you'll get everything. + +```typescript +// a single movie (an object is returned, not an array) +q.star.filterByType("movie").slice(0) + +// first 6 movies (inclusive) +q.star.filterByType("movie").slice(0, 5, true) + +// first 5 movies (non-inclusive) +q.star.filterByType("movie").slice(0, 5) + +// first 10 movie titles +q.star.filterByType("movie").project({ title: true }).slice(0, 10) + +// first 10 movie titles (alternative order) +q.star.filterByType("movie").slice(0, 10).project({ title: true }) + +// first 10 movie titles, offset by 10 +q.star.filterByType("movie").slice(10, 20).project({ title: true }) + +// all movies are returned (no slice specified) +q.star.filterByType("movie") +``` + +**Also note**: The above queries don't make much sense without also specifying an order. E.g. the "first 6 movies" query only returns "first" movies in the sense that these are the first six movies the backend happens to pull out. + +## Ordering + +> [!TIP] +> Protip +> Documents are returned by default in ascending order by _id, which may not be what you're after. If you're querying for a subset of your documents, it's usually a good idea to specify an order. +> +> No matter what sort order is specified, the ascending order by _id will always remain the final tie-breaker. + +```typescript +// order results +q.star.filterByType("movie").order("_createdAt asc") + +// order results by multiple attributes +q.star.filterByType("movie").order("releaseDate desc").order("_createdAt asc") + +// order todo items by descending priority, then most recently updated +q.star.filterByType("todo").order("priority desc, _updatedAt desc") + +// the single, oldest document +q.star.filterByType("movie").order("_createdAt asc").slice(0) + +// the single, newest document +q.star.filterByType("movie").order("_createdAt desc").slice(0) + +// oldest 10 documents +q.star.filterByType("movie").order("_createdAt asc").slice(0, 10) + +// BEWARE! This selects 10 documents using the default ordering, and *only the selection* is ordered by _createdAt in ascending order +q.star.filterByType("movie").slice(0, 10).order("_createdAt asc") + +// limit/offset using external params (see client documentation) +q.star.filterByType("movie").order("_createdAt asc").slice("$start", "$end") + +// order results alphabetically by a string field +q.star.filterByType("movie").order("title asc") + +// order results alphabetically by a string field, ignoring case +q.star.filterByType("movie").order("lower(title) asc") +``` + +## Joins + +```typescript +// Fetch movies with title, and join with poster asset with path + url +q.star.filterByType("movie").project({ + title: true, + poster: q.field("poster").project({ + asset: q.field("asset").deref().project({ + path: true, + url: true, + }), + }), +}) + +// Say castMembers is an array containing objects with character name and a reference to the person: +// We want to fetch movie with title and an attribute named "cast" which is an array of actor names +q.star.filterByType("movie").project({ + title: true, + cast: q.field("castMembers[]").field("person").deref().field("name"), +}) + +// Same query as above, except "cast" now contains objects with person._id and person.name +q.star.filterByType("movie").project({ + title: true, + cast: q.field("castMembers[]").field("person").deref().project({ + _id: true, + name: true, + }), +}) + +// Using the ^ operator to refer to the enclosing document. Here ^._id refers to the id +// of the enclosing person record. +q.star.filterByType("person").project((q) => ({ + name: true, + relatedMovies: q + .groq('*[_type=="movie" && references(^._id)]') + .project({ title: true }), +})) + +// Books by author.name (book.author is a reference) +q.star + .filter('_type == "book" && author._ref in *[_type=="author" && name=="John Doe"]._id') + .project({ /* ... */ }) +``` + +## Objects and Arrays + +```typescript +// Create your own objects +const qPeopleByPrizeYear = q.star.order("prizes[0].year desc").project({ + name: q.field("firstname").groq('+ " " +').field("surname"), + orderYear: q.field("prizes[0].year"), + prizes: true, +}) + +const qAllPrizes = q.star.field("prizes[]").order("year desc") + +// Get all Nobel prizes from all root person documents +q.star.field("prizes[]") + +// Array helpers (use groq functions via .groq()) +q.field("tags").groq('array::join(@, ", ")') +q.groq('array::join(["a", "b", "c"], ".")') +q.field("year").groq('array::join(@, ".")') +q.field("values").groq('array::join(@, 1)') +q.field("numbers").groq('array::compact(@)') +q.field("items").groq('array::unique(@)') +q.field("records").groq('array::unique(@)') +q.groq('array::intersects(firstList, secondList)') +q.groq('array::intersects(tags, keywords)') +``` + +## Object Projections + +```typescript +// return only title +q.star.filterByType("movie").project({ title: true }) + +// return values for multiple attributes +q.star.filterByType("movie").project({ _id: true, _type: true, title: true }) + +// explicitly name the return field for _id +q.star.filterByType("movie").project({ renamedId: "._id", _type: true, title: true }) + +// Return an array of attribute values (no object wrapper) +q.star.filterByType("movie").field("title") +q.star.filterByType("movie").project({ characterNames: q.field("castMembers[]").field("characterName") }) + +// movie titled Arrival and its posterUrl +q.star.filter('_type=="movie" && title == "Arrival"').project({ + title: true, + posterUrl: q.field("poster").field("asset").deref().field("url"), +}) + +// Explicitly return all attributes +q.star.filterByType("movie").project({ "...": true }) + +// Some computed attributes, then also add all attributes of the result +q.star.filterByType("movie").project({ + posterUrl: q.field("poster").field("asset").deref().field("url"), + "...": true, +}) + +// Default values when missing or null in document +q.star.filterByType("movie").project({ + "...": true, + rating: q.groq('coalesce(rating, "unknown")'), +}) + +// Number of elements in array 'actors' on each movie +q.star.filterByType("movie").project({ + actorCount: q.groq('count(actors)'), +}) + +// Apply a projection to every member of an array +q.star.filterByType("movie").project({ + castMembers: q.field("castMembers[]").project({ + characterName: true, + person: true, + }), +}) + +// Filter embedded objects +q.star.filterByType("movie").project({ + castMembers: q.field('castMembers[characterName match "Ripley"]').project({ + characterName: true, + person: true, + }), +}) + +// Follow every reference in an array of references +q.star.filterByType("book").project({ + authors: q.field("authors[]").deref().project({ + name: true, + bio: true, + }), +}) + +// Explicity name the outer return field +qRoot.project({ + threeMovieTitles: q.star.filterByType("movie").slice(0, 2).field("title"), +}) + +// Combining several unrelated queries in one request +qRoot.project({ + featuredMovie: q.star.filter('_type == "movie" && title == "Alien"').slice(0), + scifiMovies: q.star.filter('_type == "movie" && "sci-fi" in genres'), +}) +``` + +## Special variables + +```typescript +// * (everything) +q.star + +// @ (root value of the scope) +q.star.filter('@["1"]') +q.star.filter('@[$prop]._ref == $refId') +q.project({ + arraySizes: q.field("arrays[]").project({ + size: q.groq("count(@)"), + }), +}) + +// ^ (enclosing document) +q.star.filterByType("person").project((q) => ({ + name: true, + relatedMovies: q + .groq('*[_type=="movie" && references(^._id)]') + .project({ title: true }), +})) +``` + +## Conditionals + +```typescript +// select() returns the first => pair whose left-hand side evaluates to true +q.star.filterByType("movie").project({ + "...": true, + popularity: q.groq('select(popularity > 20 => "high", popularity > 10 => "medium", popularity <= 10 => "low")'), +}) + +// The first select() parameter without => is returned if no previous matches are found +q.star.filterByType("movie").project({ + "...": true, + popularity: q.groq('select(popularity > 20 => "high", popularity > 10 => "medium", "low")'), +}) + +// Projections also have syntactic sugar for inline conditionals +q.star.filterByType("movie").project((q) => ({ + "...": true, + ...q.groq(`releaseDate >= '2018-06-01' => { + "screenings": *[_type == 'screening' && movie._ref == ^._id], + "news": *[_type == 'news' && movie._ref == ^._id], + }`), + ...q.groq(`popularity > 20 && rating > 7.0 => { + "featured": true, + "awards": *[_type == 'award' && movie._ref == ^._id], + }`), +})) + +// The above is exactly equivalent to: +q.star.filterByType("movie").project((q) => ({ + "...": true, + ...q.groq(`...select(releaseDate >= '2018-06-01' => { + "screenings": *[_type == 'screening' && movie._ref == ^._id], + "news": *[_type == 'news' && movie._ref == ^._id], + })`), + ...q.groq(`...select(popularity > 20 && rating > 7.0 => { + "featured": true, + "awards": *[_type == 'award' && movie._ref == ^._id], + })`), +})) + +// Specify sets of projections for different content types in an array +q.field("content[]").project((q) => ({ + ...q.groq(`_type == 'type1' => { + // Your selection of fields for type1 + }, + _type == 'type2' => { + // Your selection of fields for type2 + "url": file.asset->url + }`), +})) +``` + +### Handling references conditionally + +In cases where an array contains both [references and non-references](https://www.sanity.io/docs/array-type#wT47gyCx), it's often desirable for a groqd query to conditionally return the inline object (where dealing with non-references) or the referenced document (where dealing with references). This can be done by considering the `_type` of each array item and dereferencing the item (`@->`) if it's a reference or getting the whole object (`@`) if it's not a reference. + +```typescript +q.project({ + content: q.field("content[]").project((q) => ({ + ...q.groq(`_type == 'reference' => @->, _type != 'reference' => @`), + })), +}) +``` + +## Functions + +```typescript +// any document that references the document with id person_sigourney-weaver, return only title +q.star.filter('references("person_sigourney-weaver")').project({ title: true }) + +// Movies which reference ancient people +q.star.filter('_type=="movie" && references(*[_type=="person" && age > 99]._id)').project({ title: true }) + +// any document that has the attribute 'tags' +q.star.filter('defined(tags)') + +// coalesce takes a number of attribute references and returns the value of the first attribute that is non-null +q.project({ + title: q.groq('coalesce(title.fi, title.en)'), +}) + +// count counts the number of items in a collection +q.groq('count(*[_type == "movie" && rating == "R"])') + +// Counts the number of elements in the array actors +q.star.filterByType("movie").project({ + title: true, + actorCount: q.groq('count(actors)'), +}) + +// round() rounds number to the nearest integer, or the given number of decimals +q.groq('round(3.14)') +q.groq('round(3.14, 1)') + +// score() adds points to the score value depending on the use of the string "GROQ" in each post's description +q.star.filterByType("post") + .score('description match "GROQ"') + .order("_score desc") + .project({ _score: true, title: true }) + +// boost() adds a defined boost integer to scores of items matching a condition +q.star.filter('_type == "movie" && movieRating > 3') + .score('title match $term', 'boost(movieRating > 8, 3)') + +// Creates a scoring system where $term matching in the title is worth more than matching in the body +q.star.filter('_type == "movie" && movieRating > 3') + .score('boost(title match $term, 4)', 'boost(body match $term, 1)') + +// Returns the body Portable Text data as plain text +q.star.filterByType("post").project({ + plaintextBody: q.groq('pt::text(body)'), +}) + +// Get all versions and drafts of a document. Use with the raw perspective or a perspective stack to ensure accurate results. +q.star.filter('sanity::versionOf("document-id")') + +// Get all documents that are part of a release. Use with the raw perspective to ensure accurate results. +q.star.filter('sanity::partOfRelease("release-id")') +``` + +## Geolocation + +```typescript +// Returns all documents that are storefronts within 10 miles of the user-provided currentLocation parameter +q.star.filter('_type == "storefront" && geo::distance(geoPoint, $currentLocation) < 16093.4') + +// For a given $currentLocation geopoint and deliveryZone area +// Return stores that deliver to a user's location +q.star.filter('_type == "storefront" && geo::contains(deliveryZone, $currentLocation)') + +// Creates a "marathonRoutes" array that contains all marathons whose routes intersect with the current neighborhood +q.star.filterByType("neighborhood").project({ + marathonRoutes: q.star.filter('_type == "marathon" && geo::intersects(^.neighborhoodRegion, routeLine)'), +}) +``` + +## Arithmetic and Concatenation + +```typescript +// Standard arithmetic operations are supported +q.groq('1 + 2') // 3 (addition) +q.groq('3 - 2') // 1 (subtraction) +q.groq('2 * 3') // 6 (multiplication) +q.groq('8 / 4') // 2 (division) +q.groq('2 ** 4') // 16 (exponentiation) +q.groq('8 % 3') // 2 (modulo) + +// Exponentiation can be used to take square- and cube-roots too +q.groq('9 ** (1/2)') // 3 (square root) +q.groq('27 ** (1/3)') // 3 (cube root) + +// + can also concatenate strings, arrays, and objects: +q.groq('"abc" + "def"') // "abcdef" +q.groq('[1,2] + [3,4]') // [1,2,3,4] +q.groq('{"a":1,"b":2} + {"c":3}') // {"a":1,"b":2,"c":3} + +// Concatenation of a string and a number requires the number be converted to a string. Otherwise, the operation returns null +q.groq('3 + " p.m."') // null +q.groq('string(3) + " p.m."') // "3 p.m." +``` +```` + + From be6d317813923aa3a27850634781320fb223fb97 Mon Sep 17 00:00:00 2001 From: scottrippey Date: Thu, 29 May 2025 10:33:46 -0600 Subject: [PATCH 02/12] docs(cheat-sheet): improved filter examples --- website/docs/API/groq-cheat-sheet.md | 114 ++++++++++++--------------- 1 file changed, 49 insertions(+), 65 deletions(-) diff --git a/website/docs/API/groq-cheat-sheet.md b/website/docs/API/groq-cheat-sheet.md index 0ad268d1..0b79bcbb 100644 --- a/website/docs/API/groq-cheat-sheet.md +++ b/website/docs/API/groq-cheat-sheet.md @@ -30,104 +30,91 @@ You can also check out [our introduction to GROQ](/docs/content-lake/how-queries // Everything, i.e. all documents q.star -// Everything with no filters applied, i.e. all documents -q.star - // All movie documents q.star.filterByType("movie") // _id equals -q.star.filter('_id == "abc.123"') +q.star.filterBy('_id == "abc.123"') // _type is movie or person -q.star.filter('_type in ["movie", "person"]') +q.star.filterByType("movie", "person") // multiple filters AND -q.star.filter('_type == "movie" && popularity > 15 && releaseDate > "2016-04-25"') +q.star.filterByType("movie").filterBy("popularity > 15").filterBy('releaseDate == "2016-04-25"') // multiple filters OR -q.star.filter('_type == "movie" && (popularity > 15 || releaseDate > "2016-04-25")') - -// less than -q.star.filter('popularity < 15') - -// greater than -q.star.filter('popularity > 15') - -// less than or equal -q.star.filter('popularity <= 15') - -// greater than or equal -q.star.filter('popularity >= 15') +q.star.filterByType("movie").filterBy('popularity > 15', 'releaseDate == "2016-04-25"') -// equal -q.star.filter('popularity == 15') - -// not equal -q.star.filter('releaseDate != "2016-04-27"') - -// not equal (alternative) -q.star.filter('!(releaseDate == "2016-04-27")') - -// even equal via double negatives "not not equal" -q.star.filter('!(releaseDate != "2016-04-27")') +q.star.filterByType("movie") + // less than + .filterBy('popularity < 15') + // greater than + .filterBy('popularity > 15') + // less than or equal + .filterBy('popularity <= 15') + // greater than or equal + .filterBy('popularity >= 15') + // equal + .filterBy('popularity == 15') + // not equal + .filterBy('releaseDate != "2016-04-27"') // Use zulu-time when comparing datetimes to strings -q.star.filter('dateTime(_updatedAt) > dateTime("2018-04-20T20:43:31Z")') +q.star.filterRaw('dateTime(_updatedAt) > dateTime("2018-04-20T20:43:31Z")') // Updated within the past week -q.star.filter('dateTime(_updatedAt) > dateTime(now()) - 60*60*24*7') +q.star.filterRaw('dateTime(_updatedAt) > dateTime(now()) - 60*60*24*7') // Records whose name precedes "Baker" alphabetically -q.star.filter('name < "Baker"') +q.star.filterRaw('name < "Baker"') // match boolean -q.star.filter('awardWinner == true') +q.star.filterBy('awardWinner') // true if awardWinner == true -q.star.filter('awardWinner') +q.star.filterBy('awardWinner') // true if awardWinner == false -q.star.filter('!awardWinner') +q.star.filterBy('!awardWinner') // has been assigned an award winner status (any kind of value) -q.star.filter('defined(awardWinner)') +q.star.filterBy('defined(awardWinner)') // has not been assigned an award winner status (any kind of value) -q.star.filter('!defined(awardWinner)') +q.star.filterBy('!defined(awardWinner)') // title equals -q.star.filter('title == "Aliens"') +q.star.filterBy('title == "Aliens"') // title in list -q.star.filter('title in ["Aliens", "Interstellar", "Passengers"]') +q.star.filterRaw('title in ["Aliens", "Interstellar", "Passengers"]') // _id matches a.b.c.d but not a.b.c.d.e -q.star.filter('_id in path("a.b.c.*")') +q.star.filterRaw('_id in path("a.b.c.*")') // _id matches a.b.c.d, and also a.b.c.d.e.f.g, but not a.b.x.1 -q.star.filter('_id in path("a.b.c.**")') +q.star.filterRaw('_id in path("a.b.c.**")') // _id matches anything that is not under the a.b.c path or deeper -q.star.filter('!(_id in path("a.b.c.**"))') +q.star.filterRaw('!(_id in path("a.b.c.**"))') // documents that have the string "yolo" in the array "tags" -q.star.filter('"yolo" in tags') +q.star.filterRaw('"yolo" in tags') // the string field status is either == "completed" or "archived" -q.star.filter('status in ["completed", "archived"]') +q.star.filterRaw('status in ["completed", "archived"]') // Any document having a castMember referencing sigourney as its person -q.star.filter('"person_sigourney-weaver" in castMembers[].person._ref') +q.star.filterRaw('"person_sigourney-weaver" in castMembers[].person._ref') // nested properties -q.star.filter('slug.current == "some-slug"') +q.star.filterBy('slug.current == "some-slug"') // documents that reference categories with slugs of "action" or "thriller" -q.star.filter('count((categories[]->slug.current)[@ in ["action", "thriller"]]) > 0') +q.star.filterRaw('count((categories[]->slug.current)[@ in ["action", "thriller"]]) > 0') // documents that reference categories with slugs of "action" and "thriller" -q.star.filter('count((categories[]->slug.current)[@ in ["action", "thriller"]]) == 2') +q.star.filterRaw('count((categories[]->slug.current)[@ in ["action", "thriller"]]) == 2') ``` ## Text matching @@ -541,26 +528,23 @@ q.star.filterByType("neighborhood").project({ ```typescript // Standard arithmetic operations are supported -q.groq('1 + 2') // 3 (addition) -q.groq('3 - 2') // 1 (subtraction) -q.groq('2 * 3') // 6 (multiplication) -q.groq('8 / 4') // 2 (division) -q.groq('2 ** 4') // 16 (exponentiation) -q.groq('8 % 3') // 2 (modulo) +q.raw('1 + 2') // 3 (addition) +q.raw('3 - 2') // 1 (subtraction) +q.raw('2 * 3') // 6 (multiplication) +q.raw('8 / 4') // 2 (division) +q.raw('2 ** 4') // 16 (exponentiation) +q.raw('8 % 3') // 2 (modulo) // Exponentiation can be used to take square- and cube-roots too -q.groq('9 ** (1/2)') // 3 (square root) -q.groq('27 ** (1/3)') // 3 (cube root) +q.raw('9 ** (1/2)') // 3 (square root) +q.raw('27 ** (1/3)') // 3 (cube root) // + can also concatenate strings, arrays, and objects: -q.groq('"abc" + "def"') // "abcdef" -q.groq('[1,2] + [3,4]') // [1,2,3,4] -q.groq('{"a":1,"b":2} + {"c":3}') // {"a":1,"b":2,"c":3} +q.raw('"abc" + "def"') // "abcdef" +q.raw('[1,2] + [3,4]') // [1,2,3,4] +q.raw<{a:number,b:number,c:number}>('{"a":1,"b":2} + {"c":3}') // {"a":1,"b":2,"c":3} // Concatenation of a string and a number requires the number be converted to a string. Otherwise, the operation returns null -q.groq('3 + " p.m."') // null -q.groq('string(3) + " p.m."') // "3 p.m." +q.raw('3 + " p.m."') // null +q.raw('string(3) + " p.m."') // "3 p.m." ``` -```` - - From f8b1b836389eef3bfe228a716c771b971b092750 Mon Sep 17 00:00:00 2001 From: scottrippey Date: Thu, 29 May 2025 10:56:08 -0600 Subject: [PATCH 03/12] docs(cheat-sheet): updated filters --- website/docs/API/groq-cheat-sheet.md | 176 +++++++++------------------ 1 file changed, 58 insertions(+), 118 deletions(-) diff --git a/website/docs/API/groq-cheat-sheet.md b/website/docs/API/groq-cheat-sheet.md index 0b79bcbb..8edf41a4 100644 --- a/website/docs/API/groq-cheat-sheet.md +++ b/website/docs/API/groq-cheat-sheet.md @@ -3,28 +3,13 @@ Here are some typical queries in GroqD (TypeScript). These examples were adapted from the [GROQ Cheat Sheet](https://www.sanity.io/docs/query-cheat-sheet) - +### `filterByType(_type, ..._type)` -## Filters +Use `filterByType` to retrieve the appropriate documents. +The `_type` is a **strongly-typed** string, representing one of your document types. -> [!TIP] -> Protip -> You will get null as a value on a query if the key you ask for doesn't exist. That means you can filter on key != null to check if it exists with a value or not. ```typescript // Everything, i.e. all documents @@ -45,76 +30,50 @@ q.star.filterByType("movie").filterBy("popularity > 15").filterBy('releaseDate = // multiple filters OR q.star.filterByType("movie").filterBy('popularity > 15', 'releaseDate == "2016-04-25"') -q.star.filterByType("movie") - // less than - .filterBy('popularity < 15') - // greater than - .filterBy('popularity > 15') - // less than or equal - .filterBy('popularity <= 15') - // greater than or equal - .filterBy('popularity >= 15') - // equal - .filterBy('popularity == 15') - // not equal - .filterBy('releaseDate != "2016-04-27"') - -// Use zulu-time when comparing datetimes to strings -q.star.filterRaw('dateTime(_updatedAt) > dateTime("2018-04-20T20:43:31Z")') - -// Updated within the past week -q.star.filterRaw('dateTime(_updatedAt) > dateTime(now()) - 60*60*24*7') - -// Records whose name precedes "Baker" alphabetically -q.star.filterRaw('name < "Baker"') - -// match boolean -q.star.filterBy('awardWinner') - -// true if awardWinner == true -q.star.filterBy('awardWinner') - -// true if awardWinner == false -q.star.filterBy('!awardWinner') - -// has been assigned an award winner status (any kind of value) -q.star.filterBy('defined(awardWinner)') - -// has not been assigned an award winner status (any kind of value) -q.star.filterBy('!defined(awardWinner)') - -// title equals -q.star.filterBy('title == "Aliens"') - -// title in list -q.star.filterRaw('title in ["Aliens", "Interstellar", "Passengers"]') - -// _id matches a.b.c.d but not a.b.c.d.e -q.star.filterRaw('_id in path("a.b.c.*")') - -// _id matches a.b.c.d, and also a.b.c.d.e.f.g, but not a.b.x.1 -q.star.filterRaw('_id in path("a.b.c.**")') +``` -// _id matches anything that is not under the a.b.c path or deeper -q.star.filterRaw('!(_id in path("a.b.c.**"))') +### `filterBy(expression, ...expression)` -// documents that have the string "yolo" in the array "tags" -q.star.filterRaw('"yolo" in tags') +Use `filterBy` to filter the documents. The `expression` is a **strongly-typed** string. -// the string field status is either == "completed" or "archived" -q.star.filterRaw('status in ["completed", "archived"]') +> This method only supports basic GROQ expressions, like `slug.current == "abc"` or `value > 10`. +> Use `filterRaw` for more complex expressions. -// Any document having a castMember referencing sigourney as its person -q.star.filterRaw('"person_sigourney-weaver" in castMembers[].person._ref') +```typescript +q.star.filterByType("movie") // gives us a strongly-typed `.filterBy` method + .filterBy('popularity < 15') // less than + .filterBy('popularity > 15') // greater than + .filterBy('popularity <= 15') // less than or equal + .filterBy('popularity >= 15') // greater than or equal + .filterBy('popularity == 15') // equal + .filterBy('releaseDate != "2016-04-27"') // not equal + .filterBy('awardWinner') // match boolean + .filterBy('awardWinner') // true if awardWinner == true + .filterBy('!awardWinner') // true if awardWinner == false + .filterBy('defined(awardWinner)') // has been assigned an award winner status (any kind of value) + .filterBy('!defined(awardWinner)') // has not been assigned an award winner status (any kind of value) + .filterBy('title == "Aliens"') // title equals + .filterBy('slug.current == "some-slug"') // nested properties +``` -// nested properties -q.star.filterBy('slug.current == "some-slug"') +### `filterRaw(expression, ...expression)` +Use `filterRaw` for expressions that are more complex than `filterBy` supports. -// documents that reference categories with slugs of "action" or "thriller" -q.star.filterRaw('count((categories[]->slug.current)[@ in ["action", "thriller"]]) > 0') +```typescript -// documents that reference categories with slugs of "action" and "thriller" -q.star.filterRaw('count((categories[]->slug.current)[@ in ["action", "thriller"]]) == 2') +q.star.filterByType("movie") + .filterRaw('dateTime(_updatedAt) > dateTime("2018-04-20T20:43:31Z")') // Use zulu-time when comparing datetimes to strings + .filterRaw('dateTime(_updatedAt) > dateTime(now()) - 60*60*24*7') // Updated within the past week + .filterRaw('name < "Baker"') // Records whose name precedes "Baker" alphabetically + .filterRaw('title in ["Aliens", "Interstellar", "Passengers"]') // title in list + .filterRaw('_id in path("a.b.c.*")') // _id matches a.b.c.d but not a.b.c.d.e + .filterRaw('_id in path("a.b.c.**")') // _id matches a.b.c.d, and also a.b.c.d.e.f.g, but not a.b.x.1 + .filterRaw('!(_id in path("a.b.c.**"))') // _id matches anything that is not under the a.b.c path or deeper + .filterRaw('"yolo" in tags') // documents that have the string "yolo" in the array "tags" + .filterRaw('status in ["completed", "archived"]') // the string field status is either == "completed" or "archived" + .filterRaw('"person_sigourney-weaver" in castMembers[].person._ref') // Any document having a castMember referencing sigourney as its person + .filterRaw('count((categories[]->slug.current)[@ in ["action", "thriller"]]) > 0') // documents that reference categories with slugs of "action" or "thriller" + .filterRaw('count((categories[]->slug.current)[@ in ["action", "thriller"]]) == 2') // documents that reference categories with slugs of "action" and "thriller" ``` ## Text matching @@ -123,23 +82,17 @@ q.star.filterRaw('count((categories[]->slug.current)[@ in ["action", "thriller"] > Gotcha > The match operator is designed for human-language text and might not do what you expect! -```typescript -// Text contains the word "word" -q.star.filter('text match "word"') - -// Title contains a word starting with "wo" -q.star.filter('title match "wo*"') - -// Inverse of the previous query; animal matches the start of the word "caterpillar" -q.star.filter('"caterpillar" match animal + "*"') +GroqD does not have strongly-typed support for text matching, so simply use `.filterRaw`: -// Title and body combined contains a word starting with "wo" and the full word "zero" -q.star.filter('[title, body] match ["wo*", "zero"]') - -// Are there aliens in my rich text? -q.star.filter('body[].children[].text match "aliens"') - -// Note: match operates on tokens! +```typescript +q.star.filterRaw('text match "word"') // Text contains the word "word" +q.star.filterRaw('title match "wo*"') // Title contains a word starting with "wo" +q.star.filterRaw('"caterpillar" match animal + "*"') // Inverse of the previous query; animal matches the start of the word "caterpillar" +q.star.filterRaw('[title, body] match ["wo*", "zero"]') // Title and body combined contains a word starting with "wo" and the full word "zero" +q.star.filterRaw('body[].children[].text match "aliens"') // Are there aliens in my rich text? +// Note how match operates on tokens! +q.star.filterRaw('"foo bar" match "fo*"') // -> true +q.star.filterRaw('"my-pretty-pony-123.jpg" match "my*.jpg" ') // -> false ``` ## Slice Operations @@ -149,26 +102,13 @@ q.star.filter('body[].children[].text match "aliens"') > There is no default limit, meaning that if you're not explicit about slice, you'll get everything. ```typescript -// a single movie (an object is returned, not an array) -q.star.filterByType("movie").slice(0) - -// first 6 movies (inclusive) -q.star.filterByType("movie").slice(0, 5, true) - -// first 5 movies (non-inclusive) -q.star.filterByType("movie").slice(0, 5) - -// first 10 movie titles -q.star.filterByType("movie").project({ title: true }).slice(0, 10) - -// first 10 movie titles (alternative order) -q.star.filterByType("movie").slice(0, 10).project({ title: true }) - -// first 10 movie titles, offset by 10 -q.star.filterByType("movie").slice(10, 20).project({ title: true }) - -// all movies are returned (no slice specified) -q.star.filterByType("movie") +q.star.filterByType("movie").slice(0) // a single movie (an object is returned, not an array) +q.star.filterByType("movie").slice(0, 5, true) // first 6 movies (inclusive) +q.star.filterByType("movie").slice(0, 5) // first 5 movies (non-inclusive) +q.star.filterByType("movie").project({ title: true }).slice(0, 10) // first 10 movie titles +q.star.filterByType("movie").slice(0, 10).project({ title: true }) // first 10 movie titles (alternative order) +q.star.filterByType("movie").slice(10, 20).project({ title: true }) // first 10 movie titles, offset by 10 +q.star.filterByType("movie") // all movies are returned (no slice specified) ``` **Also note**: The above queries don't make much sense without also specifying an order. E.g. the "first 6 movies" query only returns "first" movies in the sense that these are the first six movies the backend happens to pull out. From 7294131985aad227db93f6f4c34227174432f3c3 Mon Sep 17 00:00:00 2001 From: scottrippey Date: Thu, 29 May 2025 12:17:15 -0600 Subject: [PATCH 04/12] docs(cheat-sheet): updated q.groq to q.raw --- website/docs/API/groq-cheat-sheet.md | 142 ++++++++++++--------------- 1 file changed, 64 insertions(+), 78 deletions(-) diff --git a/website/docs/API/groq-cheat-sheet.md b/website/docs/API/groq-cheat-sheet.md index 8edf41a4..889ad04b 100644 --- a/website/docs/API/groq-cheat-sheet.md +++ b/website/docs/API/groq-cheat-sheet.md @@ -122,79 +122,61 @@ q.star.filterByType("movie") // all movies are returned (no slice specified) > No matter what sort order is specified, the ascending order by _id will always remain the final tie-breaker. ```typescript -// order results -q.star.filterByType("movie").order("_createdAt asc") - -// order results by multiple attributes -q.star.filterByType("movie").order("releaseDate desc").order("_createdAt asc") - -// order todo items by descending priority, then most recently updated -q.star.filterByType("todo").order("priority desc, _updatedAt desc") - -// the single, oldest document -q.star.filterByType("movie").order("_createdAt asc").slice(0) - -// the single, newest document -q.star.filterByType("movie").order("_createdAt desc").slice(0) - -// oldest 10 documents -q.star.filterByType("movie").order("_createdAt asc").slice(0, 10) - -// BEWARE! This selects 10 documents using the default ordering, and *only the selection* is ordered by _createdAt in ascending order -q.star.filterByType("movie").slice(0, 10).order("_createdAt asc") - -// limit/offset using external params (see client documentation) -q.star.filterByType("movie").order("_createdAt asc").slice("$start", "$end") - -// order results alphabetically by a string field -q.star.filterByType("movie").order("title asc") - -// order results alphabetically by a string field, ignoring case -q.star.filterByType("movie").order("lower(title) asc") +q.star.filterByType("movie").order("_createdAt asc") // order results +q.star.filterByType("movie").order("releaseDate desc").order("_createdAt asc") // order results by multiple attributes +q.star.filterByType("todo").order("priority desc, _updatedAt desc") // order todo items by descending priority, then most recently updated +q.star.filterByType("movie").order("_createdAt asc").slice(0) // the single, oldest document +q.star.filterByType("movie").order("_createdAt desc").slice(0) // the single, newest document +q.star.filterByType("movie").order("_createdAt asc").slice(0, 10) // oldest 10 documents +q.star.filterByType("movie").slice(0, 10).order("_createdAt asc") // BEWARE! This selects 10 documents using the default ordering, and *only the selection* is ordered by _createdAt in ascending order +q.star.filterByType("movie").order("_createdAt asc").slice("$start", "$end") // limit/offset using external params (see client documentation) +q.star.filterByType("movie").order("title asc") // order results alphabetically by a string field +q.star.filterByType("movie").order("lower(title) asc") // order results alphabetically by a string field, ignoring case ``` ## Joins ```typescript // Fetch movies with title, and join with poster asset with path + url -q.star.filterByType("movie").project({ +q.star.filterByType("movie").project(q => ({ title: true, - poster: q.field("poster").project({ + poster: q.field("poster").project(q => ({ asset: q.field("asset").deref().project({ path: true, url: true, }), - }), -}) + })), +})) // Say castMembers is an array containing objects with character name and a reference to the person: // We want to fetch movie with title and an attribute named "cast" which is an array of actor names -q.star.filterByType("movie").project({ +q.star.filterByType("movie").project(q => ({ title: true, - cast: q.field("castMembers[]").field("person").deref().field("name"), -}) + cast: q.field("castMembers[].person").deref().field("name"), +})) // Same query as above, except "cast" now contains objects with person._id and person.name -q.star.filterByType("movie").project({ +q.star.filterByType("movie").project(q => ({ title: true, - cast: q.field("castMembers[]").field("person").deref().project({ + cast: q.field("castMembers[].person").deref().project({ _id: true, name: true, }), -}) +})) // Using the ^ operator to refer to the enclosing document. Here ^._id refers to the id // of the enclosing person record. -q.star.filterByType("person").project((q) => ({ +q.star.filterByType("person").project(q => ({ name: true, - relatedMovies: q - .groq('*[_type=="movie" && references(^._id)]') + relatedMovies: q.star + .filterByType("movie") + .filterBy("references(^._id)") .project({ title: true }), })) // Books by author.name (book.author is a reference) -q.star - .filter('_type == "book" && author._ref in *[_type=="author" && name=="John Doe"]._id') +q.star.filterByType("book") + .filterRaw('author._ref in *[_type=="author" && name=="John Doe"]._id') .project({ /* ... */ }) ``` @@ -202,27 +184,31 @@ q.star ```typescript // Create your own objects -const qPeopleByPrizeYear = q.star.order("prizes[0].year desc").project({ - name: q.field("firstname").groq('+ " " +').field("surname"), - orderYear: q.field("prizes[0].year"), - prizes: true, -}) +q.project(q => ({ + // People ordered by Nobel prize year + peopleByPrizeYear: q.star.order("prizes[0].year desc").project(q => ({ + name: q.raw('firstname + " " + surname'), + orderYear: q.field("prizes[0].year"), + prizes: true + })), + // List of all prizes ordered by year awarded + allPrizes: q.star.field("prizes[]").order("year desc") +})) -const qAllPrizes = q.star.field("prizes[]").order("year desc") // Get all Nobel prizes from all root person documents q.star.field("prizes[]") -// Array helpers (use groq functions via .groq()) -q.field("tags").groq('array::join(@, ", ")') -q.groq('array::join(["a", "b", "c"], ".")') -q.field("year").groq('array::join(@, ".")') -q.field("values").groq('array::join(@, 1)') -q.field("numbers").groq('array::compact(@)') -q.field("items").groq('array::unique(@)') -q.field("records").groq('array::unique(@)') -q.groq('array::intersects(firstList, secondList)') -q.groq('array::intersects(tags, keywords)') +// Array helpers (use groq functions via .raw()) +q.field("tags").raw('array::join(@, ", ")') +q.raw('array::join(["a", "b", "c"], ".")') +q.field("year").raw('array::join(@, ".")') +q.field("values").raw('array::join(@, 1)') +q.field("numbers").raw('array::compact(@)') +q.field("items").raw('array::unique(@)') +q.field("records").raw('array::unique(@)') +q.raw('array::intersects(firstList, secondList)') +q.raw('array::intersects(tags, keywords)') ``` ## Object Projections @@ -259,12 +245,12 @@ q.star.filterByType("movie").project({ // Default values when missing or null in document q.star.filterByType("movie").project({ "...": true, - rating: q.groq('coalesce(rating, "unknown")'), + rating: q.raw('coalesce(rating, "unknown")'), }) // Number of elements in array 'actors' on each movie q.star.filterByType("movie").project({ - actorCount: q.groq('count(actors)'), + actorCount: q.raw('count(actors)'), }) // Apply a projection to every member of an array @@ -314,7 +300,7 @@ q.star.filter('@["1"]') q.star.filter('@[$prop]._ref == $refId') q.project({ arraySizes: q.field("arrays[]").project({ - size: q.groq("count(@)"), + size: q.raw("count(@)"), }), }) @@ -322,7 +308,7 @@ q.project({ q.star.filterByType("person").project((q) => ({ name: true, relatedMovies: q - .groq('*[_type=="movie" && references(^._id)]') + .star.filterByType("movie").filterBy("references(^._id)") .project({ title: true }), })) ``` @@ -333,23 +319,23 @@ q.star.filterByType("person").project((q) => ({ // select() returns the first => pair whose left-hand side evaluates to true q.star.filterByType("movie").project({ "...": true, - popularity: q.groq('select(popularity > 20 => "high", popularity > 10 => "medium", popularity <= 10 => "low")'), + popularity: q.raw('select(popularity > 20 => "high", popularity > 10 => "medium", popularity <= 10 => "low")'), }) // The first select() parameter without => is returned if no previous matches are found q.star.filterByType("movie").project({ "...": true, - popularity: q.groq('select(popularity > 20 => "high", popularity > 10 => "medium", "low")'), + popularity: q.raw('select(popularity > 20 => "high", popularity > 10 => "medium", "low")'), }) // Projections also have syntactic sugar for inline conditionals q.star.filterByType("movie").project((q) => ({ "...": true, - ...q.groq(`releaseDate >= '2018-06-01' => { + ...q.raw(`releaseDate >= '2018-06-01' => { "screenings": *[_type == 'screening' && movie._ref == ^._id], "news": *[_type == 'news' && movie._ref == ^._id], }`), - ...q.groq(`popularity > 20 && rating > 7.0 => { + ...q.raw(`popularity > 20 && rating > 7.0 => { "featured": true, "awards": *[_type == 'award' && movie._ref == ^._id], }`), @@ -358,11 +344,11 @@ q.star.filterByType("movie").project((q) => ({ // The above is exactly equivalent to: q.star.filterByType("movie").project((q) => ({ "...": true, - ...q.groq(`...select(releaseDate >= '2018-06-01' => { + ...q.raw(`...select(releaseDate >= '2018-06-01' => { "screenings": *[_type == 'screening' && movie._ref == ^._id], "news": *[_type == 'news' && movie._ref == ^._id], })`), - ...q.groq(`...select(popularity > 20 && rating > 7.0 => { + ...q.raw(`...select(popularity > 20 && rating > 7.0 => { "featured": true, "awards": *[_type == 'award' && movie._ref == ^._id], })`), @@ -370,7 +356,7 @@ q.star.filterByType("movie").project((q) => ({ // Specify sets of projections for different content types in an array q.field("content[]").project((q) => ({ - ...q.groq(`_type == 'type1' => { + ...q.raw(`_type == 'type1' => { // Your selection of fields for type1 }, _type == 'type2' => { @@ -387,7 +373,7 @@ In cases where an array contains both [references and non-references](https://ww ```typescript q.project({ content: q.field("content[]").project((q) => ({ - ...q.groq(`_type == 'reference' => @->, _type != 'reference' => @`), + ...q.raw(`_type == 'reference' => @->, _type != 'reference' => @`), })), }) ``` @@ -406,21 +392,21 @@ q.star.filter('defined(tags)') // coalesce takes a number of attribute references and returns the value of the first attribute that is non-null q.project({ - title: q.groq('coalesce(title.fi, title.en)'), + title: q.raw('coalesce(title.fi, title.en)'), }) // count counts the number of items in a collection -q.groq('count(*[_type == "movie" && rating == "R"])') +q.raw('count(*[_type == "movie" && rating == "R"])') // Counts the number of elements in the array actors q.star.filterByType("movie").project({ title: true, - actorCount: q.groq('count(actors)'), + actorCount: q.raw('count(actors)'), }) // round() rounds number to the nearest integer, or the given number of decimals -q.groq('round(3.14)') -q.groq('round(3.14, 1)') +q.raw('round(3.14)') +q.raw('round(3.14, 1)') // score() adds points to the score value depending on the use of the string "GROQ" in each post's description q.star.filterByType("post") @@ -438,7 +424,7 @@ q.star.filter('_type == "movie" && movieRating > 3') // Returns the body Portable Text data as plain text q.star.filterByType("post").project({ - plaintextBody: q.groq('pt::text(body)'), + plaintextBody: q.raw('pt::text(body)'), }) // Get all versions and drafts of a document. Use with the raw perspective or a perspective stack to ensure accurate results. From df58cb9802f6fa7d3e95880860e8d046932ed25e Mon Sep 17 00:00:00 2001 From: scottrippey Date: Thu, 29 May 2025 12:37:49 -0600 Subject: [PATCH 05/12] docs(cheat-sheet): improved coalesce and count --- website/docs/API/groq-cheat-sheet.md | 86 +++++++++++++++------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/website/docs/API/groq-cheat-sheet.md b/website/docs/API/groq-cheat-sheet.md index 889ad04b..fb1e3081 100644 --- a/website/docs/API/groq-cheat-sheet.md +++ b/website/docs/API/groq-cheat-sheet.md @@ -237,55 +237,55 @@ q.star.filter('_type=="movie" && title == "Arrival"').project({ q.star.filterByType("movie").project({ "...": true }) // Some computed attributes, then also add all attributes of the result -q.star.filterByType("movie").project({ +q.star.filterByType("movie").project(q => ({ posterUrl: q.field("poster").field("asset").deref().field("url"), "...": true, -}) +})) // Default values when missing or null in document -q.star.filterByType("movie").project({ +q.star.filterByType("movie").project(q => ({ "...": true, - rating: q.raw('coalesce(rating, "unknown")'), -}) + rating: q.coalesce("rating", q.value("unknown")), +})) // Number of elements in array 'actors' on each movie -q.star.filterByType("movie").project({ - actorCount: q.raw('count(actors)'), -}) +q.star.filterByType("movie").project(q => ({ + actorCount: q.count("actors"), +})) // Apply a projection to every member of an array -q.star.filterByType("movie").project({ +q.star.filterByType("movie").project(q => ({ castMembers: q.field("castMembers[]").project({ characterName: true, person: true, }), -}) +})) // Filter embedded objects q.star.filterByType("movie").project({ - castMembers: q.field('castMembers[characterName match "Ripley"]').project({ + castMembers: q.field("castMembers").filterRaw('characterName match "Ripley"').project({ characterName: true, person: true, }), }) // Follow every reference in an array of references -q.star.filterByType("book").project({ +q.star.filterByType("book").project(q => ({ authors: q.field("authors[]").deref().project({ name: true, bio: true, }), -}) +})) // Explicity name the outer return field -qRoot.project({ +q.project({ threeMovieTitles: q.star.filterByType("movie").slice(0, 2).field("title"), }) // Combining several unrelated queries in one request -qRoot.project({ - featuredMovie: q.star.filter('_type == "movie" && title == "Alien"').slice(0), - scifiMovies: q.star.filter('_type == "movie" && "sci-fi" in genres'), +q.project({ + featuredMovie: q.star.filterByType("movie").filterBy('title == "Alien"').slice(0), + scifiMovies: q.star.filterByType("movie").filterBy('"sci-fi" in genres'), }) ``` @@ -296,13 +296,13 @@ qRoot.project({ q.star // @ (root value of the scope) -q.star.filter('@["1"]') -q.star.filter('@[$prop]._ref == $refId') -q.project({ - arraySizes: q.field("arrays[]").project({ - size: q.raw("count(@)"), - }), -}) +q.star.filterRaw('@["1"]') +q.star.filterRaw('@[$prop]._ref == $refId') +q.project(q => ({ + arraySizes: q.field("arrays[]").project(q => ({ + size: q.count("@"), + })), +})) // ^ (enclosing document) q.star.filterByType("person").project((q) => ({ @@ -391,18 +391,18 @@ q.star.filter('_type=="movie" && references(*[_type=="person" && age > 99]._id)' q.star.filter('defined(tags)') // coalesce takes a number of attribute references and returns the value of the first attribute that is non-null -q.project({ - title: q.raw('coalesce(title.fi, title.en)'), -}) +q.star.project(q => ({ + title: q.coalesce("title.fi", "title.en"), +})) // count counts the number of items in a collection -q.raw('count(*[_type == "movie" && rating == "R"])') +q.count(q.star.filterByType("movie").filterBy("rating == 'R'")) // Counts the number of elements in the array actors -q.star.filterByType("movie").project({ +q.star.filterByType("movie").project(q => ({ title: true, - actorCount: q.raw('count(actors)'), -}) + actorCount: q.count("actors"), +})) // round() rounds number to the nearest integer, or the given number of decimals q.raw('round(3.14)') @@ -410,17 +410,17 @@ q.raw('round(3.14, 1)') // score() adds points to the score value depending on the use of the string "GROQ" in each post's description q.star.filterByType("post") - .score('description match "GROQ"') + .scoreRaw('description match "GROQ"') .order("_score desc") .project({ _score: true, title: true }) // boost() adds a defined boost integer to scores of items matching a condition -q.star.filter('_type == "movie" && movieRating > 3') - .score('title match $term', 'boost(movieRating > 8, 3)') +q.star.filterByType("movie").filterBy('movieRating > 3') + .scoreRaw('title match $term', 'boost(movieRating > 8, 3)') // Creates a scoring system where $term matching in the title is worth more than matching in the body -q.star.filter('_type == "movie" && movieRating > 3') - .score('boost(title match $term, 4)', 'boost(body match $term, 1)') +q.star.filterByType("movie").filterBy('movieRating > 3') + .scoreRaw('boost(title match $term, 4)', 'boost(body match $term, 1)') // Returns the body Portable Text data as plain text q.star.filterByType("post").project({ @@ -428,25 +428,25 @@ q.star.filterByType("post").project({ }) // Get all versions and drafts of a document. Use with the raw perspective or a perspective stack to ensure accurate results. -q.star.filter('sanity::versionOf("document-id")') +q.star.filterRaw('sanity::versionOf("document-id")') // Get all documents that are part of a release. Use with the raw perspective to ensure accurate results. -q.star.filter('sanity::partOfRelease("release-id")') +q.star.filterRaw('sanity::partOfRelease("release-id")') ``` ## Geolocation ```typescript // Returns all documents that are storefronts within 10 miles of the user-provided currentLocation parameter -q.star.filter('_type == "storefront" && geo::distance(geoPoint, $currentLocation) < 16093.4') +q.star.filterByType("storefront").filterRaw('geo::distance(geoPoint, $currentLocation) < 16093.4') // For a given $currentLocation geopoint and deliveryZone area // Return stores that deliver to a user's location -q.star.filter('_type == "storefront" && geo::contains(deliveryZone, $currentLocation)') +q.star.filterByType("storefront").filterBy('geo::contains(deliveryZone, $currentLocation)') // Creates a "marathonRoutes" array that contains all marathons whose routes intersect with the current neighborhood q.star.filterByType("neighborhood").project({ - marathonRoutes: q.star.filter('_type == "marathon" && geo::intersects(^.neighborhoodRegion, routeLine)'), + marathonRoutes: q.star.filterByType("marathon").filterBy('geo::intersects(^.neighborhoodRegion, routeLine)'), }) ``` @@ -474,3 +474,7 @@ q.raw<{a:number,b:number,c:number}>('{"a":1,"b":2} + {"c":3}') // {"a":1,"b":2," q.raw('3 + " p.m."') // null q.raw('string(3) + " p.m."') // "3 p.m." ``` + + + + From 1c0cf40930a44ece2a6d84b44bfdf4b7b0ddbf3a Mon Sep 17 00:00:00 2001 From: scottrippey Date: Thu, 29 May 2025 14:40:02 -0600 Subject: [PATCH 06/12] docs(cheat-sheet): improved projections --- website/docs/API/groq-cheat-sheet.md | 40 +++++++++++++++------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/website/docs/API/groq-cheat-sheet.md b/website/docs/API/groq-cheat-sheet.md index fb1e3081..93a699e2 100644 --- a/website/docs/API/groq-cheat-sheet.md +++ b/website/docs/API/groq-cheat-sheet.md @@ -197,18 +197,18 @@ q.project(q => ({ // Get all Nobel prizes from all root person documents -q.star.field("prizes[]") +q.star.filterByType("person").field("prizes[]") // Array helpers (use groq functions via .raw()) -q.field("tags").raw('array::join(@, ", ")') -q.raw('array::join(["a", "b", "c"], ".")') -q.field("year").raw('array::join(@, ".")') -q.field("values").raw('array::join(@, 1)') -q.field("numbers").raw('array::compact(@)') -q.field("items").raw('array::unique(@)') -q.field("records").raw('array::unique(@)') -q.raw('array::intersects(firstList, secondList)') -q.raw('array::intersects(tags, keywords)') +q.raw('array::join(tags, ", ")') // tags = ["Rust", "Go", null, "GROQ"] => "Rust, Go, , GROQ" +q.raw('array::join(["a", "b", "c"], ".")') // "a.b.c" +q.raw('array::join(year, ".")') // year = 2024 => null (not an array) +q.raw('array::join(values, 1)') // values = [10, 20, 30] => null (separator must be a string) +q.raw('array::compact(numbers)') // numbers = [1, null, 2, null, 3] => [1, 2, 3] +q.raw('array::unique(items)') // items = [1, 2, 2, 3, 4, 5, 5] => [1, 2, 3, 4, 5] +q.raw('array::unique(records)') // records = [[1], [1]] => [[1], [1]] (arrays are not comparable) +q.raw('array::intersects(firstList, secondList)') // firstList = [1, 2, 3], secondList = [3, 4, 5] => true +q.raw('array::intersects(tags, keywords)') // tags = ["tech", "science"], keywords = ["art", "design"] => false ``` ## Object Projections @@ -221,24 +221,26 @@ q.star.filterByType("movie").project({ title: true }) q.star.filterByType("movie").project({ _id: true, _type: true, title: true }) // explicitly name the return field for _id -q.star.filterByType("movie").project({ renamedId: "._id", _type: true, title: true }) +q.star.filterByType("movie").project({ renamedId: "_id", _type: true, title: true }) // Return an array of attribute values (no object wrapper) q.star.filterByType("movie").field("title") -q.star.filterByType("movie").project({ characterNames: q.field("castMembers[]").field("characterName") }) +q.star.filterByType("movie").project(q => ({ + characterNames: q.field("castMembers[].characterName") +})) // movie titled Arrival and its posterUrl -q.star.filter('_type=="movie" && title == "Arrival"').project({ +q.star.filterByType("movie").filterBy('title == "Arrival"').project(q => ({ title: true, - posterUrl: q.field("poster").field("asset").deref().field("url"), -}) + posterUrl: q.field("poster.asset").deref().field("url"), +})) // Explicitly return all attributes q.star.filterByType("movie").project({ "...": true }) // Some computed attributes, then also add all attributes of the result q.star.filterByType("movie").project(q => ({ - posterUrl: q.field("poster").field("asset").deref().field("url"), + posterUrl: q.field("poster.asset").deref().field("url"), "...": true, })) @@ -262,12 +264,12 @@ q.star.filterByType("movie").project(q => ({ })) // Filter embedded objects -q.star.filterByType("movie").project({ - castMembers: q.field("castMembers").filterRaw('characterName match "Ripley"').project({ +q.star.filterByType("movie").project(q => ({ + castMembers: q.field("castMembers[]").filterRaw('characterName match "Ripley"').project({ characterName: true, person: true, }), -}) +})) // Follow every reference in an array of references q.star.filterByType("book").project(q => ({ From 6a138d8cdaa1ec1bc94e5d9ea8926acd992e436d Mon Sep 17 00:00:00 2001 From: scottrippey Date: Thu, 29 May 2025 14:44:20 -0600 Subject: [PATCH 07/12] docs(cheat-sheet): improved special variables --- website/docs/API/groq-cheat-sheet.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/website/docs/API/groq-cheat-sheet.md b/website/docs/API/groq-cheat-sheet.md index 93a699e2..fc5b600f 100644 --- a/website/docs/API/groq-cheat-sheet.md +++ b/website/docs/API/groq-cheat-sheet.md @@ -294,23 +294,28 @@ q.project({ ## Special variables ```typescript -// * (everything) -q.star +// * +q.star // Everything, i.e. all documents + +// @ +// @ refers to the root value (document) of the scope -// @ (root value of the scope) q.star.filterRaw('@["1"]') -q.star.filterRaw('@[$prop]._ref == $refId') -q.project(q => ({ +q.star.filterRaw('@[$prop]._ref == $refId') // Select reference prop from an outside variable. +q.star.project(q => ({ arraySizes: q.field("arrays[]").project(q => ({ - size: q.count("@"), + size: q.count("@"), // @ also works for nested scopes })), })) -// ^ (enclosing document) +// ^ +// ^ refers to the enclosing document. Here ^._id refers to the id +// of the enclosing person record. q.star.filterByType("person").project((q) => ({ name: true, - relatedMovies: q - .star.filterByType("movie").filterBy("references(^._id)") + relatedMovies: q.star + .filterByType("movie") + .filterBy("references(^._id)") .project({ title: true }), })) ``` From 2b0b8fd2ad1f4cdc904e344c43f6cfd44f8c4e62 Mon Sep 17 00:00:00 2001 From: scottrippey Date: Thu, 29 May 2025 14:56:35 -0600 Subject: [PATCH 08/12] docs(cheat-sheet): updated conditionals --- website/docs/API/groq-cheat-sheet.md | 76 ++++++++++++++-------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/website/docs/API/groq-cheat-sheet.md b/website/docs/API/groq-cheat-sheet.md index fc5b600f..e6a0a7c3 100644 --- a/website/docs/API/groq-cheat-sheet.md +++ b/website/docs/API/groq-cheat-sheet.md @@ -324,52 +324,51 @@ q.star.filterByType("person").project((q) => ({ ```typescript // select() returns the first => pair whose left-hand side evaluates to true -q.star.filterByType("movie").project({ - "...": true, - popularity: q.raw('select(popularity > 20 => "high", popularity > 10 => "medium", popularity <= 10 => "low")'), -}) - -// The first select() parameter without => is returned if no previous matches are found -q.star.filterByType("movie").project({ +q.star.filterByType("movie").project(q => ({ "...": true, - popularity: q.raw('select(popularity > 20 => "high", popularity > 10 => "medium", "low")'), -}) + popularity: q.select({ + "popularity > 20": q.value("high"), + "popularity > 10": q.value("medium"), + "popularity <= 10": q.value("low"), + }), +})) -// Projections also have syntactic sugar for inline conditionals -q.star.filterByType("movie").project((q) => ({ +// The second parameter to select() is returned if no previous matches are found +q.star.filterByType("movie").project(q => ({ "...": true, - ...q.raw(`releaseDate >= '2018-06-01' => { - "screenings": *[_type == 'screening' && movie._ref == ^._id], - "news": *[_type == 'news' && movie._ref == ^._id], - }`), - ...q.raw(`popularity > 20 && rating > 7.0 => { - "featured": true, - "awards": *[_type == 'award' && movie._ref == ^._id], - }`), + popularity: q.select({ + "popularity > 20": q.value("high"), + "popularity > 10": q.value("medium"), + // Default value if no conditions match: + }, q.value("low")), })) -// The above is exactly equivalent to: +// Projections also have syntactic sugar for inline conditionals q.star.filterByType("movie").project((q) => ({ "...": true, - ...q.raw(`...select(releaseDate >= '2018-06-01' => { - "screenings": *[_type == 'screening' && movie._ref == ^._id], - "news": *[_type == 'news' && movie._ref == ^._id], - })`), - ...q.raw(`...select(popularity > 20 && rating > 7.0 => { - "featured": true, - "awards": *[_type == 'award' && movie._ref == ^._id], - })`), + ...q.conditional({ + "releaseDate >= '2018-06-01'": q.project({ + screenings: q.star.filterByType("screening").filterBy('movie._ref == ^._id'), + news: q.star.filterByType("news").filterBy('movie._ref == ^._id'), + }), + "popularity > 20 && rating > 7.0": q.project({ + featured: q.value(true), + awards: q.star.filterByType("award").filterBy('movie._ref == ^._id'), + }), + }), })) -// Specify sets of projections for different content types in an array +// Specify sets of projections for different content types in an object q.field("content[]").project((q) => ({ - ...q.raw(`_type == 'type1' => { - // Your selection of fields for type1 - }, - _type == 'type2' => { - // Your selection of fields for type2 - "url": file.asset->url - }`), + ...q.conditionalByType({ + type1: q => ({ + // Your selection of fields for type1 + }), + type2: q => ({ + // Your selection of fields for type2 + url: q.field("file.asset").deref().field("url"), + }), + }), })) ``` @@ -380,7 +379,10 @@ In cases where an array contains both [references and non-references](https://ww ```typescript q.project({ content: q.field("content[]").project((q) => ({ - ...q.raw(`_type == 'reference' => @->, _type != 'reference' => @`), + ...q.conditionalByType({ + reference: q => q.field("@").deref(), + item: q => q.field("@"), + }), })), }) ``` From d92acc2375e447b3e6fbbb5af964e1ff1094379b Mon Sep 17 00:00:00 2001 From: scottrippey Date: Thu, 29 May 2025 14:59:41 -0600 Subject: [PATCH 09/12] docs(cheat-sheet): updated functions --- website/docs/API/groq-cheat-sheet.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/website/docs/API/groq-cheat-sheet.md b/website/docs/API/groq-cheat-sheet.md index e6a0a7c3..d335f6a9 100644 --- a/website/docs/API/groq-cheat-sheet.md +++ b/website/docs/API/groq-cheat-sheet.md @@ -391,13 +391,13 @@ q.project({ ```typescript // any document that references the document with id person_sigourney-weaver, return only title -q.star.filter('references("person_sigourney-weaver")').project({ title: true }) +q.star.filterRaw('references("person_sigourney-weaver")').project({ title: true }) // Movies which reference ancient people -q.star.filter('_type=="movie" && references(*[_type=="person" && age > 99]._id)').project({ title: true }) +q.star.filterByType("movie").filterRaw('references(*[_type=="person" && age > 99]._id)').project({ title: true }) // any document that has the attribute 'tags' -q.star.filter('defined(tags)') +q.star.filterRaw('defined(tags)') // coalesce takes a number of attribute references and returns the value of the first attribute that is non-null q.star.project(q => ({ @@ -410,7 +410,7 @@ q.count(q.star.filterByType("movie").filterBy("rating == 'R'")) // Counts the number of elements in the array actors q.star.filterByType("movie").project(q => ({ title: true, - actorCount: q.count("actors"), + actorCount: q.count("actors[]"), })) // round() rounds number to the nearest integer, or the given number of decimals @@ -483,7 +483,3 @@ q.raw<{a:number,b:number,c:number}>('{"a":1,"b":2} + {"c":3}') // {"a":1,"b":2," q.raw('3 + " p.m."') // null q.raw('string(3) + " p.m."') // "3 p.m." ``` - - - - From 84868295cb1edfd975d38dffe11c139a7c791310 Mon Sep 17 00:00:00 2001 From: scottrippey Date: Fri, 30 May 2025 13:19:08 -0600 Subject: [PATCH 10/12] docs(cheat-sheet): updated frontmatter --- website/docs/API/groq-cheat-sheet-raw.md | 466 ------------------ ...oq-cheat-sheet.md => groqd-cheat-sheet.md} | 15 +- 2 files changed, 11 insertions(+), 470 deletions(-) delete mode 100644 website/docs/API/groq-cheat-sheet-raw.md rename website/docs/API/{groq-cheat-sheet.md => groqd-cheat-sheet.md} (98%) diff --git a/website/docs/API/groq-cheat-sheet-raw.md b/website/docs/API/groq-cheat-sheet-raw.md deleted file mode 100644 index 3b316a3f..00000000 --- a/website/docs/API/groq-cheat-sheet-raw.md +++ /dev/null @@ -1,466 +0,0 @@ -# Query Cheat Sheet - GROQ - -Here are some typical queries in GROQ. You can also check out [our introduction to GROQ](/docs/content-lake/how-queries-work) and [the complete reference documentation](/docs/groq). To actually run queries you can: - -- Hit your content lake's query [HTTP endpoint](/docs/http-reference/query) directly -- Use the [JavaScript](/docs/js-client) or [PHP](/docs/php-client) SDKs, or [another client](https://www.sanity.io/exchange/type=plugins/solution=apis) -- Install the [Vision plugin](/docs/content-lake/the-vision-plugin) that runs queries right inside Sanity Studio -- Go to [groq.dev](https://groq.dev) to run queries against any JSON dataset - -> [!WARNING] -> Gotcha -> If your query doesn't work as expected, it might be related to: -> -> API versioning -> -> Perspectives - -## Filters - -> [!TIP] -> Protip -> You will get null as a value on a query if the key you ask for doesn't exist. That means you can filter on key != null to check if it exists with a value or not. - -```groq -* // Everything, i.e. all documents -*[] // Everything with no filters applied, i.e. all documents -*[_type == "movie"] // All movie documents -*[_id == "abc.123"] // _id equals -*[_type in ["movie", "person"]] // _type is movie or person -*[_type == "movie" && popularity > 15 && releaseDate > "2016-04-25"] // multiple filters AND -*[_type == "movie" && (popularity > 15 || releaseDate > "2016-04-25")] // multiple filters OR -*[popularity < 15] // less than -*[popularity > 15] // greater than -*[popularity <= 15] // less than or equal -*[popularity >= 15] // greater than or equal -*[popularity == 15] -*[releaseDate != "2016-04-27"] // not equal -*[!(releaseDate == "2016-04-27")] // not equal -*[!(releaseDate != "2016-04-27")] // even equal via double negatives "not not equal" -*[dateTime(_updatedAt) > dateTime('2018-04-20T20:43:31Z')] // Use zulu-time when comparing datetimes to strings -*[dateTime(_updatedAt) > dateTime(now()) - 60*60*24*7] // Updated within the past week -*[name < "Baker"] // Records whose name precedes "Baker" alphabetically -*[awardWinner == true] // match boolean -*[awardWinner] // true if awardWinner == true -*[!awardWinner] // true if awardWinner == false -*[defined(awardWinner)] // has been assigned an award winner status (any kind of value) -*[!defined(awardWinner)] // has not been assigned an award winner status (any kind of value) -*[title == "Aliens"] -*[title in ["Aliens", "Interstellar", "Passengers"]] -*[_id in path("a.b.c.*")] // _id matches a.b.c.d but not a.b.c.d.e -*[_id in path("a.b.c.**")] // _id matches a.b.c.d, and also a.b.c.d.e.f.g, but not a.b.x.1 -*[!(_id in path("a.b.c.**"))] // _id matches anything that is not under the a.b.c path or deeper -*["yolo" in tags] // documents that have the string "yolo" in the array "tags" -*[status in ["completed", "archived"]] // the string field status is either == "completed" or "archived" -*["person_sigourney-weaver" in castMembers[].person._ref] // Any document having a castMember referencing sigourney as its person -*[slug.current == "some-slug"] // nested properties -*[count((categories[]->slug.current)[@ in ["action", "thriller"]]) > 0] // documents that reference categories with slugs of "action" or "thriller" -*[count((categories[]->slug.current)[@ in ["action", "thriller"]]) == 2] // documents that reference categories with slugs of "action" and "thriller" - // set == 2 based on the total number of items in the array -``` - -## Text matching - -> [!WARNING] -> Gotcha -> The match operator is designed for human-language text and might not do what you expect! - -```groq -// Text contains the word "word" -*[text match "word"] - -// Title contains a word starting with "wo" -*[title match "wo*"] - -// Inverse of the previous query; animal matches the start of the word "caterpillar" (perhaps animal == "cat") -*["caterpillar" match animal + "*"] - -// Title and body combined contains a word starting with "wo" and the full word "zero" -*[[title, body] match ["wo*", "zero"]] - -// Are there aliens in my rich text? -*[body[].children[].text match "aliens"] - -// Note how match operates on tokens! -"foo bar" match "fo*" // -> true -"my-pretty-pony-123.jpg" match "my*.jpg" // -> false -``` - -## Slice Operations - -> [!TIP] -> Protip -> There is no default limit, meaning that if you're not explicit about slice, you'll get everything. - -```groq -*[_type == "movie"][0] // a single movie (an object is returned, not an array) -*[_type == "movie"][0..5] // first 6 movies (inclusive) -*[_type == "movie"][0...5] // first 5 movies (non-inclusive) -*[_type == "movie"]{title}[0...10] // first 10 movie titles -*[_type == "movie"][0...10]{title} // first 10 movie titles -*[_type == "movie"][10...20]{title} // first 10 movie titles, offset by 10 -*[_type == "movie"] // no slice specified --> all movies are returned -``` - -**Also note**: The above queries don't make much sense without also specifying an order. E.g. the "first 6 movies" query only returns "first" movies in the sense that these are the first six movies the backend happens to pull out. - -## Ordering - -> [!TIP] -> Protip -> Documents are returned by default in ascending order by _id, which may not be what you're after. If you're querying for a subset of your documents, it's usually a good idea to specify an order. -> -> No matter what sort order is specified, the ascending order by _id will always remain the final tie-breaker. - -```groq -// order results -*[_type == "movie"] | order(_createdAt asc) - -// order results by multiple attributes -*[_type == "movie"] | order(releaseDate desc) | order(_createdAt asc) - -// order todo items by descending priority, -// where priority is equal, list most recently updated -// item first -*[_type == "todo"] | order(priority desc, _updatedAt desc) - -// the single, oldest document -*[_type == "movie"] | order(_createdAt asc)[0] - -// the single, newest document -*[_type == "movie"] | order(_createdAt desc)[0] - -// oldest 10 documents -*[_type == "movie"] | order(_createdAt asc)[0..9] - -// BEWARE! This selects 10 documents using the default -// ordering, and *only the selection* is ordered by -// _createdAt in ascending order -*[_type == "movie"][0..9] | order(_createdAt asc) - -// limit/offset using external params (see client documentation) -*[_type == "movie"] | order(_createdAt asc) [$start..$end] - -// order results alphabetically by a string field -// This is case sensitive, so A-Z come before a-z -*[_type == "movie"] | order(title asc) - -// order results alphabetically by a string field, -// ignoring case -*[_type == "movie"] | order(lower(title) asc) -``` - -## Joins - -```groq -// Fetch movies with title, and join with poster asset with path + url -*[_type=='movie']{title,poster{asset->{path,url}}} - -// Say castMembers is an array containing objects with character name and a reference to the person: -// We want to fetch movie with title and an attribute named "cast" which is an array of actor names -*[_type=='movie']{title,'cast': castMembers[].person->name} - -// Same query as above, except "cast" now contains objects with person._id and person.name -*[_type=='movie']{title,'cast': castMembers[].person->{_id, name}} - -// Using the ^ operator to refer to the enclosing document. Here ^._id refers to the id -// of the enclosing person record. -*[_type=="person"]{ - name, - "relatedMovies": *[_type=='movie' && references(^._id)]{ title } -} - -// Books by author.name (book.author is a reference) -*[_type == "book" && author._ref in *[_type=="author" && name=="John Doe"]._id ]{...} - -``` - -## Objects and Arrays - -```groq -// Create your own objects -// https://groq.dev/lcGV0Km6dpvYovREqq1gLS -{ - // People ordered by Nobel prize year - "peopleByPrizeYear": *[]|order(prizes[0].year desc){ - "name": firstname + " " + surname, - "orderYear": prizes[0].year, - prizes - }, - // List of all prizes ordered by year awarded - "allPrizes": *[].prizes[]|order(year desc) -} - -// Get all Nobel prizes from all root person documents -// https://groq.dev/v8T0DQawC6ihbNUf4cUeeS -*[].prizes[] - -array::join(tags, ", ") // tags = ["Rust", "Go", null, "GROQ"] => "Rust, Go, , GROQ" -array::join(["a", "b", "c"], ".") // "a.b.c" -array::join(year, ".") // year = 2024 => null (not an array) -array::join(values, 1) // values = [10, 20, 30] => null (separator must be a string) -array::compact(numbers) // numbers = [1, null, 2, null, 3] => [1, 2, 3] -array::unique(items) // items = [1, 2, 2, 3, 4, 5, 5] => [1, 2, 3, 4, 5] -array::unique(records) // records = [[1], [1]] => [[1], [1]] (arrays are not comparable) -array::intersects(firstList, secondList) // firstList = [1, 2, 3], secondList = [3, 4, 5] => true -array::intersects(tags, keywords) // tags = ["tech", "science"], keywords = ["art", "design"] => false -``` - -## Object Projections - -```groq -// return only title -*[_type == 'movie']{title} - -// return values for multiple attributes -*[_type == 'movie']{_id, _type, title} - -// explicitly name the return field for _id -*[_type == 'movie']{'renamedId': _id, _type, title} - -// Return an array of attribute values (no object wrapper) -*[_type == 'movie'].title -*[_type == 'movie']{'characterNames': castMembers[].characterName} - -// movie titled Arrival and its posterUrl -*[_type=='movie' && title == 'Arrival']{title,'posterUrl': poster.asset->url} - -// Explicitly return all attributes -*[_type == 'movie']{...} - -// Some computed attributes, then also add all attributes of the result -*[_type == 'movie']{'posterUrl': poster.asset->url, ...} - -// Default values when missing or null in document -*[_type == 'movie']{..., 'rating': coalesce(rating, 'unknown')} - -// Number of elements in array 'actors' on each movie -*[_type == 'movie']{"actorCount": count(actors)} - -// Apply a projection to every member of an array -*[_type == 'movie']{castMembers[]{characterName, person}} - -// Filter embedded objects -*[_type == 'movie']{castMembers[characterName match 'Ripley']{characterName, person}} - -// Follow every reference in an array of references -*[_type == 'book']{authors[]->{name, bio}} - -// Explicity name the outer return field -{'threeMovieTitles': *[_type=='movie'][0..2].title} - -// Combining several unrelated queries in one request -{'featuredMovie': *[_type == 'movie' && title == 'Alien'][0], 'scifiMovies': *[_type == 'movie' && 'sci-fi' in genres]} - -``` - -## Special variables - -```groq -// * -* // Everything, i.e. all documents - -// @ -*[ @["1"] ] // @ refers to the root value (document) of the scope -*[ @[$prop]._ref == $refId ] // Select reference prop from an outside variable. -*{"arraySizes": arrays[]{"size": count(@)}} // @ also works for nested scopes - -// ^ -// ^ refers to the enclosing document. Here ^._id refers to the id -// of the enclosing person record. -*[_type=="person"]{ - name, - "relatedMovies": *[_type=='movie' && references(^._id)]{ title } -} -``` - -## Conditionals - -```groq -// select() returns the first => pair whose left-hand side evaluates to true -*[_type=='movie']{..., "popularity": select( - popularity > 20 => "high", - popularity > 10 => "medium", - popularity <= 10 => "low" -)} - -// The first select() parameter without => is returned if no previous matches are found -*[_type=='movie']{..., "popularity": select( - popularity > 20 => "high", - popularity > 10 => "medium", - "low" -)} - -// Projections also have syntactic sugar for inline conditionals -*[_type=='movie']{ - ..., - releaseDate >= '2018-06-01' => { - "screenings": *[_type == 'screening' && movie._ref == ^._id], - "news": *[_type == 'news' && movie._ref == ^._id], - }, - popularity > 20 && rating > 7.0 => { - "featured": true, - "awards": *[_type == 'award' && movie._ref == ^._id], - }, -} - -// The above is exactly equivalent to: -*[_type=='movie']{ - ..., - ...select(releaseDate >= '2018-06-01' => { - "screenings": *[_type == 'screening' && movie._ref == ^._id], - "news": *[_type == 'news' && movie._ref == ^._id], - }), - ...select(popularity > 20 && rating > 7.0 => { - "featured": true, - "awards": *[_type == 'award' && movie._ref == ^._id], - }), -} - - -// Specify sets of projections for different content types in an array -content[]{ - _type == 'type1' => { - // Your selection of fields for type1 - }, - _type == 'type2' => { - // Your selection of fields for type2 - "url": file.asset->url // Use joins to get data of referenced document - } -} - -``` - -### Handling references conditionally - -In cases where an array contains both [references and non-references](https://www.sanity.io/docs/array-type#wT47gyCx), it's often desirable for a GROQ query to conditionally return the inline object (where dealing with non-references) or the referenced document (where dealing with references). This can be done by considering the `_type` of each array item and dereferencing the item (`@->`) if it's a reference or getting the whole object (`@`) if it's not a reference. - -```groq -'content': content[]{ - _type == 'reference' => @->, - _type != 'reference' => @, -} -``` - -## Functions - -```groq -// any document that references the document -// with id person_sigourney-weaver, -// return only title -*[references("person_sigourney-weaver")]{title} - -// Movies which reference ancient people -*[_type=="movie" && references(*[_type=="person" && age > 99]._id)]{title} - -*[defined(tags)] // any document that has the attribute 'tags' - -// coalesce takes a number of attribute references -// and returns the value of the first attribute -// that is non-null. In this example used to -// default back to the English language where a -// Finnish translation does not exist. -*{"title": coalesce(title.fi, title.en)} - -// count counts the number of items in a collection -count(*[_type == 'movie' && rating == 'R']) // returns number of R-rated movies - -*[_type == 'movie']{ - title, - "actorCount": count(actors) // Counts the number of elements in the array actors -} - -// round() rounds number to the nearest integer, or the given number of decimals -round(3.14) // 3 -round(3.14, 1) // 3.1 - - -// score() adds points to the score value depending -// on the use of the string "GROQ" in each post's description -// The value is then used to order the posts -*[_type == "post"] - | score(description match "GROQ") - | order(_score desc) - { _score, title } - -// boost() adds a defined boost integer to scores of items matching a condition -// Adds 1 to the score for each time $term is matched in the title field -// Adds 3 to the score if (movie > 3) is true -*[_type == "movie" && movieRating > 3] | - score( - title match $term, - boost(movieRating > 8, 3) - ) - -// Creates a scoring system where $term matching in the title -// is worth more than matching in the body -*[_type == "movie" && movieRating > 3] | score( - boost(title match $term, 4), - boost(body match $term, 1) -) - -// Returns the body Portable Text data as plain text -*[_type == "post"] - { "plaintextBody": pt::text(body) } - -// Get all versions and drafts of a document. Use with the raw perspective or a perspective stack to ensure accurate results. -*[sanity::versionOf('document-id')] - -// Get all documents that are part of a release. Use with the raw perspective to ensure accurate results. -*[sanity::partOfRelease('release-id')] -``` - -## Geolocation - -```groq -// Returns all documents that are storefronts -// within 10 miles of the user-provided currentLocation parameter -*[ - _type == 'storefront' && - geo::distance(geoPoint, $currentLocation) < 16093.4 -] - -// For a given $currentLocation geopoint and deliveryZone area -// Return stores that deliver to a user's location -*[ - _type == "storefront" && - geo::contains(deliveryZone, $currentLocation) -] - -// Creates a "marathonRoutes" array that contains -// all marathons whose routes intersect with the current neighborhood -*[_type == "neighborhood"] { - "marathonRoutes": *[_type == "marathon" && - geo::intersects(^.neighborhoodRegion, routeLine) - ] -} -``` - -## Arithmetic and Concatenation - -```groq -// Standard arithmetic operations are supported -1 + 2 // 3 (addition) -3 - 2 // 1 (subtraction) -2 * 3 // 6 (multiplication) -8 / 4 // 2 (division) -2 ** 4 // 16 (exponentiation) -8 % 3 // 2 (modulo) - -// Exponentiation can be used to take square- and cube-roots too -9 ** (1/2) // 3 (square root) -27 ** (1/3) // 3 (cube root) - -// + can also concatenate strings, arrays, and objects: -"abc" + "def" // "abcdef" -[1,2] + [3,4] // [1,2,3,4] -{"a":1,"b":2} + {"c":3} // {"a":1,"b":2,"c":3} - -// Concatenation of a string and a number requires the number be -// converted to a string. Otherwise, the operation returns null -3 + " p.m." // null -string(3) + " p.m." // "3 p.m." -``` - - - diff --git a/website/docs/API/groq-cheat-sheet.md b/website/docs/API/groqd-cheat-sheet.md similarity index 98% rename from website/docs/API/groq-cheat-sheet.md rename to website/docs/API/groqd-cheat-sheet.md index d335f6a9..00b466fb 100644 --- a/website/docs/API/groq-cheat-sheet.md +++ b/website/docs/API/groqd-cheat-sheet.md @@ -1,4 +1,8 @@ -# Query Cheat Sheet - GROQD +--- +sidebar_position: 10 +--- + +# GroqD Cheat Sheet Here are some typical queries in GroqD (TypeScript). These examples were adapted from the [GROQ Cheat Sheet](https://www.sanity.io/docs/query-cheat-sheet) @@ -34,9 +38,10 @@ q.star.filterByType("movie").filterBy('popularity > 15', 'releaseDate == "2016-0 ### `filterBy(expression, ...expression)` -Use `filterBy` to filter the documents. The `expression` is a **strongly-typed** string. +Use `filterBy` to filter the documents. +The `expression` is a **strongly-typed** string. -> This method only supports basic GROQ expressions, like `slug.current == "abc"` or `value > 10`. +> This method is strongly-typed, so it only supports basic GROQ expressions, like `slug.current == "abc"` or `value > 10`. > Use `filterRaw` for more complex expressions. ```typescript @@ -57,7 +62,9 @@ q.star.filterByType("movie") // gives us a strongly-typed `.filterBy` method ``` ### `filterRaw(expression, ...expression)` -Use `filterRaw` for expressions that are more complex than `filterBy` supports. +Use `filterRaw` for expressions that are more complex than `filterBy` supports. +The `expression` is **NOT strongly-typed**; any string is allowed. + ```typescript From 45a471366d311bb955d02fa6d0e679eb2bbd0fee Mon Sep 17 00:00:00 2001 From: scottrippey Date: Fri, 30 May 2025 14:25:39 -0600 Subject: [PATCH 11/12] docs(cheat-sheet): fixed order examples --- website/docs/API/groqd-cheat-sheet.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/website/docs/API/groqd-cheat-sheet.md b/website/docs/API/groqd-cheat-sheet.md index 00b466fb..748dada2 100644 --- a/website/docs/API/groqd-cheat-sheet.md +++ b/website/docs/API/groqd-cheat-sheet.md @@ -14,7 +14,6 @@ These examples were adapted from the [GROQ Cheat Sheet](https://www.sanity.io/do Use `filterByType` to retrieve the appropriate documents. The `_type` is a **strongly-typed** string, representing one of your document types. - ```typescript // Everything, i.e. all documents q.star @@ -22,9 +21,6 @@ q.star // All movie documents q.star.filterByType("movie") -// _id equals -q.star.filterBy('_id == "abc.123"') - // _type is movie or person q.star.filterByType("movie", "person") @@ -46,6 +42,7 @@ The `expression` is a **strongly-typed** string. ```typescript q.star.filterByType("movie") // gives us a strongly-typed `.filterBy` method + .filterBy('_id == "abc.123"') // _id equals .filterBy('popularity < 15') // less than .filterBy('popularity > 15') // greater than .filterBy('popularity <= 15') // less than or equal @@ -55,8 +52,6 @@ q.star.filterByType("movie") // gives us a strongly-typed `.filterBy` method .filterBy('awardWinner') // match boolean .filterBy('awardWinner') // true if awardWinner == true .filterBy('!awardWinner') // true if awardWinner == false - .filterBy('defined(awardWinner)') // has been assigned an award winner status (any kind of value) - .filterBy('!defined(awardWinner)') // has not been assigned an award winner status (any kind of value) .filterBy('title == "Aliens"') // title equals .filterBy('slug.current == "some-slug"') // nested properties ``` @@ -81,6 +76,9 @@ q.star.filterByType("movie") .filterRaw('"person_sigourney-weaver" in castMembers[].person._ref') // Any document having a castMember referencing sigourney as its person .filterRaw('count((categories[]->slug.current)[@ in ["action", "thriller"]]) > 0') // documents that reference categories with slugs of "action" or "thriller" .filterRaw('count((categories[]->slug.current)[@ in ["action", "thriller"]]) == 2') // documents that reference categories with slugs of "action" and "thriller" + .filterRaw('defined(awardWinner)') // has been assigned an award winner status (any kind of value) + .filterRaw('!defined(awardWinner)') // has not been assigned an award winner status (any kind of value) + ``` ## Text matching @@ -130,15 +128,15 @@ q.star.filterByType("movie") // all movies are returned (no slice specified) ```typescript q.star.filterByType("movie").order("_createdAt asc") // order results -q.star.filterByType("movie").order("releaseDate desc").order("_createdAt asc") // order results by multiple attributes -q.star.filterByType("todo").order("priority desc, _updatedAt desc") // order todo items by descending priority, then most recently updated +q.star.filterByType("movie").order("releaseDate desc", "_createdAt asc") // order results by multiple attributes +q.star.filterByType("todo").order("priority desc", "_updatedAt desc") // order todo items by descending priority, then most recently updated q.star.filterByType("movie").order("_createdAt asc").slice(0) // the single, oldest document q.star.filterByType("movie").order("_createdAt desc").slice(0) // the single, newest document q.star.filterByType("movie").order("_createdAt asc").slice(0, 10) // oldest 10 documents q.star.filterByType("movie").slice(0, 10).order("_createdAt asc") // BEWARE! This selects 10 documents using the default ordering, and *only the selection* is ordered by _createdAt in ascending order -q.star.filterByType("movie").order("_createdAt asc").slice("$start", "$end") // limit/offset using external params (see client documentation) +q.star.filterByType("movie").order("_createdAt asc").slice(0, 10) // selects the first 10 created documents q.star.filterByType("movie").order("title asc") // order results alphabetically by a string field -q.star.filterByType("movie").order("lower(title) asc") // order results alphabetically by a string field, ignoring case +q.star.filterByType("movie").raw("| order(lower(title) asc)", "passthrough") // order results alphabetically by a string field, ignoring case (TODO: implement 'orderRaw' instead of this) ``` ## Joins From 0e41a51e7391068a173acbbad88503c0ec759d2c Mon Sep 17 00:00:00 2001 From: scottrippey Date: Mon, 2 Jun 2025 15:04:44 -0600 Subject: [PATCH 12/12] docs: improved docs for `createGroqBuilder` method, and added @deprecated --- packages/groqd/src/index.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/groqd/src/index.ts b/packages/groqd/src/index.ts index dbd35910..a38d137a 100644 --- a/packages/groqd/src/index.ts +++ b/packages/groqd/src/index.ts @@ -1,6 +1,7 @@ // Be sure to keep these first 2 imports in this order: import "./groq-builder"; import "./commands"; +import { createGroqBuilderWithZod } from "./createGroqBuilderWithZod"; // Export all public types: @@ -43,7 +44,25 @@ export { createGroqBuilderLite } from "./createGroqBuilder"; export { GroqBuilderWithZod, createGroqBuilderWithZod, - createGroqBuilderWithZod as createGroqBuilder, z, zod, } from "./createGroqBuilderWithZod"; + +/** + * Creates the root `q` query builder. + * + * For convenience, includes all Zod validation methods attached to the `q` object, like `q.string()` etc. + * This ensures an API that's backwards compatible with GroqD v0.x syntax. + * + * If you want to use `z` directly, + * or a different validation library, + * or don't need runtime validation, + * use `createGroqBuilderLite` instead. + * + * @alias createGroqBuilderWithZod + * + * @deprecated + * Use `createGroqBuilderWithZod(...)` instead. + * This method will eventually change to an alias for `createGroqBuilderLite`. + */ +export const createGroqBuilder = createGroqBuilderWithZod;