@mdxdb/fumadocs
v1.9.0
Published
Fumadocs content source adapter for mdxdb
Maintainers
Readme
@mdxdb/fumadocs
Fumadocs content source adapter for mdxdb. Use mdxdb as a content source for Fumadocs documentation sites.
Installation
npm install @mdxdb/fumadocs
# or
pnpm add @mdxdb/fumadocs
# or
yarn add @mdxdb/fumadocsFeatures
- Content Source - Use mdxdb documents as Fumadocs content
- Virtual Files - Create virtual page and meta files
- Dynamic Content - Fetch content on demand with caching
- Transform Support - Custom document transformation
- Type-Safe - Full TypeScript support
Quick Start
import { createSource } from '@mdxdb/fumadocs'
import { loader } from 'fumadocs-core/source'
// Array of [path, document] tuples
const documents = [
['/docs/getting-started.mdx', doc1],
['/docs/api/reference.mdx', doc2],
]
// Create Fumadocs source
const mdxdbSource = createSource(documents, {
basePath: 'docs',
})
// Use with Fumadocs loader
export const source = loader({
source: mdxdbSource,
baseUrl: '/docs',
})API Reference
createSource(documents, options?)
Create a Fumadocs source from an array of mdxld documents.
function createSource(
documents: Array<[string, MDXLDDocument]>,
options?: CreateSourceOptions
): FumadocsSource
interface CreateSourceOptions {
basePath?: string // Base path for content (default: '')
transform?: (doc: MDXLDDocument, path: string) => PageData
filter?: (doc: MDXLDDocument, path: string) => boolean
slugs?: (path: string) => string[]
meta?: Record<string, MetaData> // Folder meta configuration
}Example:
import { createSource } from '@mdxdb/fumadocs'
const source = createSource(documents, {
basePath: 'docs',
// Custom transformation
transform: (doc, path) => ({
title: doc.data.title || 'Untitled',
description: doc.data.description,
content: doc.content,
doc,
}),
// Filter documents
filter: (doc) => doc.data.published !== false,
// Custom slug generation
slugs: (path) => path.replace(/\.mdx?$/, '').split('/'),
// Folder meta configuration
meta: {
'getting-started': {
title: 'Getting Started',
pages: ['installation', 'quick-start', 'configuration'],
defaultOpen: true,
},
},
})createDynamicSource(options)
Create a dynamic source that fetches content on demand with caching.
function createDynamicSource(options: DynamicSourceOptions): {
getSource(): Promise<FumadocsSource>
refresh(): Promise<FumadocsSource>
clearCache(): void
}
interface DynamicSourceOptions extends CreateSourceOptions {
fetchDocuments: () => Promise<Array<[string, MDXLDDocument]>>
cacheTTL?: number // Cache TTL in ms (default: 60000)
}Example:
import { createDynamicSource } from '@mdxdb/fumadocs'
const dynamicSource = createDynamicSource({
fetchDocuments: async () => {
const response = await fetch('/api/docs')
return response.json()
},
cacheTTL: 60000, // 1 minute
basePath: 'docs',
})
// Get source (uses cache if valid)
const source = await dynamicSource.getSource()
// Force refresh
await dynamicSource.refresh()
// Clear cache
dynamicSource.clearCache()queryToSource(documents, options?)
Convert mdxdb query results to Fumadocs source.
function queryToSource(
documents: MDXLDDocument[],
options?: CreateSourceOptions
): FumadocsSourceExample:
import { queryToSource } from '@mdxdb/fumadocs'
import { createFsDatabase } from '@mdxdb/fs'
const db = createFsDatabase({ root: './content' })
const { documents } = await db.list({ type: 'Documentation' })
const source = queryToSource(documents, {
basePath: 'docs',
})Type Guards
import { isPage, isMeta } from '@mdxdb/fumadocs'
for (const file of source.files) {
if (isPage(file)) {
console.log('Page:', file.data.title)
}
if (isMeta(file)) {
console.log('Meta:', file.data.title)
}
}Types
VirtualFile
Base type for virtual files.
interface VirtualFile {
path: string
type: 'page' | 'meta'
data: Record<string, unknown>
slugs?: string[]
}VirtualPage
Page file with content.
interface VirtualPage extends VirtualFile {
type: 'page'
data: PageData
}
interface PageData {
title: string
description?: string
icon?: string
content: string
doc: MDXLDDocument
lastModified?: Date
[key: string]: unknown
}VirtualMeta
Meta file for folder configuration.
interface VirtualMeta extends VirtualFile {
type: 'meta'
data: MetaData
}
interface MetaData {
title?: string
icon?: string
root?: boolean
pages?: string[] // Ordered list of page slugs
defaultOpen?: boolean
description?: string
[key: string]: unknown
}FumadocsSource
Fumadocs-compatible source object.
interface FumadocsSource {
files: VirtualFile[]
}Examples
Static Documentation Site
// source.ts
import { createSource } from '@mdxdb/fumadocs'
import { createFsDatabase } from '@mdxdb/fs'
import { loader } from 'fumadocs-core/source'
const db = createFsDatabase({ root: './docs' })
export async function getSource() {
const { documents } = await db.list({ type: 'Documentation' })
const docTuples = documents.map(doc => [
doc.id || doc.data.slug,
doc
] as [string, typeof doc])
const mdxdbSource = createSource(docTuples, {
basePath: 'docs',
meta: {
'guides': {
title: 'Guides',
pages: ['getting-started', 'configuration', 'deployment'],
},
'api': {
title: 'API Reference',
defaultOpen: false,
},
},
})
return loader({
source: mdxdbSource,
baseUrl: '/docs',
})
}Dynamic Content from API
import { createDynamicSource } from '@mdxdb/fumadocs'
import { loader } from 'fumadocs-core/source'
const dynamicSource = createDynamicSource({
fetchDocuments: async () => {
const response = await fetch(`${process.env.API_URL}/docs`)
const { documents } = await response.json()
return documents.map(doc => [doc.slug, doc])
},
cacheTTL: 5 * 60 * 1000, // 5 minutes
basePath: 'docs',
})
export async function getSource() {
const fumadocsSource = await dynamicSource.getSource()
return loader({
source: fumadocsSource,
baseUrl: '/docs',
})
}
// Webhook handler for cache invalidation
export async function handleContentUpdate() {
await dynamicSource.refresh()
}Filtered Content
import { createSource } from '@mdxdb/fumadocs'
const source = createSource(documents, {
basePath: 'docs',
// Only include published docs
filter: (doc) => {
if (doc.data.draft === true) return false
if (doc.data.publishedAt) {
return new Date(doc.data.publishedAt) <= new Date()
}
return true
},
// Custom transformation
transform: (doc, path) => ({
title: doc.data.title,
description: doc.data.description,
icon: doc.data.icon,
content: doc.content,
doc,
// Add additional metadata
author: doc.data.author,
lastUpdated: doc.data.updatedAt,
tags: doc.data.tags || [],
}),
})Multi-language Docs
import { createSource } from '@mdxdb/fumadocs'
// Group documents by language
const enDocs = documents.filter(([path]) => path.startsWith('/en/'))
const esDocs = documents.filter(([path]) => path.startsWith('/es/'))
const enSource = createSource(enDocs, {
basePath: 'en',
slugs: (path) => path.replace('/en/', '').replace(/\.mdx?$/, '').split('/'),
})
const esSource = createSource(esDocs, {
basePath: 'es',
slugs: (path) => path.replace('/es/', '').replace(/\.mdx?$/, '').split('/'),
})Integration with mdxdb Backends
Filesystem
import { createFsDatabase } from '@mdxdb/fs'
import { queryToSource } from '@mdxdb/fumadocs'
const db = createFsDatabase({ root: './content/docs' })
const { documents } = await db.list()
const source = queryToSource(documents, { basePath: 'docs' })SQLite
import { createSqliteDatabase } from '@mdxdb/sqlite'
import { createSource } from '@mdxdb/fumadocs'
const db = await createSqliteDatabase({ url: './docs.db' })
const things = await db.list({ type: 'Documentation' })
const docTuples = things.map(thing => [
thing.id,
{
id: thing.id,
type: thing.type,
data: thing.data,
content: thing.data.content || '',
}
])
const source = createSource(docTuples)Remote API
import { createApiClient } from 'mdxdb'
import { queryToSource } from '@mdxdb/fumadocs'
const client = createApiClient({
baseUrl: 'https://api.example.com',
apiKey: process.env.API_KEY,
})
const { documents } = await client.list({ type: 'Documentation' })
const source = queryToSource(documents)Related Packages
| Package | Description | |---------|-------------| | mdxdb | Database abstraction layer | | @mdxdb/fs | Filesystem backend | | fumadocs-core | Fumadocs core library | | mdxld | MDX + Linked Data parser |
License
MIT
