@contentrain/query
v5.1.5
Published
Optional type-safe query SDK for Contentrain — generated TypeScript client for platform-independent JSON content
Downloads
942
Maintainers
Readme
@contentrain/query
Optional type-safe generated query SDK for Contentrain.
Start here:
Contentrain stores content as plain JSON and Markdown in a git-backed .contentrain/ directory. Any platform that reads JSON can consume this content directly. This package adds a TypeScript convenience layer that turns content models into a generated JS/TS client with:
- exact TypeScript types from your models
- zero-dependency query runtime
- Node
#contentrainsubpath imports - ESM and CommonJS output
- framework-agnostic usage across app and server environments
🚀 Install
pnpm add @contentrain/queryRequirements:
- Node.js
22+ - a Contentrain project with
.contentrain/config.json
✨ What This Package Provides
@contentrain/query has two roles:
Generator
- reads
.contentrain/config.json, models, and content files - writes
.contentrain/client/ - injects
#contentrainimports into yourpackage.json
- reads
Base runtime
- exports low-level runtime classes for framework SDK authors
- exports
createContentrainClient(projectRoot?)for loading a generated client module
🚀 Quick Start
Generate a client:
npx contentrain-query generateThis writes:
.contentrain/client/
index.mjs
index.cjs
index.d.ts
data/*It also updates your package.json with:
{
"imports": {
"#contentrain": {
"types": "./.contentrain/client/index.d.ts",
"import": "./.contentrain/client/index.mjs",
"require": "./.contentrain/client/index.cjs",
"default": "./.contentrain/client/index.mjs"
}
}
}Then use the generated client in your app:
import { query, singleton, dictionary, document } from '#contentrain'
const posts = query('blog-post')
.locale('en')
.where('status', 'published')
.sort('title')
.all()
const hero = singleton('hero').locale('en').get()
const messages = dictionary('error-messages').locale('en').get()
const article = document('blog-article').locale('en').bySlug('welcome-post')📦 Generated Client API
The generated client exposes four entry points:
query(model)
For collection models.
Supported methods:
locale(lang)where(field, value)— equality shorthandwhere(field, op, value)— operators:eq,ne,gt,gte,lt,lte,in,containssort(field, order?)limit(n)offset(n)include(...fields)count()first()all()
Where operator examples:
query('plans').where('slug', 'ne', 'free').all()
query('plans').where('price', 'gte', 10).where('price', 'lte', 50).all()
query('starters').where('framework', 'in', ['nuxt', 'next']).all()
query('blog').where('title', 'contains', 'Guide').count()singleton(model)
For singleton models.
Supported methods:
locale(lang)include(...fields)get()
dictionary(model)
For dictionary models.
Supported methods:
locale(lang)get()get(key)
document(model)
For markdown/document models.
Supported methods:
locale(lang)where(field, value)— equality shorthandwhere(field, op, value)— same operators asquery()include(...fields)bySlug(slug)count()first()all()
🔗 Relations
Generated clients support relation resolution via include(...).
Examples:
const posts = query('blog-post')
.locale('en')
.include('author', 'tags')
.all()
const settings = singleton('site-settings')
.locale('en')
.include('featured_post')
.get()🧱 Framework SDK Authors
The package root exports runtime primitives and an async loader:
import { createContentrainClient } from '@contentrain/query'
const client = await createContentrainClient(process.cwd())
const posts = client.query('blog-post').locale('en').all()Public root exports:
QueryBuilder,SingletonAccessor,DictionaryAccessor,DocumentQuery— runtime classescreateContentrainClient— local generated client loadercreateContentrain— CDN client factoryMediaAccessor— CDN media manifest readerFormsClient— CDN forms API clientConversationClient— Conversation API clientContentrainError— HTTP error class for CDN modeapplyWhere— shared where filter helper
CDN Transport
For apps that fetch content from Contentrain Studio CDN (SSR, serverless, mobile):
import { createContentrain } from '@contentrain/query/cdn'
const client = createContentrain({
projectId: '350696e8-...',
apiKey: 'crn_live_xxx',
// baseUrl: 'https://studio.contentrain.io/api/cdn/v1' (default)
})
// All CDN queries are async
const posts = await client.collection('faq').locale('en').all()
const hero = await client.singleton('hero').locale('en').get()
const t = await client.dictionary('ui').locale('en').get()
const doc = await client.document('docs').locale('en').bySlug('intro')CDN collection queries support extended operators:
const filtered = await client.collection('faq')
.locale('en')
.where('order', 'gt', 5)
.where('category', 'in', ['general', 'billing'])
.sort('order', 'desc')
.limit(10)
.all()CDN collection queries support count() and entry metadata:
const total = await client.collection('faq').locale('en').count()
// Enrich entries with _meta (status, publish_at, expire_at)
const posts = await client.collection('blog')
.locale('en')
.withMeta()
.all()
// posts[0]._meta → { status: 'published', publish_at: '...', ... }Media
Access the media manifest and resolve asset variant URLs:
const media = client.media()
const assets = await media.list() // All assets with paths
const asset = await media.asset('hero.jpg') // Single asset
// Resolve variant URL
const thumbUrl = media.url(asset, 'thumb') // Full CDN URL
const original = media.url(asset) // Original URL
// Asset metadata
asset.meta.width // 1920
asset.meta.blurhash // 'LEHV6nWB...'
asset.meta.alt // 'Hero image'Forms
Fetch form schema and submit data from external sites:
const form = client.form()
// Get form field configuration
const config = await form.config('contact')
// config.fields → [{ id: 'name', type: 'string', required: true }, ...]
// Submit form data
const result = await form.submit('contact', {
name: 'Alice',
email: '[email protected]',
message: 'Hello!',
}, { captchaToken: 'tok_xxx' })
// result → { success: true, message: 'Thank you!' }Conversation API
Send messages to the AI content agent and manage conversation history:
const conv = client.conversation()
// Send a message — returns complete response with tool results
const response = await conv.send('Create a new blog post about Vue 4')
response.conversationId // 'conv-abc123'
response.message // 'I created the blog post...'
response.toolResults // [{ id: 't-1', name: 'save_content', result: {...} }]
response.usage // { inputTokens: 150, outputTokens: 80 }
// Continue a conversation
const followUp = await conv.send('Now translate it to Turkish', {
conversationId: response.conversationId,
})
// Provide UI context
await conv.send('Update the hero section', {
context: { activeModelId: 'hero', activeLocale: 'en' },
})
// Fetch conversation history
const history = await conv.history('conv-abc123', { limit: 50 })
history.messages // [{ id, role, content, createdAt }, ...]Metadata Endpoints
const manifest = await client.manifest()
const models = await client.models()
const model = await client.model('faq')CDN vs Local
| Aspect | Local (#contentrain) | CDN (createContentrain()) |
|--------|----------------------|---------------------------|
| Data source | Bundled .mjs files | HTTP fetch from CDN |
| Return type | Sync (T[]) | Async (Promise<T[]>) |
| Auth | None | API key required |
| Caching | In-memory (embedded) | ETag-based HTTP cache |
| Use case | SSG, build-time | SSR, client-side, serverless |
CommonJS Usage
Generated clients support CommonJS through init():
const clientModule = require('#contentrain')
const client = await clientModule.init()
const hero = client.singleton('hero').get()🛠 Generation Commands
Via the contentrain CLI (recommended for most users):
contentrain generate # Generate once
contentrain generate --watch # Regenerate on model/content changes
contentrain generate --json # Machine-readable JSON for CIVia contentrain-query (programmatic / build tool flows):
npx contentrain-query generate
npx contentrain-query generate --watch
npx contentrain-query generate --root /path/to/projectOr from TypeScript:
import { generate } from '@contentrain/query/generate'
const result = await generate({ projectRoot: process.cwd() })
console.log(result.generatedFiles.length)📤 Package Exports
Main package:
@contentrain/query— runtime classes +createContentrain()CDN factory
Generator entry:
@contentrain/query/generate— programmatic generation API
CDN transport:
@contentrain/query/cdn— CDN client withHttpTransport, async query classes,MediaAccessor,FormsClient,ConversationClient
🧠 Design Constraints
This package intentionally:
- generates into
.contentrain/client/instead ofnode_modules - uses
package.json#importsinstead of custom alias plugins - ships zero-dependency runtime classes
- keeps the runtime framework-agnostic
- treats generated client output as the primary consumer surface
🛠 Development
From the monorepo root:
pnpm --filter @contentrain/query build
pnpm --filter @contentrain/query test
pnpm --filter @contentrain/query typecheck
pnpm exec oxlint packages/sdk/js/src packages/sdk/js/testsAgent Skill (embedded)
This package ships an embedded Agent Skill at skills/contentrain-query/SKILL.md. AI coding agents can discover and load it for type-safe SDK usage guidance, including:
- QueryBuilder, SingletonAccessor, DictionaryAccessor, DocumentQuery APIs
- Local mode vs CDN mode differences
- Framework-specific bundler configuration (Vite, Next.js, Nuxt, SvelteKit, Metro)
The skill is available via the @contentrain/query/skills/* subpath export.
🔗 Related Packages
contentrain— CLI that runs project initialization, validation, serve, and generation flows@contentrain/mcp— local-first MCP server and core content workflow engine@contentrain/types— shared schema and model definitions@contentrain/rules— agent rules and prompts
📚 Documentation
Full documentation at ai.contentrain.io/packages/sdk.
📄 License
MIT
