Skip to content
Packages

@docvia/source

Runtime collection model for consuming compiled docvia output.

@docvia/source defines the runtime data model that frameworks use to consume compiled docvia documentation. It declares the page, collection, and page-tree types and the createCollection / createSource factories that the generated .docvia/source.ts file relies on. It contains no Markdown loader. Under the in-place architecture the host bundler's ?docvia transform compiles each .md file as a module, and these factories just wire those modules into collections.

Install

pnpm add @docvia/source

Package exports

SubpathContentsNotes
.Re-exports ./runtimeThe default entry: types only, no runtime values. Does not re-export the ./internal factories.
./runtimeTypes onlydocviaPage, docviaCollection, docviaSource, PageTree, HydrationManifest.
./internalcreateCollection, createSource, ModuleExportsUsed by the generated .docvia/source.ts.

createCollection and createSource live in ./internal and are intentionally not re-exported from .. Application code generally does not import them directly, because the compiler emits a .docvia/source.ts that calls them for you. Import from @docvia/source/internal only when you are building generated output by hand.

This package ships no binary.

Runtime types (@docvia/source/runtime)

HydrationManifest

type HydrationManifest = any;

An opaque manifest describing the interactive islands embedded in a page. Its concrete shape is renderer-specific.

namespace PageTree

The navigation tree model. A Root holds an ordered list of Nodes; each Node is one of three shapes.

namespace PageTree {
  interface Root {
    name: string;
    children: Node[];
  }

  interface Item {
    type: "page";
    name: string;
    url: string;
    $id?: string;
  }

  interface Folder {
    type: "folder";
    name: string;
    children: Node[];
    index?: Item;
    defaultOpen?: boolean;
    $id?: string;
  }

  interface Separator {
    type: "separator";
    name: string;
  }

  type Node = Item | Folder | Separator;
}
MemberFieldMeaning
RootnameDisplay name of the tree root.
RootchildrenTop-level nodes.
ItemtypeAlways "page".
ItemnameLink label.
ItemurlResolved page URL.
Item$idOptional stable identifier.
FoldertypeAlways "folder".
FolderchildrenNested nodes.
FolderindexOptional Item rendered as the folder's own landing page.
FolderdefaultOpenWhether the folder starts expanded.
Folder$idOptional stable identifier.
SeparatortypeAlways "separator".
SeparatornameSeparator label.

interface docviaPage

interface docviaPage<TFrontmatter = unknown> {
  slugs: string[];
  url: string;
  data: TFrontmatter;
  content: any;
  manifest: HydrationManifest;
  headings?: Array<{ depth: number; text: string; id: string }>;
}
FieldTypeMeaning
slugsstring[]Path segments identifying the page.
urlstringResolved URL.
dataTFrontmatterValidated frontmatter.
contentanyRenderer-native compiled content (e.g. a component module).
manifestHydrationManifestIsland hydration manifest.
headingsarrayOptional flat list of headings for building a table of contents.

interface docviaCollection

interface docviaCollection<TFrontmatter = unknown, _TRouteKey extends string = string> {
  ready(): Promise<void>;
  getPage(slugs: string[] | undefined): Promise<docviaPage<TFrontmatter> | undefined>;
  getPages(): Array<{ slugs: string[]; url: string; data: TFrontmatter }>;
  get pageTree(): PageTree.Root;
  getPageTree(): PageTree.Root;
  generateParams<TSlug extends string = "slug">(slug?: TSlug): Record<TSlug, string[]>[];
}
MemberSignatureBehavior
ready() => Promise<void>Resolves page metadata, so the synchronous members below return real data. No-op on the server (metadata is already in hand); required on the browser build before reading getPages / pageTree.
getPage(slugs) => Promise<docviaPage | undefined>Resolves a single page by slug segments. Returns undefined when no page matches. Always accurate, since it awaits the page module regardless of build.
getPages() => Array<{ slugs, url, data }>Lightweight listing of every page, without loading content. data is the page's full frontmatter, including any custom fields your schema defines. Synchronous; see ready.
pageTreegetter => PageTree.RootThe navigation tree as a property. Synchronous; see ready.
getPageTree() => PageTree.RootThe navigation tree as a method (equivalent to pageTree).
generateParams(slug?) => Record<TSlug, string[]>[]Produces route params for static generation, keyed by the given slug name.

interface docviaSource

interface docviaSource {
  collections: Record<string, docviaCollection<unknown, string>>;
}

The top-level container. collections maps each collection name to its docviaCollection.

Internal factories (@docvia/source/internal)

interface ModuleExports

interface ModuleExports {
  meta: unknown;
  content: any;
  manifest: unknown;
}

The shape of a compiled page module emitted into .docvia/. meta carries frontmatter (including order), content is the renderer-native module, and manifest is the hydration manifest.

createCollection

function createCollection<TFrontmatter, TRouteKey extends string>(opts: {
  name: string;
  baseUrl: string;
  routeKeys: readonly TRouteKey[];
  getModule(slug: string): Promise<ModuleExports | undefined>;
  getEagerModules(): Promise<Record<string, ModuleExports> | null>;
  sourceModuleUrl: string;
}): docviaCollection<TFrontmatter, TRouteKey>;

Builds a docviaCollection from a set of route keys and module loaders.

OptionTypePurpose
namestringCollection name.
baseUrlstringURL prefix prepended to every page.
routeKeysreadonly TRouteKey[]All known page slugs in the collection.
getModule(slug) => Promise<ModuleExports | undefined>Lazily loads one page module.
getEagerModules() => Promise<Record<string, ModuleExports> | null>Loads every module up front (or null to disable eager mode).
sourceModuleUrlstringURL of the generated source module, used for relative resolution.

Behavior:

  • Builds a parent → children page tree from routeKeys.
  • Sorts siblings by meta.order, then by slug.
  • A slug that has children becomes a Folder; otherwise it becomes an Item.
  • getPage normalizes the incoming slugs and keys on slugs.join("/") || "index".
  • generateParams(slug = "slug") maps every route key to { [slug]: segments }, where the index page maps to an empty array [].

createSource

function createSource<TCollections>(
  collections: TCollections,
): docviaSource & { collections: TCollections };

Wraps a record of collections into a docviaSource. The return type preserves the concrete TCollections shape, so accessing docviaSource.collections.docs stays fully typed.

Generated source example

The compiler emits a .docvia/source.ts that uses the internal factories. Under the in-place architecture getModule resolves the Markdown file as a module via the host bundler's ?docvia transform (no JSON, no filesystem read):

import { createCollection, createSource } from "@docvia/source/internal";

const docs = createCollection({
  name: "docs",
  baseUrl: "/",
  routeKeys: ["index", "getting-started", "guides/install"],
  // `?docvia` is compiled in place by the bundler; content lives in the .md
  getModule: (slug) => import(`../src/docs/${slug}.md?docvia`),
  // eager metadata for the page tree / getPages()
  getEagerModules: () => _modules.docs,
  sourceModuleUrl: import.meta.url,
});

// Each collection is exported by name, and all of them together as `docviaSource`.
export { docs };
export const docviaSource = createSource({ docs });

Consuming it from a framework app. The import specifier is bundler-specific:

// Vite (and SvelteKit): the Vite plugin serves a virtual module
import { docs, docviaSource } from "virtual:docvia/source";

// Next.js (webpack + Turbopack): the plugin aliases the bare specifier
import { docs, docviaSource } from "docvia/source";

// Import the collection by name…
const page = await docs.getPage(["getting-started"]);
const tree = docs.pageTree;

// …or reach it through the source, which is handy when the name is dynamic.
const same = await docviaSource.collections.docs.getPage(["getting-started"]);

[!WARNING] Importing a collection is server-only. virtual:docvia/source statically imports every compiled page so that getPages() and pageTree have their metadata up front. Import it from a universal module, such as a SvelteKit +page.ts or a client component, and your entire content set is bundled into the browser, which defeats the bundle-size benefit the compiler exists to provide.

Read it from server-only modules (+page.server.ts, +layout.server.ts, a React Server Component, getStaticProps). If you genuinely need a collection on the client, import the lazy counterpart instead: virtual:docvia/source/browser (Vite) / docvia/source/browser (Next). Its getModule uses () => import("…md?docvia"), so each page is its own chunk and only the page you ask for is fetched.

Metadata timing on the browser build

On the server the page metadata is in hand synchronously, so getPages() and pageTree are correct on first read. On the browser build it is resolved through a dynamic import per page and cannot be produced synchronously, so reading those two before it lands yields slug-derived titles and alphabetical ordering (and logs a warning). Await ready() first:

await docs.ready();
const tree = docs.pageTree; // real titles, frontmatter order

ready() resolves immediately on the server, so universal code can always await it. getPage() is unaffected, since it awaits the page module either way.