@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-content

npm · Source on GitHub

How 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 first

Providers (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.ts JSDoc, 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

ProviderPackage
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 ContentValidationError naming 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 with isContentValidationError(err), never instanceof, so every provider's error is caught the same way.

  • Drafts are excluded by default. readDirectory drops records whose front matter says draft: true unless includeDrafts is set, so a production build cannot publish a draft by forgetting a filter.

  • Slugs are the URL. slug comes 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 by slug, 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.