Query API
Typed document reads shared by the browser, SSR, and Nitro.
Import query functions from @lupinum/ginko-content/client in Vue code and @lupinum/ginko-content/server in Nitro code. Both surfaces use the same handles, options, and result types; server functions take the active H3Event first.
// Client or SSR Vue
import { many, one, paginate } from '@lupinum/ginko-content/client'
// Nitro
import { many, one, paginate } from '@lupinum/ginko-content/server'Signatures
// Client
one(handle, options)
many(handle, options?)
paginate(handle, options)
resolveOne(handle, options)
backlinks(handle, options)
surround(handle, options)
navigation(handle, options?)
// Server
one(event, handle, options)
many(event, handle, options?)
paginate(event, handle, options)
resolveOne(event, handle, options)
backlinks(event, handle, options)
surround(event, handle, options)
navigation(event, handle, options?)An i18n collection handle makes locale and the options object required where route context cannot provide them. Dynamic string collection names cannot carry that compile-time guarantee.
Selectors
Single-document operations accept exactly one selector:
type ContentSelector =
| { path: string }
| { route: string }
| { ref: string }| Selector | Meaning |
|---|---|
route | Public application URL, including the locale prefix and the collection route mount |
path | Canonical content path, excluding both the locale prefix and the collection route mount |
ref | Stable alias authored in content |
Supplying more than one selector is invalid.
For a collection mounted at route: { en: '/guide', de: '/leitfaden' }, one
document is reachable as route: '/de/leitfaden/erste-schritte' or as
path: '/erste-schritte' with locale: 'de'. The collection index is
path: '/' in every locale. where: { path } uses the same canonical
vocabulary.
A selector that matches nothing is not an error: one() returns null and
many() / paginate() return an empty result set. If you are porting queries
from 0.3.0-rc.4, where path still carried the collection mount, see the
Ginko version migration.
one()
Returns the first matching localized document or null.
const page = await one(docs, {
by: { ref: 'guide:introduction' },
locale: 'de',
fallback: true,
select: ['title', 'description']
})| Option | Type | Required |
|---|---|---|
by | ContentSelector | yes |
locale | string | for i18n handles |
fallback | LocaleFallback | no |
select | string[] | no |
populate | PopulateSpec | no |
many()
Returns an array of localized documents. A miss returns [].
const posts = await many(blog, {
where: {
tags: { $contains: 'release' },
draft: { $ne: true }
},
sort: { publishedAt: 'desc' },
limit: 20,
select: ['title', 'description', 'publishedAt']
})| Option | Type | Purpose |
|---|---|---|
where | QueryWhere | Filter using the public operators |
sort | SortSpec | Ordered field-to-'asc'/'desc' clauses |
limit | number | Maximum rows; defaults to 100 and cannot exceed 100 |
skip | number | Rows to skip; defaults to 0 and cannot exceed 10,000 |
locale | string | Requested locale |
fallback | LocaleFallback | Locale fallback policy |
select | string[] | Authored fields to retain |
populate | PopulateSpec | Reference fields to resolve |
Without sort, order is provider-defined. Always sort results whose presentation or paging depends on stable order.
paginate()
paginate() has offset and cursor modes. New code should always write mode: 'offset' explicitly; omitting it remains an offset-mode compatibility default.
const result = await paginate(blog, {
mode: 'offset',
page: 2,
limit: 10,
sort: { publishedAt: 'desc' }
})Offset mode accepts the many() options except skip, plus page and mode. page defaults to 1; limit defaults to 10. It returns:
interface OffsetPaginationResult<T> {
mode: 'offset'
data: T[]
page: number
limit: number
total: number
pageCount: number
hasNext: boolean
hasPrevious: boolean
nextPage: number | null
previousPage: number | null
}Cursor mode replaces page with the opaque endCursor from the preceding result:
const result = await paginate(blog, {
mode: 'cursor',
after: previousEndCursor,
limit: 10,
sort: { publishedAt: 'desc' }
})interface CursorPaginationResult<T> {
mode: 'cursor'
data: T[]
limit: number
endCursor: string | null
hasNext: boolean
}Cursor mode has no exact total or page number. Offset mode rejects after; cursor mode rejects page.
resolveOne()
Returns { doc, explain }. The document has the same shape and miss behavior as one(); explain records the requested and normalized selector, the match, and any fallback.
const { doc, explain } = await resolveOne(docs, {
by: { route: '/de/dokumentation/einstieg' },
locale: 'de',
fallback: true
})Its options are identical to one().
backlinks()
Returns source documents that refer to one selected target document.
const posts = await backlinks(authors, {
by: { ref: 'author:jane' },
from: blog,
via: ['author'],
sort: { publishedAt: 'desc' }
})| Option | Type | Purpose |
|---|---|---|
by | ContentSelector | Select the target document |
from | Handle, name, or array | Source collection or collections |
via | string[] | Record<collection, string[]> | Additional relation fields; Ginko also includes fields inferred from relation metadata |
sort | SortSpec | Source-document order |
limit / skip | number | Bound or offset the results |
locale / fallback | locale options | Resolve target and source locale behavior |
select | string[] | Source fields to retain |
populate | PopulateSpec | Source references to resolve |
If neither via nor relation metadata identifies a field for a source collection, the query throws instead of scanning arbitrary fields.
surround()
Returns route-bearing navigation items before and after one selected document:
const { previous, next } = await surround(docs, {
by: { ref: 'guide:installation' },
locale: 'en',
select: ['description']
})Options are by, locale, fallback, and select. fallback defaults to true while resolving the selected document. Both entries may be null; returned entries contain title, path, and selected fields rather than full document bodies.
navigation()
Returns the collection navigation tree.
const tree = await navigation(docs, {
locale: 'de',
where: { draft: { $ne: true } },
sort: { order: 'asc' },
select: ['description', 'icon']
})Options are where, sort, select, locale, and fallback. Linkable nodes contain path; structural group nodes may omit it. Children use the same shape recursively.
Population
populate maps a top-level reference field to its target collection handle or dynamic name:
const article = await one(articles, {
by: { ref: 'article:intro' },
locale: 'en',
fallback: true,
populate: {
author: authors,
topics
},
select: ['title']
})Each authored reference string is resolved by ref with the parent query's locale and fallback. A scalar becomes a localized document or null; an array becomes an array of resolved documents with missing targets omitted. Populated fields survive select even when not named separately.
One result set may populate at most 1,000 authored references. Repeated references to the same target are fetched once per result set, and at most eight population reads run concurrently.
When relation metadata declares a target collection, using a different target in populate throws. Population is supported by one, many, paginate, resolveOne, backlinks, and useContentPage; it is not an option for navigation or surround.
Locale fallback
type LocaleFallback = boolean | 'default' | readonly string[]| Value | Behavior |
|---|---|
false or [] | Exact locale only |
true | Configured fallback chain followed by the collection default |
'default' | Collection default only |
['de', 'en'] | Explicit ordered fallback locales; 'default' may appear in the array |
Projection and errors
select narrows authored and parsed fields. Ginko still preserves document identity plus locale, route, and resolution, so a projected result can be identified and linked. The exact shape is documented in the document envelope.
Queries reject malformed selectors, unsupported operators, invalid limits or pagination combinations, populate-target mismatches, and provider operations or capabilities that cannot execute the plan. They never fetch every provider record to emulate an unsupported operation silently.