Skip to main content

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.

ts
// Client or SSR Vue
import { many, one, paginate } from '@lupinum/ginko-content/client'

// Nitro
import { many, one, paginate } from '@lupinum/ginko-content/server'

Signatures

ts
// 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:

ts
type ContentSelector =
  | { path: string }
  | { route: string }
  | { ref: string }
SelectorMeaning
routePublic application URL, including the locale prefix and the collection route mount
pathCanonical content path, excluding both the locale prefix and the collection route mount
refStable 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.

ts
const page = await one(docs, {
  by: { ref: 'guide:introduction' },
  locale: 'de',
  fallback: true,
  select: ['title', 'description']
})
OptionTypeRequired
byContentSelectoryes
localestringfor i18n handles
fallbackLocaleFallbackno
selectstring[]no
populatePopulateSpecno

many()

Returns an array of localized documents. A miss returns [].

ts
const posts = await many(blog, {
  where: {
    tags: { $contains: 'release' },
    draft: { $ne: true }
  },
  sort: { publishedAt: 'desc' },
  limit: 20,
  select: ['title', 'description', 'publishedAt']
})
OptionTypePurpose
whereQueryWhereFilter using the public operators
sortSortSpecOrdered field-to-'asc'/'desc' clauses
limitnumberMaximum rows; defaults to 100 and cannot exceed 100
skipnumberRows to skip; defaults to 0 and cannot exceed 10,000
localestringRequested locale
fallbackLocaleFallbackLocale fallback policy
selectstring[]Authored fields to retain
populatePopulateSpecReference 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.

ts
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:

ts
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:

ts
const result = await paginate(blog, {
  mode: 'cursor',
  after: previousEndCursor,
  limit: 10,
  sort: { publishedAt: 'desc' }
})
ts
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.

ts
const { doc, explain } = await resolveOne(docs, {
  by: { route: '/de/dokumentation/einstieg' },
  locale: 'de',
  fallback: true
})

Its options are identical to one().

Returns source documents that refer to one selected target document.

ts
const posts = await backlinks(authors, {
  by: { ref: 'author:jane' },
  from: blog,
  via: ['author'],
  sort: { publishedAt: 'desc' }
})
OptionTypePurpose
byContentSelectorSelect the target document
fromHandle, name, or arraySource collection or collections
viastring[] | Record<collection, string[]>Additional relation fields; Ginko also includes fields inferred from relation metadata
sortSortSpecSource-document order
limit / skipnumberBound or offset the results
locale / fallbacklocale optionsResolve target and source locale behavior
selectstring[]Source fields to retain
populatePopulateSpecSource 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:

ts
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.

Returns the collection navigation tree.

ts
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:

ts
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

ts
type LocaleFallback = boolean | 'default' | readonly string[]
ValueBehavior
false or []Exact locale only
trueConfigured 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.