{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @mdxdb/fumadocs - Fumadocs content source adapter for mdxdb\n *\n * Provides integration between mdxdb document stores and Fumadocs,\n * allowing you to use mdxdb as a content source for Fumadocs documentation sites.\n *\n * @packageDocumentation\n */\n\nimport type { MDXLDDocument } from 'mdxld'\n\n/**\n * Virtual file types for Fumadocs source\n */\nexport interface VirtualFile {\n  path: string\n  type: 'page' | 'meta'\n  data: Record<string, unknown>\n  slugs?: string[]\n}\n\nexport interface VirtualPage extends VirtualFile {\n  type: 'page'\n  data: PageData\n}\n\nexport interface VirtualMeta extends VirtualFile {\n  type: 'meta'\n  data: MetaData\n}\n\n/**\n * Page data structure for Fumadocs\n */\nexport interface PageData {\n  /** Page title */\n  title: string\n  /** Page description */\n  description?: string\n  /** Icon identifier */\n  icon?: string\n  /** Full MDX/markdown content */\n  content: string\n  /** Original mdxld document */\n  doc: MDXLDDocument\n  /** Last modified date */\n  lastModified?: Date\n  /** Additional frontmatter data */\n  [key: string]: unknown\n}\n\n/**\n * Meta data structure for folder configuration\n */\nexport interface MetaData {\n  /** Folder title */\n  title?: string\n  /** Icon identifier */\n  icon?: string\n  /** Whether this is a root folder */\n  root?: boolean\n  /** Ordered list of page slugs */\n  pages?: string[]\n  /** Whether folder is expanded by default */\n  defaultOpen?: boolean\n  /** Folder description */\n  description?: string\n  /** Allow additional properties */\n  [key: string]: unknown\n}\n\n/**\n * Source configuration\n */\nexport interface FumadocsSourceConfig {\n  pageData: PageData\n  metaData: MetaData\n}\n\n/**\n * Fumadocs-compatible source object\n */\nexport interface FumadocsSource {\n  files: VirtualFile[]\n}\n\n/**\n * Options for creating a Fumadocs source from mdxdb\n */\nexport interface CreateSourceOptions {\n  /**\n   * Base path for content (e.g., 'docs' for /docs/*)\n   * @default ''\n   */\n  basePath?: string\n\n  /**\n   * Transform document data before adding to source\n   */\n  transform?: (doc: MDXLDDocument, path: string) => PageData\n\n  /**\n   * Filter documents to include\n   */\n  filter?: (doc: MDXLDDocument, path: string) => boolean\n\n  /**\n   * Custom slug generation from path\n   */\n  slugs?: (path: string) => string[]\n\n  /**\n   * Meta files configuration for folder ordering\n   */\n  meta?: Record<string, MetaData>\n}\n\n/**\n * Convert a path to slugs array\n */\nfunction pathToSlugs(path: string, basePath: string = ''): string[] {\n  // Normalize basePath - remove leading/trailing slashes\n  const normalizedBasePath = basePath.replace(/^\\/+|\\/+$/g, '')\n\n  // Remove leading slashes from path for comparison\n  let cleanPath = path.replace(/^\\/+/, '')\n\n  // Remove base path prefix\n  if (normalizedBasePath && cleanPath.startsWith(normalizedBasePath)) {\n    cleanPath = cleanPath.slice(normalizedBasePath.length)\n  }\n\n  // Remove leading/trailing slashes and file extension\n  cleanPath = cleanPath.replace(/^\\/+|\\/+$/g, '').replace(/\\.(mdx?|md)$/i, '')\n\n  // Handle index files\n  if (cleanPath.endsWith('/index') || cleanPath === 'index') {\n    cleanPath = cleanPath.replace(/\\/?index$/, '')\n  }\n\n  // Split into slugs\n  return cleanPath ? cleanPath.split('/') : []\n}\n\n/**\n * Extract title from document\n */\nfunction extractTitle(doc: MDXLDDocument): string {\n  // Check frontmatter\n  if (doc.data.title && typeof doc.data.title === 'string') {\n    return doc.data.title\n  }\n\n  // Extract from first heading in content\n  const headingMatch = doc.content.match(/^#\\s+(.+)$/m)\n  if (headingMatch?.[1]) {\n    return headingMatch[1].trim()\n  }\n\n  return 'Untitled'\n}\n\n/**\n * Default transform function\n */\nfunction defaultTransform(doc: MDXLDDocument, path: string): PageData {\n  return {\n    title: extractTitle(doc),\n    description: doc.data.description as string | undefined,\n    icon: doc.data.icon as string | undefined,\n    content: doc.content,\n    doc,\n    ...doc.data,\n  }\n}\n\n/**\n * Create a Fumadocs source from an array of mdxld documents\n *\n * @param documents - Array of [path, document] tuples\n * @param options - Source configuration options\n * @returns Fumadocs-compatible source object\n *\n * @example\n * ```ts\n * import { createSource } from '@mdxdb/fumadocs'\n * import { loader } from 'fumadocs-core/source'\n *\n * const documents = [\n *   ['/docs/getting-started.mdx', doc1],\n *   ['/docs/api/reference.mdx', doc2],\n * ]\n *\n * const mdxdbSource = createSource(documents, {\n *   basePath: 'docs',\n * })\n *\n * export const source = loader({\n *   source: mdxdbSource,\n *   baseUrl: '/docs',\n * })\n * ```\n */\nexport function createSource(\n  documents: Array<[string, MDXLDDocument]>,\n  options: CreateSourceOptions = {}\n): FumadocsSource {\n  const {\n    basePath = '',\n    transform = defaultTransform,\n    filter,\n    slugs: customSlugs,\n    meta = {},\n  } = options\n\n  const files: VirtualFile[] = []\n  const folderPaths = new Set<string>()\n\n  // Process documents\n  for (const [path, doc] of documents) {\n    // Apply filter\n    if (filter && !filter(doc, path)) {\n      continue\n    }\n\n    // Generate slugs\n    const slugs = customSlugs ? customSlugs(path) : pathToSlugs(path, basePath)\n\n    // Track folder paths for meta files\n    if (slugs.length > 1) {\n      for (let i = 1; i < slugs.length; i++) {\n        folderPaths.add(slugs.slice(0, i).join('/'))\n      }\n    }\n\n    // Transform document to page data\n    const pageData = transform(doc, path)\n\n    // Create virtual page file\n    const page: VirtualPage = {\n      path,\n      type: 'page',\n      data: pageData,\n      slugs,\n    }\n\n    files.push(page)\n  }\n\n  // Add meta files for folders\n  for (const folderPath of folderPaths) {\n    const metaPath = `${basePath ? '/' + basePath : ''}/${folderPath}/meta.json`\n    const metaData = meta[folderPath] || {}\n\n    const metaFile: VirtualMeta = {\n      path: metaPath,\n      type: 'meta',\n      data: {\n        title: metaData.title,\n        icon: metaData.icon,\n        root: metaData.root,\n        pages: metaData.pages,\n        defaultOpen: metaData.defaultOpen,\n        description: metaData.description,\n      },\n    }\n\n    files.push(metaFile)\n  }\n\n  return { files }\n}\n\n/**\n * Options for the dynamic source adapter\n */\nexport interface DynamicSourceOptions extends CreateSourceOptions {\n  /**\n   * Function to fetch documents dynamically\n   */\n  fetchDocuments: () => Promise<Array<[string, MDXLDDocument]>>\n\n  /**\n   * Cache TTL in milliseconds\n   * @default 60000 (1 minute)\n   */\n  cacheTTL?: number\n}\n\n/**\n * Create a dynamic Fumadocs source that fetches content on demand\n *\n * Useful for remote content sources or when you need to refresh content\n * without rebuilding the entire site.\n *\n * @param options - Dynamic source options\n * @returns Object with methods to get source and refresh cache\n *\n * @example\n * ```ts\n * import { createDynamicSource } from '@mdxdb/fumadocs'\n *\n * const dynamicSource = createDynamicSource({\n *   fetchDocuments: async () => {\n *     const response = await fetch('/api/docs')\n *     return response.json()\n *   },\n *   cacheTTL: 60000,\n * })\n *\n * // Get cached or fresh source\n * const source = await dynamicSource.getSource()\n *\n * // Force refresh\n * await dynamicSource.refresh()\n * ```\n */\nexport function createDynamicSource(options: DynamicSourceOptions) {\n  const { fetchDocuments, cacheTTL = 60000, ...sourceOptions } = options\n\n  let cachedSource: FumadocsSource | null = null\n  let cacheTime = 0\n\n  return {\n    /**\n     * Get the source, using cache if valid\n     */\n    async getSource(): Promise<FumadocsSource> {\n      const now = Date.now()\n\n      if (cachedSource && now - cacheTime < cacheTTL) {\n        return cachedSource\n      }\n\n      const documents = await fetchDocuments()\n      cachedSource = createSource(documents, sourceOptions)\n      cacheTime = now\n\n      return cachedSource\n    },\n\n    /**\n     * Force refresh the cache\n     */\n    async refresh(): Promise<FumadocsSource> {\n      const documents = await fetchDocuments()\n      cachedSource = createSource(documents, sourceOptions)\n      cacheTime = Date.now()\n      return cachedSource\n    },\n\n    /**\n     * Clear the cache\n     */\n    clearCache(): void {\n      cachedSource = null\n      cacheTime = 0\n    },\n  }\n}\n\n/**\n * Helper to convert mdxdb query results to Fumadocs source\n *\n * @example\n * ```ts\n * import { queryToSource } from '@mdxdb/fumadocs'\n * import { db } from '@mdxdb/fs'\n *\n * const docs = await db.query({ type: 'Documentation' })\n * const source = queryToSource(docs, { basePath: 'docs' })\n * ```\n */\nexport function queryToSource(\n  documents: MDXLDDocument[],\n  options: CreateSourceOptions = {}\n): FumadocsSource {\n  const docTuples: Array<[string, MDXLDDocument]> = documents.map((doc) => [\n    doc.id || (doc.data.slug as string) || '',\n    doc,\n  ])\n  return createSource(docTuples, options)\n}\n\n/**\n * Type guard to check if a virtual file is a page\n */\nexport function isPage(file: VirtualFile): file is VirtualPage {\n  return file.type === 'page'\n}\n\n/**\n * Type guard to check if a virtual file is meta\n */\nexport function isMeta(file: VirtualFile): file is VirtualMeta {\n  return file.type === 'meta'\n}\n\n// Re-export mdxld types for convenience\nexport type { MDXLDDocument } from 'mdxld'\n"],"mappings":"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","names":[]}