Add provider search and caching
Return search hits from a provider, attach cache hints, and invalidate affected content after publication.
Select provider search when the external content source already maintains the index:
export default defineNuxtConfig({
content: {
search: {
engine: 'provider'
}
}
})Implement search on the provider and return the route fields Ginko needs with every hit:
import type { ContentProvider } from '@lupinum/ginko-content/provider'
export default {
name: 'acme',
capabilities: {
query: {
operators: ['$eq'],
pagination: ['offset']
}
},
async query(event, query) {
return executeQuery(query)
},
async search(event, request) {
const hits = await acmeSearch({
query: request.term,
locale: request.locale,
collections: request.collections
})
return hits.map(hit => ({
title: hit.title,
excerpt: hit.excerpt,
score: hit.score,
route: {
collection: hit.collection,
canonicalKey: hit.canonicalKey,
locale: hit.locale,
contentPath: hit.contentPath
}
}))
}
} satisfies ContentProviderGinko turns those route fields into public path values and the standard result shape. Application code keeps using useContentSearch and does not import the provider SDK:
const search = await useContentSearch({
locale: computed(() => locale.value),
limit: 10
})The provider owns index freshness. Send every publish, update, delete, and route change to the search backend. Tag cached responses with search:{locale} and purge that locale tag after a relevant content change.
Attach cache hints to provider results
Configure the cache adapter module and the secret used to authenticate revalidation:
export default defineNuxtConfig({
content: {
cache: '~~/server/content-cache',
revalidate: {
token: process.env.GINKO_CONTENT_REVALIDATE_TOKEN!
}
}
})Without a token, Ginko does not register /api/_content/revalidate.
Wrap a provider result with withContentCache to attach freshness hints:
import { withContentCache } from '@lupinum/ginko-content/provider'
async query(event, query) {
const result = await executeQuery(query)
return withContentCache(result, {
tags: [`collection:${query.collection}`],
maxAge: 300,
swr: 60
})
}Cache tags are application-defined. Use one vocabulary in provider results and publish webhooks, for example:
entry:{collection}:{id}for one source entrycollection:{collection}for listsroute:{path}for a rendered routenav:{collection}:{locale}for navigationsearch:{locale}for searchsitemapfor sitemap output
The runtime calls apply after rendering a response. It calls invalidate after an authenticated revalidation request only when the adapter implements that capability:
import { setHeader } from 'h3'
import { contentCacheHeaders } from '@lupinum/ginko-content/server'
import type { ContentCacheAdapter } from '@lupinum/ginko-content/provider'
export default {
name: 'application-cache',
async apply(event, hint) {
for (const [name, value] of contentCacheHeaders(hint)) {
setHeader(event, name, value)
}
await applyPlatformCacheTags(event, hint.tags ?? [])
},
async invalidate(input) {
await purgePlatformCache(input)
}
} satisfies ContentCacheAdaptercontentCacheHeaders handles standard freshness headers. The two platform helpers represent the tag API and purge API of the cache you deploy to; they must use the same tag names.
Adapters that only apply response headers must omit invalidate; revalidation then returns 501 revalidation_not_supported.
Invalidate after publication
Sign production requests with HMAC-SHA256 over <timestamp>.<event id>.<raw request body>. Send a millisecond Unix timestamp in x-ginko-signature-timestamp, the event ID in x-ginko-revalidation-event, and the sha256=... signature in x-ginko-signature. The timestamp must be within five minutes of the server clock.
The JSON body accepts only tags and paths:
{
"tags": ["entry:docs:intro", "collection:docs", "nav:docs:en", "search:en", "sitemap"],
"paths": ["/docs/intro"]
}When a route changes, send both the old and the new public path. Purging only the new path leaves the old URL serving a cached page that no longer exists.
The endpoint accepts at most 32 KiB of JSON, 200 combined tag and path entries, and 1,000 characters per entry.
Leave revalidate.allowUnsigned disabled in production. Token-only requests are intended for local or compatibility workflows.
Start with collection-wide invalidation: purge the affected collection, navigation locales, search locales, sitemap, and public paths. Narrow those targets only after the provider has a tested dependency map.