/** * definePage —— 页面声明层 * * 理念(设计文档 §3.1): * - 页面只管业务:setup 只做数据/状态,结构全部来自注册表 * - 布局/组件用 id 引用,不写任何布局 JSX * - 生成产物受控:引用的 id 必须是已注册项(validate 把关) */ import { type RegistryApi, registry } from './registry.ts' /** 页面中一个块的声明:引用已注册 widget,并给 props/绑定 */ export interface PageBlockDecl { /** widget 注册 id,如 'data-table'、'page-header' */ readonly widget: string /** 落在布局的哪个槽位(缺省 'content';渲染内核按布局 slots 装配) */ readonly slot?: string /** 静态 props(可选) */ readonly props?: Record /** 业务绑定:setup 返回的数据里取哪些字段喂给 widget(键:widget prop 名,值:setup 结果字段) */ readonly bind?: Record } /** 页面声明:布局 id + 块列表 + 业务 */ export interface PageDeclaration { /** 布局注册 id,如 'admin' —— 用户只需说"用 admin 布局" */ readonly layout: string readonly blocks: readonly PageBlockDecl[] /** 业务层:返回的数据可供 blocks.bind 使用;只做业务,不碰 UI 结构 */ readonly setup?: (ctx: PageContext) => Promise> | Record /** 行为回调:业务动作(bivariant,允许实现方标注收窄的 payload 类型) */ readonly actions?: Record /** 页标题等元信息(可选) */ readonly title?: string } export interface PageContext { readonly query: Record readonly params: Record } /** 把声明物化为"已校验的页面对象";未知引用直接抛错(校验闸门) */ export function definePage(decl: PageDeclaration, reg: RegistryApi = registry): PageDeclaration { const refs = [ { kind: 'layout' as const, id: decl.layout }, ...decl.blocks.map((b) => ({ kind: 'widget' as const, id: b.widget })), ] const missing = reg.validateRefs(refs) if (missing.length > 0) { throw new Error( `[vibe-registry] definePage 引用了未注册项:${missing.join(', ')} ` + `(万物皆注册:请先注册,或用已有注册项)`, ) } return decl } /** 业务 setup 辅助类型:让页面文件只声明"取哪些数据" */ export type SetupData

= Awaited>>