Content types

Core Content, content, cache, source, database, parser and registry types exported by comark-content.

The core types describing content, the manifest, sources, the cache, the database, parsers, and the Content instance.

Every export is re-exported from the main comark-content entry point:

import type {
  ComarkContent,
  ContentOptions,
  ContentHooks,
  ContentFile,
  ContentListFile,
  ContentFileMeta,
  Manifest,
  NavigationItem,
  Source,
  Database,
  RelationalDatabase,
  CacheArtifact,
  CacheOptions,
  CacheStrategy,
  CacheArtifactOptions,
  Cache,
  SearchOptions,
  SearchResult,
  Logger,
  OnErrorMode,
} from 'comark-content'

Every read API is built on two types: ContentFile (the full file, with parsed nodes) and ContentListFile (the same shape minus nodes).

ContentFile<TData, TMeta>

The full parsed file: the shape content.get() returns. Built around three fields: path, data (the frontmatter / parsed data), and meta (Content- and plugin-provided metadata), plus the parsed nodes.

interface ContentFile<TData = Record<string, any>, TMeta = Record<string, unknown>> {
  path: string                      // '/posts/hello'
  data: TData                       // the file's frontmatter / parsed data
  meta: TMeta & ContentFileMeta         // kind, type, key, source, extension, stem, partial (+ plugin fields)
  nodes: MarkdownDocument['nodes']      // the parsed AST
}

ContentFileMeta

The metadata every Content file carries, regardless of which API returned it. Plugins and parsers extend it through ContentFile's TMeta parameter.

interface ContentFileMeta {
  kind: 'document' | 'media'        // whether it's a document or a media file
  type: string                      // mime type, e.g. 'text/markdown'
  key: string                       // 'default/posts/hello.md'
  source: string                    // the source the file belongs to
  extension: string                 // '.md' (with the leading dot)
  stem: string                      // 'posts/hello' (path without extension)
  partial: boolean                 // true when only frontmatter was parsed (body skipped)
}

ContentListFile<TData>

The lightweight representation: ContentFile without nodes. This is what content.list(), content.query() and content.stat() return, and what the manifest stores. Fetch the full file (including parsed nodes) with content.get().

type ContentListFile<TData = Record<string, any>> = Omit<ContentFile<TData>, 'nodes'>

Manifest

The in-memory index of every entry by path. Built from frontmatter on content.init(), it backs list() and navigation(), and is serialised into an artifact by content.cache.manifest(). See the manifest concept.

interface Manifest {
  sources: string[]                          // source names loaded into the index
  items: Record<string, ContentListFile>         // every entry, keyed by path
  time: number                               // build timestamp (ms)
  schemas?: Record<string, JsonSchema>       // per-source `data` JSON Schema
}

The recursive shape produced by content.navigation().

interface NavigationItem {
  title: string
  description?: string
  path: string
  stem?: string
  children?: NavigationItem[]
  page?: false
  [key: string]: unknown
}

Source

The boundary that loads raw content. Implement this to build a custom source.

interface Source {
  prefix?: string
  keys: () => Promise<string[]> | string[]
  getItem: (key: string) => Promise<string> | string
  getItemRaw: (key: string) => Promise<unknown> | unknown
  watch?: (cb: (event: 'update' | 'remove', key: string) => void) => () => void
}

FSSourceOptions

The optional second argument of the fs source. It extends unstorage's fs driver options (everything except base, which fs takes as its first argument) and adds the source-level fields below.

interface FSSourceOptions extends Omit<FSStorageOptions, 'base'> {
  prefix?: string
  exclude?: string[]
  schema?: JsonSchema
}

See Filesystem options for what each field does.

GithubSource

The argument of the github source. It extends unstorage's github driver options (repo, branch, ttl, token, apiURL, cdnURL) and adds path plus the source-level fields below.

interface GithubSource extends Omit<GithubOptions, 'dir'> {
  path?: string
  prefix?: string
  exclude?: string[]
  schema?: JsonSchema
}

See GitHub options for what each field does.

Database

The storage contract for parsed files. It is passed to the SQL-aware plugins (sqlQuery, sqliteFullTextSearch).

interface Database {
  upsert: (source: string, data: ContentFile) => void | Promise<void>
  delete: (source: string, key: string) => void | Promise<void>
  get: (source: string, key: string) => ContentFile | Promise<ContentFile>
  list: (source: string) => ContentFile[] | Promise<ContentFile[]>
  clear: (source: string) => void | Promise<void>
}

RelationalDatabase

Extends Database with the raw SQL methods the query and full-text search plugins actually call.

interface RelationalDatabase extends Database {
  first: <T>(query: string, params?: unknown[]) => Promise<T | undefined>
  all: <T>(query: string, params?: unknown[]) => Promise<T[]>
  execute: <T>(query: string, params?: unknown[]) => Promise<T>
}

CacheArtifact

Serialised representation of a source's entries or the manifest, produced by content.cache.manifest() / content.cache.snapshot() and consumed by the cache.loadManifest / cache.loadSnapshot loaders.

interface CacheArtifact {
  name: string                      // 'manifest' or a source name
  data: string                      // JSON, optionally gzipped + base64
  checksum: string                  // SHA-256 of `data`
  size: number                      // bytes
}

CacheOptions

The cache object passed to comarkContent(). Configures the driver, freshness, and snapshot loaders. See Caching.

interface CacheOptions {
  driver?: Driver                   // unstorage driver; defaults to in-memory
  ttl?: number                      // ms before an entry is stale; omit to never expire
  strategy?: CacheStrategy          // how a stale entry is served; defaults to 'swr'
  loadManifest?: () => Promise<CacheArtifact | null | undefined> | CacheArtifact | null | undefined
  loadSnapshot?: (source: string) => Promise<CacheArtifact | null | undefined> | CacheArtifact | null | undefined
}

CacheStrategy

How a stale entry is served: see strategy.

type CacheStrategy = 'swr' | 'none'

CacheArtifactOptions

Options for producing a CacheArtifact via content.cache.manifest() / content.cache.snapshot().

interface CacheArtifactOptions {
  compress?: boolean                // gzip + base64 the payload; defaults to true
  fresh?: boolean                   // re-read the source (true) or build from cache (false)
}

Cache

The resolved cache exposed as content.cache. Backed by an unstorage driver, it stores ContentFile entries keyed <source>:<path> and the manifest. See Refreshing on demand.

interface Cache {
  get<T = ContentFile>(key: string): Promise<T | null>
  set<T = ContentFile>(key: string, value: T): Promise<void>
  keys(prefix?: string): Promise<string[]>
  refresh(source: string): Promise<ContentFile[]>       // re-read a source, reconcile the manifest
  invalidate(key: string): Promise<void>            // drop one entry
  expire(key: string): Promise<void>                // mark one entry stale (swr refreshes on next read)
  manifest(opts?: CacheArtifactOptions): Promise<CacheArtifact | null>   // fresh: true (default) inits first; fresh: false reads the persisted index
  snapshot(source: string, opts?: CacheArtifactOptions): Promise<CacheArtifact | null>
}

ContentHooks

Events you can subscribe to via content.hooks.hook(name, callback).

interface ContentHooks {
  'watch:file:update': (sourceName: string, key: string, file: ContentFile) => void
  'watch:file:remove': (sourceName: string, key: string) => void
  'file:parsed': (ctx: FileParsedContext) => void | Promise<void>
  'typegen:field': (ctx: TypegenFieldContext) => void | Promise<void>
  'file:upsert': (sourceName: string, key: string, file: ContentFile) => void
  'file:remove': (sourceName: string, key: string) => void
  'file:bulk:load': (sourceName: string, items: ContentFile[]) => void
}

See FileParsedContext for the file:parsed payload and TypegenFieldContext for typegen:field. The markdown-fields, schema-validation, and references plugins are reference implementations.

SearchOptions

Options accepted by content.search(), added by the sqliteFullTextSearch plugin.

interface SearchOptions {
  limit?: number
  fields?: ('title' | 'content')[]
  minTermLength?: number
  weights?: { title?: number; content?: number; heading?: boolean }
  snippet?: { columns?: ('title' | 'content')[]; around?: number; tag?: string }
}

SearchResult

A single result row returned by content.search().

interface SearchResult {
  source: string
  id: string
  title: string
  titles: string[]
  level: number
  content: string
  rank: number
  snippets?: { title?: string; content?: string }
}

Parser

Registered via addParser to turn a raw file into a parsed result. ParserContext describes the file; ParserResult is what the parser returns.

interface ParserContext {
  filepath: string
  extension: string
  partial: boolean
  read: () => string | Promise<string>
}

type ParserResult = {
  kind?: 'media' | 'document'
  type?: string
  data?: ContentFile['data']
  meta?: Record<string, any>
  nodes?: ContentFile['nodes']
  partial?: boolean
}

type Parser = (ctx: ParserContext) => ParserResult | null | Promise<ParserResult | null>

ContentOptions

The options object passed to comarkContent(). See Options.

interface ContentOptions {
  source?: Source
  sources?: Record<string, Source>
  cache?: CacheOptions | false
  plugins?: ReadonlyArray<ContentPlugin<any>>
  markdown?: ParserOptions
  basePath?: string          // default '/api/content'
  baseURL?: string
  logger?: Logger | false    // default console-backed; false silences output
  onError?: OnErrorMode      // default 'warn'
}

Logger

The minimal logger interface used across the Content instance pipeline. Provide one via ContentOptions.logger to redirect output, or pass false to silence it. The active logger is exposed as content.logger and in the plugin and parser contexts.

interface Logger {
  debug: (...args: unknown[]) => void
  info: (...args: unknown[]) => void
  warn: (...args: unknown[]) => void
  error: (...args: unknown[]) => void
}

OnErrorMode

How the pipeline reacts when a file fails to parse. Set the default via ContentOptions.onError; the built-in json, yaml, and markdown-fields plugins accept a per-plugin override.

type OnErrorMode =
  | 'throw'   // surface the error and abort
  | 'warn'    // log via the logger and drop the file (default)
  | 'ignore'  // silently drop the file

ComarkContent

The Content instance returned by comarkContent(); plugin methods are mixed in on top. Every member is documented on the comarkContent page.

interface ComarkContent {
  options: ContentOptions
  status: 'created' | 'initializing' | 'initialized-partial' | 'initialized-full'
  cache: Cache
  logger: Logger
  hooks: Hookable<ContentHooks>
  init(opts?: { partial?: boolean; ignoreCache?: boolean }): Promise<void>
  loadSnapshot(sourceName: string): Promise<void>
  list: ContentList
  stat(key: string): ContentListFile | undefined
  getSource(name: string): Source | undefined
  get: ComarkGet
  navigation(sources?: string[]): Promise<NavigationItem[]>
  readonly manifest: Manifest
  handler(request: Request): Promise<Response>
  update(sourceName: string, file: ContentFile): Promise<void>
  remove(sourceName: string, key: string): Promise<void>
  watch(): Promise<() => Promise<void>>
}

ContentList

The typed signature of content.list(): known source names narrow each item's data.

interface ContentList {
  <K extends keyof ContentRegistry>(sources: K[]): Promise<ContentListFile<SourceData<K>>[]>
  <Data = RegistryData>(sources?: string[]): Promise<ContentListFile<Data>[]>
}

Registry and query types

Generated and advanced types powering path/source autocompletion and the sqlQuery builder. ContentRegistry and ContentPaths are empty by default and augmented by generateSourceTypes().

TypeDescription
ContentRegistryMap of source name to { data, row }; augmented by generated types. Empty by default.
ContentPathsMap of known document path to data type; augmented by generated types. Empty by default.
RegistryRow<K>The query-row type for source K.
SourceData<K>The data (frontmatter) type for source K.
RegistryDataUnion of every source's data type (the fallback for get/list).
PathData<P>The data type for a known path P.
Ref<K>Branded path string pointing at source K — emitted by typegen for reference fields.
QueryRowBaseBase columns present on every query row.
QueryRowA flattened query row (path, meta.*, data.* columns).
SourceQueryBuilderThe chainable builder returned by content.query().
SqlQueryMethods, ComarkQueryThe typed query() method the sqlQuery plugin adds.
ComarkGetThe typed signature of content.get().

Option and method types

Per-feature option and method bags, each documented in full on its feature page.

TypeUsed by
SqliteOptionssqlite()
SqliteWasmOptionssqliteWasm()
ContentRowthe SQLite __contents table row shape
IndexSearchOptionsthe full-text search index builder
JsonSchemaa source's data schema (the source schema option)