Package Exports
- @contentrain/types
Readme
@contentrain/types
Shared TypeScript types for the Contentrain ecosystem.
Start here:
This package is the common schema layer used by:
@contentrain/mcpcontentrain@contentrain/query@contentrain/rules
It defines the stable type vocabulary for models, config, metadata, validation, scanning, and context files.
โจ When To Use It
Use @contentrain/types when you are:
- building tooling on top of Contentrain packages
- sharing model/config types between packages in a workspace
- authoring framework integrations or SDK extensions
- consuming Contentrain JSON structures directly in TypeScript
๐ Install
pnpm add @contentrain/types๐ฆ What It Exports
Core unions:
FieldTypeModelKindContentStatusContentSourceWorkflowModeStackTypePlatformContextSourceCollectionRuntimeFormatLocaleStrategy
Core interfaces:
FieldDefModelDefinitionContentrainConfigVocabularyEntryMetaAssetEntryValidationErrorValidationResultScaffoldTemplateScanCandidateDuplicateGroupGraphNodeProjectGraphScanCandidatesResultScanSummaryResultContextJson
Storage/runtime helper types:
SingletonContentFileCollectionContentFileDictionaryContentFileCollectionEntryCollectionContentOutputSingletonMetaCollectionMetaDocumentMetaDictionaryMeta
Validate functions (pure, dependency-free):
validateSlug(slug)โ kebab-case slug validationvalidateEntryId(id)โ entry ID format validationvalidateLocale(locale, config)โ locale format + config support checkdetectSecrets(value)โ detect potential secrets in field valuesvalidateFieldValue(value, fieldDef)โ full field schema validation (type, required, min/max, pattern, select)
Serialize functions (pure, dependency-free):
sortKeys(obj, fieldOrder?)โ recursive key sorting for canonical outputcanonicalStringify(data, fieldOrder?)โ deterministic JSON serializationgenerateEntryId()โ 12-char hex ID generationparseMarkdownFrontmatter(content)โ parse YAML frontmatter + body from markdownserializeMarkdownFrontmatter(data, body)โ serialize data + body into markdown frontmatter
๐งญ Stability
This package is intended to be the shared public contract across the Contentrain ecosystem.
In practice that means:
- types exported from the package root are the public surface
- packages should depend on these shared definitions instead of redefining domain types
- breaking changes here should be treated as ecosystem-level breaking changes
๐งช Quick Example
import type {
ContentrainConfig,
FieldDef,
ModelDefinition,
ValidationResult,
} from '@contentrain/types'
const fields: Record<string, FieldDef> = {
title: { type: 'string', required: true },
slug: { type: 'slug', required: true, unique: true },
}
const model: ModelDefinition = {
id: 'blog-post',
name: 'Blog Post',
kind: 'collection',
domain: 'blog',
i18n: true,
fields,
}
const config: ContentrainConfig = {
version: 1,
stack: 'next',
workflow: 'review',
locales: { default: 'en', supported: ['en', 'tr'] },
domains: ['blog'],
}
const result: ValidationResult = {
valid: true,
errors: [],
}๐ Import Style
Type-only usage:
import type { ModelDefinition, ContentrainConfig } from '@contentrain/types'Mixed usage (types + runtime functions):
import type { FieldDef, ValidationError } from '@contentrain/types'
import {
validateFieldValue,
validateSlug,
detectSecrets,
canonicalStringify,
parseMarkdownFrontmatter,
} from '@contentrain/types'๐ข Studio Integration
Studio (Nuxt 4, web) cannot import @contentrain/mcp directly because MCP depends on Node.js-only packages (simple-git, @modelcontextprotocol/sdk). The validate and serialize functions in this package are pure, dependency-free, and browser-compatible โ designed for Studio to share the same validation contract as MCP.
What Studio gets from @contentrain/types
| Function | Use case |
|---|---|
validateSlug(slug) |
Form validation for document slugs |
validateEntryId(id) |
Validate collection entry IDs |
validateLocale(locale, config) |
Locale picker validation |
detectSecrets(value) |
Content editor secret detection warnings |
validateFieldValue(value, fieldDef) |
Full field-level validation in content forms |
canonicalStringify(data, fieldOrder?) |
Preview canonical JSON output |
parseMarkdownFrontmatter(content) |
Document editor frontmatter parsing |
serializeMarkdownFrontmatter(data, body) |
Document editor serialization |
generateEntryId() |
Client-side entry ID generation |
SECRET_PATTERNS |
Extend or customize secret detection |
What stays in MCP (not available to Studio directly)
These require file system I/O or Node.js dependencies:
checkRelation()โ validates relation references against actual content files on diskvalidateProject()โ full project validation with file readingwriteContent()/deleteContent()โ content persistence with git worktreeresolveContentDir()/resolveJsonFilePath()โ path resolution withnode:path
Studio implementation example
// composables/useContentValidation.ts
import type { FieldDef, ContentrainConfig, ValidationError } from '@contentrain/types'
import { validateFieldValue, validateSlug, detectSecrets } from '@contentrain/types'
export function useContentValidation(config: ContentrainConfig) {
function validateEntry(
fields: Record<string, FieldDef>,
data: Record<string, unknown>,
): ValidationError[] {
const issues: ValidationError[] = []
for (const [fieldName, fieldDef] of Object.entries(fields)) {
// Schema validation (type, required, min/max, pattern, select)
const fieldErrors = validateFieldValue(data[fieldName], fieldDef)
for (const err of fieldErrors) {
issues.push({ ...err, field: fieldName })
}
// Secret detection on all string values
const secretErrors = detectSecrets(data[fieldName])
for (const err of secretErrors) {
issues.push({ ...err, field: fieldName })
}
}
return issues
}
return { validateEntry, validateSlug }
}// composables/useDocumentEditor.ts
import { parseMarkdownFrontmatter, serializeMarkdownFrontmatter } from '@contentrain/types'
export function useDocumentEditor() {
function loadDocument(rawMarkdown: string) {
const { frontmatter, body } = parseMarkdownFrontmatter(rawMarkdown)
return { frontmatter, body }
}
function saveDocument(data: Record<string, unknown>, body: string): string {
return serializeMarkdownFrontmatter(data, body)
}
return { loadDocument, saveDocument }
}Unique constraints and relation validation
validateFieldValue handles schema-level checks. Two things require external state:
- Unique constraints โ need to check across all entries (Studio should query its API/store)
- Relation references โ need to verify target entries exist (Studio should query its content API)
These are left to Studio's server-side or API layer to implement on top of the pure validation.
๐ง Design Role
@contentrain/types exists so every package in the monorepo speaks the same domain language.
Examples:
- MCP validates and writes
ModelDefinition - CLI reads
ContextJson - SDK codegen consumes
ModelDefinitionandFieldDef - AI rules align with the same model and workflow vocabulary
- Studio uses the same validation functions in the browser
This package should stay:
- small
- zero runtime dependencies
- browser + Node.js compatible
- stable
- free of package-specific behavior
๐ Development
From the monorepo root:
pnpm --filter @contentrain/types build
pnpm --filter @contentrain/types test
pnpm --filter @contentrain/types typecheck๐ Related Packages
@contentrain/mcpcontentrain@contentrain/query@contentrain/rules
๐ License
MIT