API / @aihu/app

@aihu/app

App & routing

Top-level app integration — wires runtime, router, and adapters into a Vite app.

version
8.1.0
exports
33
values
11
types
22
01

collectAihuModules

function
function collectAihuModules(plugins: ReadonlyArray<unknown>): Map<string, unknown>

Collect every aihu module's resolved options from a plugin array.

02

createApp

function
function createApp(config?: AppConfig): AppHandle
03

criticalPath

function
function criticalPath(opts: CriticalPathOptions = {}): Plugin
04

declareAihuModule

function
function declareAihuModule<TOptions, TPlugins extends readonly unknown[]>( aihuModule: string, options: TOptions, plugins: TPlugins, ): TPlugins

Attach the module contract to a plugin (or plugin array).

05

defineConfig

function
function defineConfig(config: AihuConfig): AihuConfig

Define the aihu application configuration.

06

loadAihuConfig

function
async 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.

07

validateAihuConfig

function
function validateAihuConfig(config: AihuConfig): void

Validate a config object without the `defineConfig` ceremony.

08

viteAihuPlugin

function
function viteAihuPlugin(config?: AihuConfig): PluginOption[]

viteAihuPlugin() — composed Vite plugin for aihu SPA projects.

09

AIHU_CONFIG_KEYS

const
const AIHU_CONFIG_KEYS: ReadonlyArray<string>

Keys aihu owns, derived from the schema rather than hand-listed.

10

AIHU_CONFIG_PLUGIN

const
const AIHU_CONFIG_PLUGIN

Plugin name carrying the config handle.

11

AihuConfigError

class
class AihuConfigError extends Error

Thrown by `defineConfig` when configuration validation fails.

12

AdapterContext

interface
interface 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.

13

AihuAdapter

interface
interface 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.

14

AihuConfig

interface
interface 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
}
15

AihuModuleApi

interface
interface 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.

16

AihuPluginApi

interface
interface AihuPluginApi {
  /** The config object the user passed to `viteAihuPlugin()`. */
  getAihuConfig(): AihuConfig
}

The public API handle attached to aihu's marker plugin.

17

AppConfig

interface
interface 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().

18

AppHandle

interface
interface 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>
}
19

AppHeadConfig

interface
interface AppHeadConfig {
  readonly head?: HeadConfig
}
20

CreateHandlerSourceOptions

interface
interface 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().

21

CriticalPathOptions

interface
interface 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
}
22

CriticalPathRule

interface
interface 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.

23

DirConfig

interface
interface 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
}
24

HeadConfig

interface
interface 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>>
}
25

LoadedAihuConfig

interface
interface 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>
}
26

RouterConfig

interface
interface 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).

27

RuntimeConfig

interface
interface 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.

28

SiteConfig

interface
interface 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.

29

AgentReadinessConfig

typeagent
type AgentReadinessConfig = import('@aihu-plugin/agent-readiness').AgentReadinessConfig

Type-only import — not bundled when agentReadiness is absent.

30

AihuPlugin

type
type AihuPlugin = Plugin

A Aihu plugin is structurally identical to a Vite plugin (V0).

31

AppRenderingMode

type
type AppRenderingMode = 'ssr' | 'spa' | 'hybrid'

Rendering mode passed from the server config into the client bootstrap.

32

OutputMode

type
type OutputMode = 'spa' | 'static'

Build output mode.

33

VitePassthrough

type
type VitePassthrough = Omit<UserConfig, 'plugins'>

Vite config fields that can be safely merged (excludes plugins — use AihuConfig.plugins).