Top-level app integration — wires runtime, router, and adapters into a Vite app.
collectAihuModulesfunction collectAihuModules(plugins: ReadonlyArray<unknown>): Map<string, unknown>Collect every aihu module's resolved options from a plugin array.
createAppfunction createApp(config?: AppConfig): AppHandlecriticalPathfunction criticalPath(opts: CriticalPathOptions = {}): PlugindeclareAihuModulefunction declareAihuModule<TOptions, TPlugins extends readonly unknown[]>( aihuModule: string, options: TOptions, plugins: TPlugins, ): TPluginsAttach the module contract to a plugin (or plugin array).
defineConfigfunction defineConfig(config: AihuConfig): AihuConfigDefine the aihu application configuration.
loadAihuConfigasync function loadAihuConfig( root: string, options: { readonly mode?: string; readonly command?: 'build' | 'serve' } = {}, ): Promise<LoadedAihuConfig | null>Load an aihu project's config from its Vite config file.
validateAihuConfigfunction validateAihuConfig(config: AihuConfig): voidValidate a config object without the `defineConfig` ceremony.
viteAihuPluginfunction viteAihuPlugin(config?: AihuConfig): PluginOption[]viteAihuPlugin() — composed Vite plugin for aihu SPA projects.
AIHU_CONFIG_KEYSconst AIHU_CONFIG_KEYS: ReadonlyArray<string>Keys aihu owns, derived from the schema rather than hand-listed.
AIHU_CONFIG_PLUGINconst AIHU_CONFIG_PLUGINPlugin name carrying the config handle.
AihuConfigErrorclass AihuConfigError extends ErrorThrown by `defineConfig` when configuration validation fails.
AdapterContextinterface AdapterContext {
/** Absolute path to Vite's output directory (resolved build.outDir). */
readonly outDir: string
/** Absolute path to the project root (Vite's config.root). */
readonly root: string
/**
* Route definitions derived from the pages directory scan.
* Contains pattern, segments, and name — module() is irrelevant at adapt() time.
*/
readonly routes: ReadonlyArray<RouteDefinition>
/** The resolved AihuConfig passed to viteAihuPlugin(). */
readonly config: import('./config.ts').AihuConfig
/**
* Emit a file relative to outDir. Creates parent directories as needed.
* path is relative to outDir.
*/
emitFile(path: string, content: string): Promise<void>
/**
* Copy a file or directory (absolute paths). Recursive. Overwrites existing.
*/
copy(src: string, dest: string): Promise<void>
/**
* Write a file at an absolute path. Creates parent directories as needed.
*/
writeFile(absolutePath: string, content: string): Promise<void>
/**
* Generate the source text of a server handler module.
*
* Returns a JS string that imports routes and createRequestRouter,
* wires the handler, and exports `{ handler }`. The adapter appends
* its platform-specific export wrapper.
*/
createHandlerSource(options?: CreateHandlerSourceOptions): string
}Context provided to adapter.adapt() after Vite's closeBundle completes.
AihuAdapterinterface AihuAdapter {
/**
* Unique adapter name. Used in log output and error messages.
* Convention: '<platform>' e.g. 'cloudflare', 'vercel', 'node'.
*/
readonly name: string
/**
* Called by viteAihuPlugin's closeBundle hook after Vite finishes
* writing all output files. The adapter reads from context.outDir,
* transforms the build output into the platform's required format,
* and writes the final deployment artifact.
*/
adapt(context: AdapterContext): Promise<void>
}The AihuAdapter interface.
AihuConfiginterface AihuConfig {
/** Directory layout overrides. */
readonly dir?: DirConfig
/**
* Output mode. Supports `'spa'` (default) and `'static'` (SSG prerender).
* defineConfig throws AihuConfigError for any other value.
*/
readonly output?: OutputMode
/**
* Site-level configuration. `site.url` is the absolute base URL used by the
* `'static'` output mode to resolve relative canonical/OG/Twitter URLs.
*/
readonly site?: SiteConfig
/**
* Aihu plugins. Order is preserved.
* Appended after the three framework plugins (compiler, router, agent-readiness).
*/
readonly plugins?: ReadonlyArray<AihuPlugin>
/** Runtime configuration split — public values are inlined in the client bundle. */
readonly runtimeConfig?: RuntimeConfig
/**
* App-level values made available to all components as bare identifiers.
* Declared here for documentation and future build-time validation; the
* values are hoisted into globalThis by createApp() at runtime.
*
* @example
* export default defineConfig({ provide: { supabase, checkAuth } })
*/
readonly provide?: Record<string, unknown>
/** HTML <head> metadata. */
readonly app?: AppHeadConfig
/** Passthrough to Vite's UserConfig. Merged via Vite's config() hook. */
readonly vite?: VitePassthrough
/**
* Opt-in agent-readiness integration.
* Requires { name: string } at minimum.
* When absent or false, a no-op plugin is substituted.
*/
readonly agentReadiness?: AgentReadinessConfig | false
/**
* Deployment adapter. Transforms the Vite build output into the target
* platform's required format. Called after vite build completes.
* When absent, no post-build transformation is applied (manual deployment).
*/
readonly adapter?: AihuAdapter
/**
* Router-related app config (arch-5 M1).
*
* WARNING: `router.viewTransitions` is declared but NOT wired — nothing
* forwards it from here to the router runtime, so setting it has no effect.
* `defineConfig` warns when you do. The working lever is the
* `<router viewTransitions>` prop. Tracked for wiring or removal.
*/
readonly router?: RouterConfig
/** Compiler options forwarded to `aihuCompilerPlugin`. */
readonly compiler?: CompilerConfig
/** `aihu dev` options. Read by the CLI, not by Vite. */
readonly dev?: DevConfig
/** `aihu build` / `aihu dev` build options. Read by the CLI, not by Vite. */
readonly build?: BuildConfig
/** `aihu-tsc` options. Read by the CLI, not by Vite. */
readonly typecheck?: TypecheckConfig
/**
* CSS / styling integration. Currently surfaces the project-wide
* `shadowMode` forwarded to the compiler. Set to `{ shadowMode: 'light' }`
* when using a cascade-dependent CSS framework (Tailwind, UnoCSS, Pico).
*/
readonly css?: CssConfig
}AihuModuleApiinterface AihuModuleApi<TOptions = unknown> {
/**
* Stable module id — the package name, e.g. `'@aihu/ui'`.
*
* Keyed on this rather than the plugin `name` because a package may
* contribute several plugins (Vite has no dedupe and a factory returning an
* array is the norm), and consumers want the package, not each plugin.
*/
readonly aihuModule: string
/** The resolved options for this module, after its own defaults. */
getOptions(): TOptions
}The contract EVERY aihu package that contributes build behaviour satisfies.
AihuPluginApiinterface AihuPluginApi {
/** The config object the user passed to `viteAihuPlugin()`. */
getAihuConfig(): AihuConfig
}The public API handle attached to aihu's marker plugin.
AppConfiginterface AppConfig {
/** Id of the outlet element in index.html. Default: 'outlet' */
outletId?: string
/**
* App-level values hoisted into globalThis before any component runs.
* Use this for singletons (db clients, auth helpers, i18n) that are
* referenced as bare identifiers inside @state blocks.
*
* NOTE — this is NOT `@aihu/context`'s `provide()`. Despite the shared name
* it is a different mechanism: values land on `globalThis`, not in a context
* token, and `inject(Token)` will never see them. For real token-based DI at
* the app root, use {@link AppConfig.context} below.
*
* @example
* createApp({ provide: { supabase, checkAuth } })
*/
provide?: Record<string, unknown>
/**
* App-root context scope. Runs ONCE at bootstrap, inside a real
* `@aihu/context` scope owned by the outlet element — so every
* `provide(Token, value)` made here is visible to `inject(Token)` in every
* page, layout and nested component the app renders.
*
* This is the app-root seam that several packages' docs already assume
* exists ("provide at app root" — `@aihu/magna`'s `MagnaFetchToken`,
* `@aihu-plugin/data`'s `ResourceStoreToken`). Without it, `provide()` at
* bootstrap lands in no scope at all and `inject()` silently returns the
* token default forever — `inject` falls back rather than throwing, so the
* failure is invisible. `@aihu/app` installs the router's own `RouteContext`
* through the same scope, immediately BEFORE this callback runs, so an app
* may also deliberately override it.
*
* Must be synchronous: `provide()` only writes to the active scope, and the
* scope is torn down when the callback returns. Providing from an `await`ed
* continuation is a silent no-op.
*
* @example
* import { provide } from '@aihu/context'
* import { MagnaFetchToken, createMagnaFetch } from '@aihu/magna'
*
* createApp({
* context: () => provide(MagnaFetchToken, createMagnaFetch({ url })),
* })
*/
context?: () => void
/**
* Rendering mode from the server config. Controls whether the client
* wires the hydration function into the runtime.
*
* - 'ssr' | 'hybrid' (default): wires _setHydrate so the client can
* take over from server-rendered HTML without re-creating DOM.
* - 'spa': skips _setHydrate — no SSR HTML to hydrate, mount-only.
*
* Pass `defineAihuConfig(…).rendering?.mode` from your server config.
* Default: 'ssr' (hydration wired).
*/
rendering?: { mode?: AppRenderingMode }
/**
* Site-level config. `site.url` is the absolute base URL used to resolve
* relative per-route `canonical` / `og:*` / `twitter:*` values into absolute
* URLs as the head is applied on client navigation (mirrors the SSG path's
* `AihuConfig.site.url`). When absent, relative values are emitted unchanged.
*/
site?: { url?: string }
/**
* Global `<head>` defaults (typically `aihu.config.ts`'s `app.head`). On every
* navigation these defaults are folded under the active route's head
* (`routeHeadToSsrHead`'s `globalHead`) and re-applied — so a route that omits
* a field falls back to the global default, and global tags persist across
* navigations while route-only tags are cleaned up.
*/
head?: HeadConfig
}Inline runtime configuration accepted by createApp().
AppHandleinterface AppHandle {
/**
* Switch the active layout on the current route without navigating.
* `setLayout(name)` forces that layout; `setLayout(null)` forces none. The
* override is reset on the next navigation. Wire it to a UI toggle or expose
* it to an `@agent` action (e.g. `setLayout("compact")`).
*/
setLayout(name: string | null): Promise<void>
}AppHeadConfiginterface AppHeadConfig {
readonly head?: HeadConfig
}CreateHandlerSourceOptionsinterface CreateHandlerSourceOptions {
/**
* Import specifier for the compiled routes manifest module.
* Default: './routes-manifest.js'
*/
routesSpecifier?: string
/**
* Import specifier for @aihu/server.
* Default: '@aihu/server'
* Adapters that bundle server deps may override this to a relative path.
*/
serverSpecifier?: string
}Options for AdapterContext.createHandlerSource().
CriticalPathOptionsinterface CriticalPathOptions {
/** Modules forbidden in the critical path. */
readonly deny?: readonly CriticalPathRule[]
/**
* Max gzipped size of the critical path (all statically-entry-reachable
* chunks combined). Gzip, not raw, because that is what crosses the wire —
* and it matches `scripts/size.ts`'s existing convention.
*/
readonly maxBytes?: number
/** Report without failing the build. Default `false`. */
readonly warnOnly?: boolean
}CriticalPathRuleinterface CriticalPathRule {
/** Tested against the module id (absolute path, posix-normalized). */
pattern: RegExp
/**
* Printed on violation. Say WHY it must stay out and what to do instead —
* this message is the whole value of the rule when it fires months later.
*/
reason: string
}A module pattern that must never become statically reachable from an entry.
DirConfiginterface DirConfig {
/** Directory to scan for page routes. Default: 'pages' */
readonly pages?: string
/** Directory to scan for layout files. Default: 'src/layouts' */
readonly layouts?: string
/** Public static assets directory. Default: 'public' */
readonly public?: string
/**
* Directory to scan for components. Default: 'src/components'
*
* `@aihu/router`'s `componentsDir` has always existed but was unreachable
* from here: `viteAihuPlugin` forwarded only `pagesDir` and `layoutsDir`, so
* changing it meant calling `viteRouterIntegration()` yourself — i.e.
* abandoning `viteAihuPlugin` entirely.
*/
readonly components?: string
}HeadConfiginterface HeadConfig {
readonly title?: string
/** Default: 'UTF-8' */
readonly charset?: string
/** Default: 'width=device-width, initial-scale=1' */
readonly viewport?: string
readonly meta?: ReadonlyArray<Record<string, string>>
}LoadedAihuConfiginterface LoadedAihuConfig {
/** The evaluated config. `{}` when the plugin was called with no argument. */
readonly config: AihuConfig
/** Absolute path of the Vite config file it came from. */
readonly configFile: string
/**
* Files the config depends on, from Vite's own dependency tracking. A watcher
* should invalidate when any of these change — this is what makes a dev
* server restart on config edits.
*/
readonly dependencies: ReadonlyArray<string>
/**
* Every aihu module registered in the Vite config, keyed by `aihuModule`.
*
* This is what gives the CLI coverage that grows on its own: a new package
* that ships a plugin with an `AihuModuleApi` handle shows up here with no
* change to `@aihu/app`, to this function, or to any consumer.
*/
readonly modules: ReadonlyMap<string, unknown>
}RouterConfiginterface RouterConfig {
/**
* When `true`, `<a>` navigation wraps in `document.startViewTransition()`
* if the browser supports the View Transitions API. No-op in unsupported
* browsers (graceful degradation). Default: `false`.
*
* SSR safety: the wrapping is browser-only — server-rendered HTML is
* unchanged, and hydration is unaffected.
*/
readonly viewTransitions?: boolean
}Router-related app config (arch-5 M1, RFC-A5-012).
RuntimeConfiginterface RuntimeConfig {
readonly public?: Record<string, unknown>
/** V0: accepted but ignored at runtime (server-side enforcement deferred to V1). */
readonly private?: Record<string, unknown>
}Runtime configuration split.
SiteConfiginterface SiteConfig {
/**
* Absolute base URL of the deployed site (e.g. `https://example.com`).
* Used by the `'static'` (SSG) output mode to resolve relative per-route
* `canonical` / `og:*` / `twitter:*` URLs into absolute URLs (passed as
* `siteUrl` to @aihu/server's `routeHeadToSsrHead`). When absent, relative
* URLs are emitted unchanged.
*/
readonly url?: string
}Site-level configuration.
AgentReadinessConfigtype AgentReadinessConfig = import('@aihu-plugin/agent-readiness').AgentReadinessConfigType-only import — not bundled when agentReadiness is absent.
AihuPlugintype AihuPlugin = PluginA Aihu plugin is structurally identical to a Vite plugin (V0).
AppRenderingModetype AppRenderingMode = 'ssr' | 'spa' | 'hybrid'Rendering mode passed from the server config into the client bootstrap.
OutputModetype OutputMode = 'spa' | 'static'Build output mode.
VitePassthroughtype VitePassthrough = Omit<UserConfig, 'plugins'>Vite config fields that can be safely merged (excludes plugins — use AihuConfig.plugins).