import type { ApiExtension, ApiModule } from "./module.js"; /** * Manifest-driven runtime composition. * * The `voyant.config.ts` manifest already drives the migration/schema side * (see `@voyant-travel/cli` `db doctor` and `docs/architecture/migration-resilience-rfc.md`). * This module lets the SAME manifest drive runtime composition: instead of a * template hand-listing `createApp({ modules, extensions })`, it registers a * factory per manifest entry and derives the arrays from the manifest. * * The factories receive a typed **capability container** — the template's * deployment-specific capabilities (storage, FX, notification providers, * document-download resolvers, …) gathered in one place. Because Voyant runs * on Cloudflare Workers (per-request `bindings`), capabilities are typically * bindings-deferred closures (`(bindings) => T`), so the container is a plain * typed value resolved per request rather than a boot-time singleton. */ /** A manifest entry: a bare specifier or `{ resolve, options }`. */ export type CompositionEntry = string | { resolve: string; options?: Record; }; /** The subset of `VoyantConfig` this composer reads. */ export interface CompositionManifest { modules?: CompositionEntry[]; extensions?: CompositionEntry[]; } /** Context handed to every factory: the capability container + per-entry options. */ export interface CompositionContext { capabilities: TCapabilities; options: Record; } export type ModuleFactory = (ctx: CompositionContext) => ApiModule | ApiModule[]; export type ExtensionFactory = (ctx: CompositionContext) => ApiExtension; /** * Maps manifest specifiers to the factory that builds the runtime unit. Keys * MUST match the `voyant.config.ts` `modules` / `extensions` specifiers. */ export interface CompositionRegistry { modules: Record>; extensions?: Record>; } export interface ComposedApp { modules: ApiModule[]; extensions: ApiExtension[]; } /** * Derive the `createApp({ modules, extensions })` arrays from a manifest by * looking each entry up in the registry, **preserving manifest order** (mount * + hook-registration order is significant). Throws if the manifest lists an * entry the registry has no factory for — so "added to the manifest but not * wired" fails loudly at boot rather than silently dropping a module. */ export declare function composeFromManifest(manifest: CompositionManifest, registry: CompositionRegistry, capabilities: TCapabilities): ComposedApp; export interface ManifestRegistryDiff { /** In the manifest but with no registered factory. */ missingFactories: string[]; /** Registered factories not referenced by the manifest. */ orphanFactories: string[]; } /** * Pure parity check between a manifest's entries and a registry's keys, for * tooling (`voyant db doctor`). Reports manifest entries with no factory and * factories the manifest never references. */ export declare function diffManifestRegistry(manifestEntries: CompositionEntry[], registryKeys: string[]): ManifestRegistryDiff;