@molecule/api-content
Core interface · content · API (Node) · v1.0.3 · Apache-2.0
Content core interface: read a directory of front-matter markdown files into typed records (front matter, body, slug, path, draft), validating loudly; bond a provider for the parsing
npm install @molecule/api-contentHow it works
@molecule/api-content is the content core interface on the API (Node) side: the API your app calls, with no vendor inside.
Choose the implementation by bonding one of its 1 provider: @molecule/api-content-markdown.
import { readContentDirectory, setProvider } from '@molecule/api-content'
import { provider as markdown } from '@molecule/api-content-markdown'
setProvider(markdown) // once, at startup (bonds.ts) or in a build script
const posts = await readContentDirectory('content/posts', {
requiredFields: ['title', 'date', 'description'],
})
// → [{ slug, path, frontMatter, body, draft: false, date, title }, …] newest firstProviders (1): @molecule/api-content-markdown
Works with: @molecule/api-bond, @molecule/api-i18n
Live demos
Running apps built with Molecule that install this package — open the app or study the template.
Reference
Content core interface for molecule.dev: a directory of front-matter files
(markdown posts, docs pages, changelog entries) read into typed records.
Defines the ContentProvider contract and the record shape; bond packages
(e.g. @molecule/api-content-markdown) do the parsing. Application and
build code calls readContentDirectory() and never parses a file itself.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { readContentDirectory, setProvider } from '@molecule/api-content'
import { provider as markdown } from '@molecule/api-content-markdown'
setProvider(markdown) // once, at startup (bonds.ts) or in a build script
const posts = await readContentDirectory('content/posts', {
requiredFields: ['title', 'date', 'description'],
})
// → [{ slug, path, frontMatter, body, draft: false, date, title }, …] newest first
Type
core
Installation
npm install @molecule/api-content @molecule/api-bond @molecule/api-i18n
API
Interfaces
ContentRecord
One content file, parsed. The shape every content provider returns, whatever the on-disk format (markdown with YAML front matter today).
interface ContentRecord {
/**
* URL-safe identifier: lowercase letters, digits and hyphens, from the
* front matter's `slug` when present, else the file name without its
* extension. Unique within one `readDirectory` call.
*/
slug: string
/** The file's path as given to the provider (absolute when the directory was). */
path: string
/** The parsed front matter as a plain object (never null; `{}` when the file has none). */
frontMatter: Record<string, unknown>
/** The body after the front matter, with the leading blank line removed. */
body: string
/** `true` when the front matter says `draft: true`. */
draft: boolean
/**
* The front matter's `date` normalized to an ISO-8601 string when present
* and valid; `undefined` when the file has no date.
*/
date?: string
/** The front matter's `title` when it is a string. */
title?: string
}
ContentReadOptions
Options for parse and readDirectory.
interface ContentReadOptions {
/** Include records whose front matter says `draft: true`. Defaults to `false`. */
includeDrafts?: boolean
/**
* Front-matter fields every record must carry (non-empty). A file missing
* one fails loudly with a `ContentValidationError`; nothing is skipped
* silently. Defaults to `[]`.
*/
requiredFields?: string[]
}
ContentSource
What parse reads: the file's source and the path it came from (for errors and record.path).
interface ContentSource {
/** The file's full text. */
source: string
/** Where it came from; used in error messages and `record.path`. */
path: string
}
ContentValidationError
The error every provider raises for a malformed file: a missing closing
front-matter fence, invalid YAML, front matter that is not a mapping, a
required field missing or empty, an invalid slug or date, two files with
the same slug. Identified by code, never by class, so any provider's
error is caught the same way (isContentValidationError).
interface ContentValidationError extends Error {
/** Always `'CONTENT_VALIDATION'`. */
code: 'CONTENT_VALIDATION'
/** The offending file's path. */
path: string
/** What was wrong, in one sentence. */
reason: string
/** The 1-based line in the file when known. */
line?: number
}
ContentProvider
Content provider interface. Bond packages implement it against one on-disk format; application and build code reads records and never parses files.
interface ContentProvider {
/** Provider name identifier. */
readonly name: string
/**
* Parses one file's source into a record.
*
* @param input - The file's source and path.
* @param options - Validation options.
* @returns The record.
* @throws {ContentValidationError} When the file is malformed.
*/
parse(input: ContentSource, options?: ContentReadOptions): ContentRecord
/**
* Reads every content file under a directory into records, newest first
* (by `date`, then by `slug`), drafts excluded unless asked for.
*
* @param dir - The directory to read.
* @param options - Validation and filtering options.
* @returns The records.
* @throws {ContentValidationError} On the first malformed file, or a duplicate slug.
*/
readDirectory(dir: string, options?: ContentReadOptions): Promise<ContentRecord[]>
}
Functions
setProvider(provider)
Registers a content provider as the active singleton. Called by bond packages during application startup, or by a build script.
function setProvider(provider: ContentProvider): void
provider— The content provider implementation to bond.
getProvider()
Retrieves the bonded content provider, throwing if none is configured.
function getProvider(): ContentProvider
Returns: The bonded content provider.
hasProvider()
Checks whether a content provider is currently bonded.
function hasProvider(): boolean
Returns: true if a content provider is bonded.
parseContent(input, options)
Parses one file's source into a record through the bonded provider.
function parseContent(input: ContentSource, options?: ContentReadOptions): ContentRecord
input— The file's source and path.options— Validation options.
Returns: The record.
readContentDirectory(dir, options)
Reads every content file under a directory through the bonded provider.
function readContentDirectory(dir: string, options?: ContentReadOptions): Promise<ContentRecord[]>
dir— The directory to read.options— Validation and filtering options.
Returns: The records, newest first, drafts excluded unless asked for.
isContentValidationError(error)
Whether an error is a content validation error, from any provider.
function isContentValidationError(error: unknown): boolean
error— The caught value.
Returns: true when it carries code: 'CONTENT_VALIDATION'.
Available Providers
| Provider | Package |
|---|---|
| Markdown files | @molecule/api-content-markdown |
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bond^1.0.1@molecule/api-i18n^1.0.1
Runtime Dependencies
-
@molecule/api-bond -
@molecule/api-i18n -
Validation fails loudly, never silently. A file with an unclosed front-matter fence, invalid YAML, a front matter that is not a mapping, a missing required field, an invalid slug or date, or a slug another file already uses throws a
ContentValidationErrornaming the file (and the line when known). A build that swallows that error ships a site with a post missing; catch it only to report it. Check the shape withisContentValidationError(err), neverinstanceof, so every provider's error is caught the same way. -
Drafts are excluded by default.
readDirectorydrops records whose front matter saysdraft: trueunlessincludeDraftsis set, so a production build cannot publish a draft by forgetting a filter. -
Slugs are the URL.
slugcomes from the front matter when present, else the file name without its extension, and must be lowercase letters, digits and hyphens. Two files with the same slug are an error, not a last-one-wins. -
Order is newest first by
date, then byslug, so an index page can take the records as they come. -
This package is server-side. Read content in a build step or an API route; never import it from client components.