Related
A single query can also fetch entries related to the ones it finds: their parents, children and siblings, their translations, the entries they link to and the entries that link to them. Put a relation in select (or in include when you don't select), and it's resolved in the same query.
import {cms} from '@/cms'
import {Blog, BlogPost} from '@/schema'
import {Query} from 'alinea'
const blog = await cms.get({
type: Blog,
locale: 'en',
select: {
title: Query.title,
posts: Query.children({
type: BlogPost,
select: {title: Query.title, url: Query.url, date: BlogPost.publishDate},
orderBy: {desc: BlogPost.publishDate},
take: 5
})
}
})Every relation takes the same options as a query, such as type, filter, select, orderBy, skip and take. parent, previous and next return a single entry, the others a list. Add count: true to get the number of matches instead, as in Query.children({count: true}), or first: true to get a single entry (or null). Relations can be nested, for example children with their own children.
Tree relations
Query.parent(...): the parent entry, or nothing for entries at the top of a root.Query.parents(...): all ancestors, starting at the top of the root. Passdepthto get only the nearest ones:depth: 1is the parent.Query.children(...): the direct children. Passdepthto include deeper descendants too, returned as one flat list.Query.siblings(...): the other children of the same parent. AddincludeSelf: trueto include the entry itself.Query.previous(...)andQuery.next(...): the sibling right before or after the entry, in sidebar order, or nothing at the ends.Query.translations(...): the same entry in the other locales of the root. AddincludeSelf: trueto include the current locale, listed first.
Tree relations stay in the locale of the entry they start from (except translations, of course) and use the status of the outer query.
import {cms} from '@/cms'
import {BlogPost} from '@/schema'
import {Query} from 'alinea'
export async function postPage(url: string) {
return cms.first({
type: BlogPost,
url,
select: {
title: Query.title,
body: BlogPost.body,
// Breadcrumbs, from the top of the root down
breadcrumbs: Query.parents({
select: {title: Query.title, url: Query.url}
}),
// Pagination at the bottom of the post
previous: Query.previous({
type: BlogPost,
select: {title: Query.title, url: Query.url}
}),
next: Query.next({
type: BlogPost,
select: {title: Query.title, url: Query.url}
}),
// A language switcher
translations: Query.translations({
select: {locale: Query.locale, url: Query.url}
})
}
})
}siblings, previous and next find entries that share a parent. For an entry at the top level of a root, they find the other top-level entries of the same root (and locale).
Linked entries
Entry, Link, Image and File fields return the basics of what they link to. To get other fields of the linked entries, query through the field:
a single link field has
.first(query).a multiple link field has
.find(query),.first(query)and.count(query). Results keep the order the editor gave the links.
import {cms} from '@/cms'
import {Author, BlogPost} from '@/schema'
import {Query} from 'alinea'
const post = await cms.get({
type: BlogPost,
path: 'hello-world',
select: {
title: Query.title,
author: BlogPost.author.first({
select: {name: Query.title, bio: Author.bio, avatar: Author.avatar}
}),
categories: BlogPost.categories.find({
select: {title: Query.title, url: Query.url}
})
}
})Linked entries are looked up in the locale of the entry you queried, falling back to entries without a locale, so a link to an untranslated author keeps working from every translation of a post.
Entries that link here
Links are stored on the entry that links. Query.referencedBy finds the entries that link to the entry, through any of their fields, including links in rich text. Pass type to only find entries of that type, and field to only follow one link field of it. Like the other relations it takes the query options, count: true and first: true:
import {cms} from '@/cms'
import {Author, BlogPost} from '@/schema'
import {Query} from 'alinea'
export async function authorPage(path: string) {
return cms.get({
type: Author,
path,
select: {
name: Query.title,
bio: Author.bio,
// The posts whose author field links to this author
posts: Query.referencedBy({
type: BlogPost,
field: BlogPost.author,
select: {title: Query.title, url: Query.url},
orderBy: {desc: BlogPost.publishDate}
}),
// Every entry linking to the author, in any field
mentions: Query.referencedBy({count: true})
}
})
}The entries that link to a translated entry are looked up in its locale. For an entry without a locale, such as a shared author, the linking entries of every locale are returned: pass locale to only get those of one locale.
The same relation is a filter condition: _referenced holds for entries that an entry of the query links to. {_referenced: true} matches entries that anything links to and {_referenced: false} entries nothing links to, such as unused media files:
import {cms} from '@/cms'
import {Author, BlogPost} from '@/schema'
import {Query} from 'alinea'
// Authors of at least one featured post
const featuredAuthors = await cms.find({
type: Author,
filter: {
_referenced: Query.referencedBy({
type: BlogPost,
field: BlogPost.author,
filter: {featured: true}
})
},
select: Query.title
})Filtering on the link
The other way around, "the posts by this author" is a filter on the posts: links store the id of their target as _entry. This is the way to go when you start from the posts, for example to page through them:
import {cms} from '@/cms'
import {BlogPost} from '@/schema'
import {Query} from 'alinea'
export async function postsBy(authorId: string, page: number) {
return cms.find({
type: BlogPost,
// Single link: has, multiple links: includes
filter: {author: {has: {_entry: authorId}}},
select: {title: Query.title, url: Query.url},
orderBy: {desc: BlogPost.publishDate},
skip: page * 10,
take: 10
})
}For multiple links, such as categories, use {categories: {includes: {_entry: id}}}. More combinations are in Filtering and the examples.