contentrain-sdk — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited contentrain-sdk (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
@contentrain/query is a Prisma-pattern generated client that provides type-safe access to Contentrain content. Generated output lives in .contentrain/client/ — never edit it manually.
npx contentrain generate # Generate client from modelsimport { query, singleton, dictionary, document } from '#contentrain'
// Collection
const posts = query('blog-post').where('category', 'eq', 'engineering').sort('published_at', 'desc').limit(10).all()
// Singleton
const hero = singleton('hero').get()
// Dictionary
const t = dictionary('ui-texts').get()
const loginLabel = t['auth.login.button']
// Document
const doc = document('blog-post').bySlug('getting-started')| Method | Description |
|---|---|
.all() | Get all entries |
.first() | Get first entry |
.count() | Return count of matching entries |
.where(field, value) | Equality filter (shorthand for eq) |
.where(field, op, value) | Operator filter: eq, ne, gt, gte, lt, lte, in, contains |
.sort(field, direction?) | Sort (asc/desc, default asc) |
.limit(n) | Limit results |
.offset(n) | Skip results |
.include(relation) | Resolve relation (1 level deep) |
| Method | Description |
|---|---|
.get() | Get the singleton data |
.include(relation) | Resolve relation fields |
| Method | Description |
|---|---|
.get() | Get all key-value pairs |
.get(key) | Get single value by key |
.get(key, params) | Get with interpolation: {placeholder} → value |
| Method | Description |
|---|---|
.all() | Get all documents |
.bySlug(slug) | Get document by slug |
.first() | Get first document |
.count() | Return count of matching documents |
.where(field, value) | Equality filter (shorthand) |
.where(field, op, value) | Operator filter (same ops as QueryBuilder) |
.include(relation) | Resolve relation fields |
For server-side or client-side apps that fetch content from Contentrain Studio CDN:
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 (return Promise)
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')| Operator | Example | Description |
|---|---|---|
eq | .where('status', 'eq', 'published') | Equals |
ne | .where('status', 'ne', 'draft') | Not equals |
gt | .where('order', 'gt', 5) | Greater than |
gte | .where('order', 'gte', 5) | Greater than or equal |
lt | .where('price', 'lt', 100) | Less than |
lte | .where('price', 'lte', 100) | Less than or equal |
in | .where('category', 'in', ['a','b']) | In array |
contains | .where('tags', 'contains', 'vue') | String/array contains |
// Entry metadata (status, publish_at, expire_at)
const posts = await client.collection('blog').locale('en').withMeta().all()
// Media manifest & variant URLs
const media = client.media()
const asset = await media.asset('hero.jpg')
const url = media.url(asset, 'thumb')
// Forms (config fetch + submit)
const form = client.form()
const config = await form.config('contact')
const result = await form.submit('contact', { name: 'Alice' })
// Conversation API (external AI content operations)
const conv = client.conversation()
const response = await conv.send('Create a blog post about Vue 4')
const history = await conv.history(response.conversationId)| 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 |
await neededawait.contentrain/client/ are immutable — always regenerate, never editcontentrain generate after any model change| Framework | Import | Notes |
|---|---|---|
| Nuxt 3 | import { query } from '#contentrain' | Server-only (server routes, plugins) |
| Next.js | import { query } from '#contentrain' | Works in RSC and API routes |
| Astro | import { query } from '#contentrain' | Works in .astro frontmatter |
| SvelteKit | import { query } from '#contentrain' | Works in +page.server.ts |
| Vue + Vite | import { query } from '#contentrain' | Requires Vite alias config |
| React + Vite | import { query } from '#contentrain' | Requires Vite alias config |
| Node.js | import { query } from '#contentrain' | ESM with subpath imports |
| Reference | Description |
|---|---|
| Bundler Configuration | Vite, Next.js, Nuxt, SvelteKit, Metro alias setup |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.