UNPKG

@scenemesh/entity-engine

Version:

一个“元数据驱动 + 组件适配 + 动态关系 + 视图管线”式的实体引擎。以 **Model + View + FieldType + SuiteAdapter + DataSource** 为五大支点,统一 CRUD / 查询 / 引用管理 / 视图渲染 / 扩展注册,支持在运行期无侵入拼装出 **表单、网格、主从、看板、仪表盘、流程/树形视图** 等多形态界面。

1,003 lines (766 loc) 34.8 kB
# @scenemesh/entity-engine 一个“元数据驱动 + 组件适配 + 动态关系 + 视图管线”式的实体引擎。以 **Model + View + FieldType + SuiteAdapter + DataSource** 为五大支点,统一 CRUD / 查询 / 引用管理 / 视图渲染 / 扩展注册,支持在运行期无侵入拼装出 **表单、网格、主从、看板、仪表盘、流程/树形视图** 等多形态界面。 **设计原则:配置驱动、运行补全、插槽扩展、最小绑定、可模块化注入。** 适合构建中后台、数据工作台、领域建模平台、低代码/AI 辅助生成系统。 ## 目录一览 1. 特性速览 2. workbench 集成全景示例(真实用法) 3. 安装与 Peer 依赖 4. 快速上手(客户端 / 服务端 / 混合渲染) 5. 核心概念与架构示意 6. 模型 (Model) 与视图 (View) 元数据 7. 视图渲染与插槽扩展 (Renderers & Slots) 8. 组件套件 (Suite Adapter) 与自定义 Widget 9. 数据源与引用关系(References / Trees / Grouping) 10. 查询 DSL(条件表达表达式结构) 11. 服务端接入(Next.js Route Handler / tRPC 端点) 12. 模块化扩展(动态加载 & AI 模块示例) 13. 权限 / 导航 / 会话集成 14. 多对多引用编辑组件使用(ReferenceEditMMComp) 15. 调试与开发辅助(View Inspector / Studio Launcher) 16. Roadmap 17. FAQ 18. License ## 1. 特性速览 | 能力 | 说明 | | ---- | ---- | | 元数据驱动 | 使用 `IEntityModel` + `IEntityView` 描述领域与 UI 形态,减少硬编码。 | | 运行时补全 | 视图缺省字段、Widget、顺序等由 FieldTyper 与模型自动推导。 | | 插槽扩展 | 通过命名渲染器 (Named Renderer) 在壳层 / 工具栏 / 行内插入自定义区域。 | | 多视图内置 | `form` / `grid` / `master-detail` / `shell` / `kanban` / `dashboard`。 | | 引用关系统一 | 基于引用表抽象支持一对多、多对多、树、反向查询、计数。 | | 多对多编辑 | 内置 `ReferenceEditMMComp` 管理选择/删除/批量操作。 | | 查询 DSL | 嵌套 AND/OR/NOT、Between/In/Contains 等操作符统一结构表达。 | | 数据源抽象 | `IEntityDataSource` 屏蔽实现,默认 tRPC + Prisma(可扩展 REST / GraphQL)。 | | 模块化 | 运行期动态加载模块(esm.sh / 本地),扩展模型、组件、动作、AI 工具。 | | 组件套件 | UI 套件适配器(Mantine / 自定义 / additions suite),解耦 UI 风格。 | | 行为注册 | Action / Event / Servlet 三类行为管线统一注册与调用。 | | DevTools | View Inspector / Studio Launcher 提供运行态调试与可视化。 | | 类型安全 | TypeScript + zod(字段 schema 可选)保障运行与编译期安全。 | > 目标:用最少的“约束 + 元信息”表达,驱动出尽可能多的动态 UI 与行为。 - [新增视图实现](#新增视图实现) - [自定义 Widget](#自定义-widget) - [命名 Renderer (Slot)](#命名-renderer-slot) - [字段类型扩展 (Field Type Extension)](#字段类型扩展-field-type-extension) - [数据源扩展 (DataSource Extension)](#数据源扩展-datasource-extension) - [校验与默认值 (Validation \& Defaults)](#校验与默认值-validation--defaults) - [tRPC 集成 (tRPC Integration)](#trpc-集成-trpc-integration) - [常用接口参考 (API Reference Snapshot)](#常用接口参考-api-reference-snapshot) - [IEntityModel](#ientitymodel) - [IEntityField](#ientityfield) - [IEntityView](#ientityview) - [IEntityDataSource (节选)](#ientitydatasource-节选) - [Roadmap](#roadmap) - [贡献指南 (Contributing)](#贡献指南-contributing) - [许可 (License)](#许可-license) - [常见问题 (FAQ)](#常见问题-faq) - [致谢 (Acknowledgements)](#致谢-acknowledgements) --- ## 2. 与 workbench 集成全景示例 `apps/workbench` 展示了一个真实集成: **客户端 Provider 封装示例**(简化自 `entity-engine-provider-warpper.tsx`): ```tsx // src/entity/provider/entity-engine-provider-warpper.tsx 'use client'; import { useRouter } from 'next/navigation'; import { createEntityEngineProvider, useEntityEngine, EntityViewInspector } from '@scenemesh/entity-engine'; import { AdditionsSuiteAdapter } from '@scenemesh/entity-suite-additions'; import { EntityAIModule } from '@scenemesh/entity-engine-aimodule'; import { EntityEngineStudioLauncher } from '@scenemesh/entity-engine-studio'; export function EntityEngineProviderWrapper({ children }: { children: React.ReactNode }) { const router = useRouter(); const EntityEngineProvider = createEntityEngineProvider({ // config: { models, views }, // 可按需注入模型与视图 suiteAdapters: [new AdditionsSuiteAdapter()], suiteAdapter: { suiteName: 'additions', suiteVersion: '1.0.0' }, router: { navigate: (path, state) => router.push(path) }, permissionGuard: { checkPermission: async () => true }, renderers: [ { ...EntityViewInspector }, { ...EntityEngineStudioLauncher }, { name: 'view-tool-2', slotName: 'view-tool', renderer: (ctx) => <button>工具扩展</button> }, ], settings: { baseUrl: process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8082', endpoint: process.env.NEXT_PUBLIC_API_ENDPOINT || '/api/ee', authenticationEnabled: true, }, modules: [new EntityAIModule()], }); return <EntityEngineProvider>{children}</EntityEngineProvider>; } ``` **服务端路由集成(Next.js App Router)**: ```ts // app/api/ee/[[...slug]]/route.ts import { EnginePrimitiveInitializer, fetchEntityEntranceHandler } from '@scenemesh/entity-engine/server'; import { EntityAIModule } from '@scenemesh/entity-engine-aimodule'; import { models, views } from 'src/entity/model-config'; const init = new EnginePrimitiveInitializer({ models, views, modules: [new EntityAIModule()] }); const handler = (req: Request) => fetchEntityEntranceHandler({ request: req, endpoint: '/api/ee', initializer: init }); export { handler as GET, handler as POST }; ``` **在界面中使用容器组件(动态视图渲染)**: ```tsx import { EntityViewContainer } from '@scenemesh/entity-engine'; export function ProductGrid() { return ( <EntityViewContainer modelName="product" viewType="grid" viewName="productGridView" maintain={{ pageSize: 20 }} /> ); } ``` **树 + 引用 + 流程可视化结合(节选自场景设计器)**: ```ts const ds = engine.datasourceFactory.getDataSource(); // 基于引用关系查找根节点 await ds.findMany({ modelName: 'scene', query: { pageIndex: 1, pageSize: 1, references: { fromModelName: 'product', fromFieldName: 'rootScene', fromObjectId: currentProductId, toModelName: 'scene', }, }, }); // 树结构展开 await ds.findTreeObjects({ modelName: 'scene', fieldName: 'children', rootObjectId }); ``` **AI 模块集成**:`EntityAIModule` 注入后可在渲染器/工具链中暴露 AI 辅助功能(例如智能填表、生成字段描述、查询建议等)。 > 更多完整示例可参考:`apps/workbench/src/entity` `apps/workbench/src/viewports/scene`。 --- ## 3. 安装与 Peer 依赖 安装 ```bash # npm npm i @scenemesh/entity-engine # yarn yarn add @scenemesh/entity-engine # pnpm pnpm add @scenemesh/entity-engine ``` 必需 peer 依赖 - react >=18 <20 - react-dom >=18 <20 - react-hook-form >=7 <8 - Mantine(供内置视图与检查器使用) - @mantine/core 8.2.5 - @mantine/hooks 8.2.5 - @mantine/modals 8.2.5 - @mantine/notifications 8.2.5 - mantine-datatable 8.2.0 可选 peer(仅使用 server 能力时) - @prisma/client(与 prisma CLI) 样式引入(必需) ```ts import '@scenemesh/entity-engine/main.css' ``` ## 4. 快速上手 > 假设你在一个包含本包的 monorepo(已安装依赖)中开发。 Next.js(App Router)最小用法: ```tsx // app/layout.tsx import '@scenemesh/entity-engine/main.css' import { EntityEngineProvider } from 'src/entity-provider' // 参考下文示例或自行封装 Provider export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="zh-CN"> <body> <EntityEngineProvider>{children}</EntityEngineProvider> </body> </html> ) } ``` Vite(或 CRA)最小用法: ```tsx // src/main.tsx import React from 'react' import ReactDOM from 'react-dom/client' import '@scenemesh/entity-engine/main.css' import { EngineInitializer, getEntityEngine } from '@scenemesh/entity-engine' async function bootstrap() { const initializer = new EngineInitializer([], []) await getEntityEngine(initializer) ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <div>Entity Engine Ready</div> </React.StrictMode> ) } bootstrap() ``` 提示:本包将 React/ReactDOM、部分 UI 库设为 peer 依赖;构建产物 external 常见 UI 库,避免与你的应用重复打包或版本冲突。 ### 构建 / 开发 ```bash # 构建产物 (dist) yarn build # 持续编译 (tsc --watch) yarn dev # 数据库(生成 Prisma Client / 迁移) yarn db:generate # prisma migrate dev yarn db:migrate # prisma migrate deploy yarn db:push # prisma db push yarn db:studio # 启动数据浏览 ``` ### 初始化引擎(最小) ```ts import { EngineInitializer, getEntityEngine } from '@scenemesh/entity-engine'; // 1. 定义模型(最小示例) const UserModel = { name: 'user', title: '用户', fields: [ { name: 'id', title: 'ID', type: 'string', isPrimaryKey: true }, { name: 'name', title: '姓名', type: 'string', searchable: true, isRequired: true }, { name: 'age', title: '年龄', type: 'number', searchable: true }, { name: 'role', title: '角色', type: 'enum', typeOptions: { options: [ { label: '管理员', value: 'admin' }, { label: '访客', value: 'guest' } ] }, searchable: true }, ], } as const; // 2. 定义视图(若不定义,也可由元数据自动补全创建默认视图) const UserGridView = { name: 'user-grid', title: '用户列表', modelName: 'user', viewType: 'grid', items: [ { name: 'id', width: 120 }, { name: 'name', flex: 1 }, { name: 'age', width: 80 }, { name: 'role', width: 120 }, ], }; // 3. 初始化(一次性调用) async function bootstrap() { const initializer = new EngineInitializer([UserModel], [UserGridView]); const engine = await getEntityEngine(initializer); console.log(engine.toString()); } bootstrap(); ``` ### 在 React 中使用某个视图(手动装配模式) ```tsx import React from 'react'; import { getEntityEngine } from '@scenemesh/entity-engine'; export function UserGridContainer() { const [engine, setEngine] = React.useState<any>(); React.useEffect(() => { getEntityEngine().then(setEngine); // 已初始化后无需再次传入 initializer }, []); if (!engine) return <div>Loading...</div>; const view = engine.metaRegistry.findView('user', 'grid'); if (!view) return <div>View not found</div>; const supplemented = view.toSupplementedView(); const gridView = engine.componentRegistry.getView('grid'); if (!gridView) return <div>Grid view component missing</div>; const Comp = gridView.Component; return <Comp model={engine.metaRegistry.getModel('user')!} viewData={supplemented} behavior={{ mode: 'display' }} />; } ``` --- ## 5. 核心概念与架构 | 概念 | 接口 / | 作用 | | ---- | --------- | ---- | | 实体字段 | `IEntityField` | 描述字段元信息(类型、校验、引用、搜索能力等)。 | | 实体模型 | `IEntityModel` / `EntityModelDelegate` | 字段集合 + 行为补全(默认值 / Schema / QueryMeta)。 | | 实体视图 | `IEntityView` / `EntityViewDelegate` | 描述一个模型的某种可视化(grid / form / master-detail / shell)。 | | 视图字段项 | `IEntityViewField` | 与模型字段映射,可定义 widget、顺序、显示条件等。 | | 引擎 | `EntityEngine` | 单例;聚合 metaRegistry / fieldTyperRegistry / dataSourceFactory / componentRegistry。 | | 元数据注册表 | `EntityMetaRegistry` | 管理模型、视图、菜单;生成缺省视图。 | | 组件注册表 | `EntityComponentRegistry` | 管理视图实现、Widget 套件、命名 Renderer。 | | 组件套件适配器 | `IEntityComponentSuiteAdapter` | 一组 Widget 的命名空间(built-in 套件)。 | | Widget | `EntityWidget` | 针对单字段或容器/引用呈现的可复用 UI 单元。 | | 渲染器 | `IEntityNamedRenderer` | Slot 渲染扩展点(装饰 / 布局 / 行内渲染)。 | | 字段类型 Typer | `IModelFieldTyper` | 决定默认值 / 默认 Widget / 查询能力。 | | 数据源 | `IEntityDataSource` | CRUD + 引用 + 树形查询抽象。默认实现:tRPCEntityObjectDataSource。 | | 初始化器 | `EngineInitializer` | 首次创建引擎时批量注册模型、视图、套件、渲染器。 | --- ### 架构概览 ```text +---------------------------+ | EntityEngine | (Singleton) +---------------------------+ | | | | v v v v +---------+ +-----------+ +--------+ +----------------+ | Meta | | FieldType | | Data | | Component | | Registry| | Registry | |Source | | Registry | +----+----+ +-----+-----+ +---+----+ +-------+--------+ | | | | Models <->| | | |<-> Views Impl Views <->| | | |<-> Widget Suites | | | |<-> Renderers | | | | v v v v Delegates FieldTypers TRPC DS React Components ``` 关键流: 1. 初始化阶段:`EngineInitializer` 注册模型 / 视图 / 套件 / 渲染器。 2. 运行时请求某视图:从 `metaRegistry` 获取 `IEntityViewDelegate`,补全 => 视图组件执行数据加载(通过 `datasourceFactory.getDataSource()`)。 3. 用户操作(增删改) -> DataSource (tRPC) -> Server (Prisma) -> 结果回显。 --- ## 6. 目录结构概览 简化摘录: ``` src/ core/ engine/ # 引擎与初始化 datasources/ # 数据源工厂 & tRPC 实现 delegate/ # Delegate 封装 (Model/View/Menu) fieldtypes/ # 字段类型注册表 theme/ # 主题 (占位 / 可扩展) types/ # 核心类型接口 (engine / delegate / datasource ...) components/ views/ # 视图实现 (form / grid / mastail / shell) types/ # 视图 & widget 类型定义 share/ # 公共共享组件 (search-panel 等) build-ins/ suite/ # 内置组件套件 & widgets services/api/trpc/ # tRPC 客户端工具 lib/data/ # 数据转换工具 (convertRawEntityObject 等) types/ # 元数据 / 数据 / 字段 / 样式 类型 uikit/ # UI 基础 (provider / surface 等) ``` --- ## 7. 模型与视图元数据 模型(`IEntityModel`)定义领域结构,视图(`IEntityView`)定义展现形态。未定义的视图可按模型自动补全生成。大型项目建议将模型拆分多个文件并汇总导出。 ### 示例:片段(来自 workbench `model-config.ts`) ```ts export const models: IEntityModel[] = [ { name: 'product', title: '产品', fields: [ { name: 'name', title: '名称', type: 'string', isRequired: true, searchable: true }, { name: 'price', title: '价格', type: 'number' }, { name: 'rootScene', title: '根场景', type: 'one_to_one', refModel: 'scene' }, ], }, { name: 'scene', title: '场景', fields: [ { name: 'title', title: '标题', type: 'string', isRequired: true }, { name: 'children', title: '子场景', type: 'one_to_many', refModel: 'scene' }, ], }, ]; ``` ### 视图定义示例 ```ts export const views: IEntityView[] = [ { name: 'productGridView', title: '产品列表', modelName: 'product', viewType: 'grid', items: [ { name: 'name' }, { name: 'price' }, { name: 'rootScene' } ], }, { name: 'sceneFormView', title: '场景表单', modelName: 'scene', viewType: 'form', items: [ { name: 'title' }, { name: 'children', widget: 'reference-many' } ], }, ]; ``` ## 8. 视图渲染与插槽扩展 通过注册命名渲染器(`IEntityNamedRenderer`)向视图生命周期插入 UI: | slotName 示例 | 典型用途 | | -------------- | -------- | | `view-tool` | 表格/表单顶部工具条扩展 | | `shell-settings-item2` | 系统壳层设置菜单条目 | | `record-inline` | 行内扩展按钮/标签 | 注册示例: ```ts renderers: [ { name: 'view-tool-2', slotName: 'view-tool', renderer: (ctx) => <span>工具扩展 {ctx.model.name}</span> }, { ...EntityViewInspector }, // 内置调试器 ] ``` > 上层应用可通过 DSL + 权限系统动态控制哪些渲染器激活。 ## 9. 组件套件 (Suite Adapter) 与自定义 Widget 组件套件用于将“抽象字段语义”映射为“具体 UI 实现”,不同 `suiteAdapter` 可复用同一组模型与视图元数据。 实现要点: 1. 实现接口 `IEntityComponentSuiteAdapter` 2. 提供 `suiteName`、版本与 `getWidget(name)` 3. Provider 中通过 `suiteAdapters` 注册,并指定当前使用的 `suiteAdapter` > 你可以同时注册多套适配器(例如 Mantine / Additions),再让用户在运行时切换主题或 UI 表现。 ## 10. 数据源与引用 / 树 / 分组 数据源接口:`IEntityDataSource`,默认实现:`TRPCEntityObjectDataSource`。 常用方法: | 方法 | 作用 | | ---- | ---- | | `findMany` | 分页查询实体对象 | | `create / update / delete` | 基础 CRUD | | `findReferences / findReferencesCount` | 引用关系列表与计数 | | `createReference / deleteReference / deleteReferences` | 多对多关系增删 | | `findTreeObjects` | 递归树结构(支持 children 字段) | | `findGroupedObjects` | 根据字段或时间/范围聚合分组 | 多对多引用 UI 可直接使用内置组件: ```tsx import { ReferenceEditMMComp } from '@scenemesh/entity-engine'; // 在一个自定义表单 Widget 内: <ReferenceEditMMComp fieldType={{ /* 来自 view 字段解析 */ }} object={currentObject} value={selectedIds} onChange={(ids) => setSelectedIds(ids)} /> ``` ## 11. 查询 DSL 查询结构由 `IEntityQuery` + `IEntityQueryItem` 组成: ```ts const query = { pageIndex: 1, pageSize: 20, filter: { and: [ { field: 'name', operator: 'contains', value: 'x' }, { or: [ { field: 'status', operator: 'eq', value: 'active' }, { field: 'status', operator: 'eq', value: 'draft' } ] }, ], }, }; ``` > 未来规划:提供“可视化 Query Builder + 表达式编译器”生成最终 Prisma where 条件。 ## 12. 服务端接入(Next.js) 沿用 tRPC 风格: 1. 创建 `EnginePrimitiveInitializer` 注入模型与视图 2. Route Handler 中调用 `fetchEntityEntranceHandler` 3. 客户端 Provider 使用相同 endpoint 已支持的后端能力:对象 CRUD / 引用 / / 分组 / 视图+模型增量下发。 ## 13. 模块化扩展 & AI 集成 模块(`IEntityModule`)可: - 注册额外模型 / 视图 - 注册渲染器 / 动作 / Servlet - 注入 AI 功能(见 `EntityAIModule`) 动态加载:客户端可通过 `esm.sh/<package>/dist/index.js` 按需拉取(需保证导出 default)。 ## 14. 权限 / 导航 / 会话 Provider 选项: | 选项 | 作用 | | ---- | ---- | | `router.navigate(path,state)` | 桥接到应用路由(Next.js push / React Router) | | `permissionGuard.checkPermission(action)` | 统一权限校验入口 | | `settings` | baseUrl / endpoint / authenticationEnabled | | `modules` | 注入模块数组 | > 你可在 `checkPermission` 里访问全局用户上下文,实现模型/字段/操作级别控制。 ## 15. 调试与开发工具 | 工具 | 描述 | | ---- | ---- | | `EntityViewInspector` | 在运行时查看当前视图的补全后结构(模型 / 字段 / widget 解析结果)。 | | `EntityEngineStudioLauncher` | 打开交互式运行时面板,未来扩展模型编辑 / 视图热更新。 | | 日志 | `TRPCEntityObjectDataSource` URL 变更时输出新客户端创建日志。 | ## 16. Roadmap - [ ] 查询 DSL -> 编译器 + 单元测试 - [ ] 模型 / 视图 版本化与快照导出 - [ ] 引用关系附加属性(排序 / 标签 / 权重) - [ ] 渲染器 DevTools 面板 - [ ] 模块远程签名校验与缓存策略 - [ ] 多数据源聚合(federation) - [ ] 表达式求值沙箱化 (CEL / jsep) - [ ] 视图性能探针 & 监控面板 - [ ] AI 辅助:模型结构生成 / 视图建议 / 查询自然语言解析 ## 17. FAQ **Q: 可以只用数据源而不使用内置视图吗?** A: 可以。直接 `getEntityEngine().datasourceFactory.getDataSource()` 调用 CRUD。 **Q: 如何扩展一个新视图类型?** A: 实现 `EntityView` 接口(或继承内置基类),在 `componentRegistry.registerView()` 注册。 **Q: 可以在多引擎实例间隔离吗?** A: 当前主设计是单例。后续将加入多实例 + context 方案。 **Q: 引用是否支持附加元数据?** A: 当前结构最简。可在后端扩展引用表(例如增加 jsonb 列)并在数据源扩展。 **Q: SSR 支持吗?** A: 是。服务端使用 `EnginePrimitiveInitializer`,客户端使用 `createEntityEngineProvider`。注意避免重复初始化。 ## 18. License MIT © scenemesh --- > 如果你在集成上遇到困难,或希望我进一步为你的场景定制示例/脚手架,可直接在仓库发起 Issue 或讨论。 ### 1. 注册额外视图与组件套件 ```ts import { EngineInitializer, getEntityEngine } from '@scenemesh/entity-engine'; class MySuiteAdapter { /* implements IEntityComponentSuiteAdapter */ } class MyRenderer { name = 'toolbar-extra'; slotName = 'toolbar'; renderer = () => <div>Extra</div>; } const initializer = new EngineInitializer([UserModel], [UserGridView], [new MySuiteAdapter()], [new MyRenderer()]); await getEntityEngine(initializer); ``` ### 2. 访问数据源 CRUD ```ts const engine = await getEntityEngine(); const ds = engine.datasourceFactory.getDataSource(); await ds.create({ modelName: 'user', data: { values: { name: 'Lucy', age: 20, role: 'guest' } } }); const { data, count } = await ds.findMany({ modelName: 'user', query: { pageIndex: 1, pageSize: 10 } }); ``` ### 3. 动态生成默认视图 如果未显式注册某模型的某种 viewType(例如 `grid`),调用: ```ts const v = engine.metaRegistry.findView('user', 'grid'); // 若无则按模型字段生成 ``` ### 4. 条件显示 / 只读 / 必填逻辑 `IEntityViewField` 支持: | 属性 | 含义 | 示例 | | ---- | ---- | ---- | | hiddenWhen | 条件表达式为 true 时隐藏 | `role=="guest"` | | showWhen | 条件表达式为 true 时显示 | `age>18` | | readOnlyWhen | 条件成立时只读 | `role=="admin"` | | disabledWhen | 条件成立时禁用 | `age<1` | | requiredWhen | 条件成立时必填 | `role=="admin"` | (表达式解析器按需在上层应用实现,可替换为任意逻辑解析方案) ### 5. 在应用中通过 Provider 集成(Provider Integration) `apps/workbench/src/entity/provider` 目录展示了如何在 Next.js (App Router) 环境中一次性封装引擎: 核心点: 1. 使用 `createEntityEngineProvider` 生成 React 上下文 Provider(内部完成 `EntityEngine` 初始化与依赖注入)。 2. 注入多套组件套件:示例中同时使用 `SemiSuiteAdapter` `MUISuiteAdapter`,并指定一个当前使用的套件 `suiteAdapter`(可作为默认/首选套件描述)。 3. 包裹在 `TRPCReactProvider` 外层(或内层)以提供数据访问上下文。 4. 自定义 `router.navigate` 以适配 Next.js `useRouter().push()`。 5. 提供 `permissionGuard.checkPermission` 异步函数集中处理权限校验(此处示例直接放行)。 6. 注册自定义 `renderers`(命名插槽扩展)以及内置 `EntityViewInspector` 以调试视图元数据。 示例代码(建议放在 `src/entity-provider.tsx`): ```tsx 'use client'; import { useRouter } from 'next/navigation'; import { createEntityEngineProvider, EntityViewInspector } from '@scenemesh/entity-engine'; import { MUISuiteAdapter } from '@scenemesh/entity-suite-mui'; import { SemiSuiteAdapter } from '@scenemesh/entity-suite-semi'; import { TRPCReactProvider } from '@scenemesh/entity-engine/services/api/trpc/react'; import { models, views } from 'src/entity/model-config'; // 你的模型与视图配置 import React from 'react' import { createEntityEngineProvider, EntityViewInspector, type IEntityNamedRenderer } from '@scenemesh/entity-engine' // 你的模型与视图(最小示例) const models = [ { name: 'user', title: '用户', fields: [ { name: 'id', title: 'ID', type: 'string', isPrimaryKey: true }, { name: 'name', title: '姓名', type: 'string' } ] } ] as any const views = [ { name: 'user-grid', title: '用户列表', modelName: 'user', viewType: 'grid', items: [ { name: 'id' }, { name: 'name' } ] } ] as any // 自定义插槽渲染器(可选) const ViewToolbar: IEntityNamedRenderer = { name: 'view-tool-2', slotName: 'view-tool', renderer: (ctx) => <div>工具区 {ctx.model.name}</div> } export function EntityEngineProvider({ children }: { children: React.ReactNode }) { const router = useRouter() // 使用 memo 避免每次渲染都重新创建 Provider 组件 const Provider = React.useMemo(() => createEntityEngineProvider({ models, views, suiteAdapter: { suiteName: 'build-in', suiteVersion: '1.0.0' }, router: { navigate: (path, state) => router.push(path) }, permissionGuard: { checkPermission: async () => true }, renderers: [ ViewToolbar, { ...EntityViewInspector } ], serverInfo: { baseUrl: '', endpoint: '/api/ee' }, }), [router]) return <Provider>{children}</Provider> } ``` 在页面中直接使用: ```tsx // app/layout.tsx 或某上层布局 import { EntityEngineProvider } from 'src/entity-provider'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="zh-CN"> <body> <EntityEngineProvider> {children} </EntityEngineProvider> </body> </html> ); } ``` 这样任意子组件即可通过(假设库已导出相应 hooks,如未来 `useEntityEngine()`)获取引擎实例与上下文数据,实现: - 视图渲染(Grid/Form/MasterDetail) - 数据 CRUD / 引用查询 - 自定义插槽渲染扩展(toolbar / shell settings) - 权限与路由统一入口 > 若需按租户/用户隔离实例,可在 `createEntityEngineProvider` 外再包一层,根据参数动态构建不同 initializer。 --- ## 查询与过滤 (Query & Filter) 查询结构:`IEntityQuery`。 ```ts type IEntityQuery = { pageSize?: number; pageIndex?: number; sortBy?: Record<string, 'asc' | 'desc'>; references?: { fromModelName; fromFieldName; fromObjectId; toModelName }; filter?: IEntityQueryItem; // 递归 AND / OR / NOT 结构 } ``` 叶子条件: ```ts { field: 'age', operator: QueryOperator.GT, value: 18 } ``` 复合条件: ```ts { and: [ { field: 'role', operator: QueryOperator.EQ, value: 'admin' }, { or: [ { field: 'age', operator: QueryOperator.GT, value: 30 }, { field: 'age', operator: QueryOperator.IS_NULL, value: null } ] } ] } ``` 支持操作符(QueryOperator):`EQ, NE, GT, GTE, LT, LTE, CONTAINS, STARTS_WITH, ENDS_WITH, IN, NOT_IN, BETWEEN, IS_NULL, IS_NOT_NULL` 等。 引擎可通过 `modelDelegate.getQueryMeta()` UI 构建器提供字段支持的操作符列表及枚举选项。 --- ## 组件与视图扩展 (Views & Widgets Extensibility) ### 新增视图实现 ```ts import { EntityView } from '@scenemesh/entity-engine'; export class TimelineView extends EntityView { readonly info = { viewName: 'timeline', displayName: '时间线视图' }; readonly Component = (props) => <div>TODO Timeline of {props.model.name}</div>; } // 注册 engine.componentRegistry.registerView(new TimelineView()); ``` ### 自定义 Widget ```ts import { EntityWidget } from '@scenemesh/entity-engine'; class BadgeWidget extends EntityWidget { readonly info = { widgetName: 'badge', displayName: '徽章' }; readonly Component = ({ value }) => <span className="badge">{value}</span>; } class BadgeSuiteAdapter { // implements IEntityComponentSuiteAdapter suiteName = 'badge-suite'; suiteVersion = '0.0.1'; private widgets = new Map([['badge', new BadgeWidget()]]); getWidget(n){ return this.widgets.get(n); } getWidgets(){ return [...this.widgets.values()]; } } engine.componentRegistry.registerAdapter(new BadgeSuiteAdapter()); ``` ### 命名 Renderer (Slot) ```ts engine.componentRegistry.registerRenderer({ name: 'grid-toolbar-extra', slotName: 'grid.toolbar.right', renderer: () => <button>导出 CSV</button> }); ``` --- ## 字段类型扩展 (Field Type Extension) 实现 `IModelFieldTyper`: ```ts import { z } from 'zod'; class GeoPointFieldTyper { // implements IModelFieldTyper get title(){ return '地理点'; } get type(){ return 'geopoint'; } get icon(){ return 'map_pin'; } get description(){ return '经纬度'; } getDefaultValue(){ return { lat: 0, lng: 0 }; } getDefaultSchema(){ return z.object({ lat: z.number(), lng: z.number() }); } getQueryItemMeta(field){ return { field, operators: [/* ... */] }; } getDefaultWidgetType(viewType:string){ return 'map'; } } engine.fieldTyperRegistry.registerFieldTyper(new GeoPointFieldTyper()); ``` 随后在模型字段中使用 `type: 'geopoint'`。 --- ## 数据源扩展 (DataSource Extension) 默认实现:`TRPCEntityObjectDataSource`。 自定义: ```ts class RestEntityDataSource { // implements IEntityDataSource async findOne({ id }) { /* fetch(`/api/entity/${id}`) */ } async findMany({ modelName, query }) { /* ... */ return { data: [], count: 0 }; } // 其余方法按接口补全 } class MyDataSourceFactory { // implements IEntityDataSourceFactory private ds = new RestEntityDataSource(); getDataSource(){ return this.ds; } } // 在初始化时替换 // 方式一:fork 引擎创建逻辑 // 方式二:后续扩展 createEntityEngine 暴露工厂注入(TODO: 参见 Roadmap) ``` --- ## 校验与默认值 (Validation & Defaults) 1. 字段可以直接提供 `schema: ZodTypeAny`;否则按类型自动推导。 2. 模型委托 `EntityModelDelegate.schema` 聚合所有字段 schema -> 用于 form 校验。 3. 未提供值时,通过 `toSupplementedValues` 使用字段默认值或类型默认值填充。 > 注意:复杂业务校验(交叉字段、异步校验)可在上层表单库 / 后端再补充。 --- ## tRPC 集成 (tRPC Integration) 文件:`src/services/api/trpc/utils.ts` ```ts export const vanillaTrpcClient = createTRPCClient<AppRouter>({ links: [ httpBatchLink({ url, transformer: superjson }) ] }); ``` 数据源 `TRPCEntityObjectDataSource` 通过 `vanillaTrpcClient.model.*` 调用,例如: ```ts await vanillaTrpcClient.model.listObjects.query({ modelName, pagination, reference, filter }); ``` > SSR 关闭 (`ssr: false`),如需要服务端渲染可扩展配置。 --- ## 常用接口参考 (API Reference Snapshot) > 摘要列出关键接口字段,详见源码 `src/**`。 ### IEntityModel ```ts interface IEntityModel { name: string; title: string; description?: string; fields: IEntityField[] } ``` ### IEntityField ```ts interface IEntityField { name; title; type; isRequired?; isPrimaryKey?; isUnique?; searchable?; defaultValue?; refModel?; refField?; schema?; order?; } ``` ### IEntityView ```ts interface IEntityView { name; title; modelName; viewType; items: IEntityViewField[]; density?; canEdit?; canNew?; canDelete?; } ``` ### IEntityDataSource (节选) ```ts interface IEntityDataSource { findOne({ id }): Promise<IEntityObject | null | undefined>; findMany({ modelName, query }): Promise<{ data: IEntityObject[]; count: number }>; create({ modelName, data, reference? }): Promise<IEntityObject | null | undefined>; update({ id, data }): Promise<boolean>; delete({ id }): Promise<boolean>; } ``` --- ## Roadmap | 状态 | 目标 | 说明 | | ---- | ---- | ---- | | | 基础引擎单例 / 模型 / 视图补全 | 当前实现 | | | tRPC 数据源 CRUD & 引用 & | `TRPCEntityObjectDataSource` | | | 内置视图 (form / grid / mastail / shell) | 可扩展注册更多 | | | 数据源注入定制 API | 允许外部直接提供自定义 factory | | | 视图运行时条件解析器抽象 | 允许替换表达式解析策略 | | | 字段类型可配置默认 WidgetMap | 细粒度控制 per viewType | | | 国际化 (i18n) 插件化 | `locales` 目录集成 | | | 权限与操作策略接口 | 结合 `permission.types` | | | 测试覆盖提升 | Unit + Integration + 可视回归 | 欢迎在 Issue / PR 中补充你的需求。 --- ## 贡献指南 (Contributing) 1. Fork / 新建分支:`feat/x`、`fix/x`。 2. 安装依赖并执行 `yarn build` 确保通过。 3. 提交前运行: ```bash yarn lint && yarn fm:check ``` 4. 附带最小复现或使用示例。 5. 遵循语义化提交(建议):`feat: ...` / `fix: ...` / `docs: ...` 等。 --- ## 许可 (License) 当前 `package.json` 标记为 `private: true`,尚未明确开源 License。若计划公开发布,建议: 1. 选择合适协议(MIT / Apache-2.0 / MPL-2.0 等)。 2. 添加 `LICENSE` 文件并在 `package.json` 中声明 `"license": "MIT"`(示例)。 3. 更新本 README 中的许可章节。 --- ## 常见问题 (FAQ) | 问题 | 说明 | | ---- | ---- | | 为什么需要 EngineInitializer? | 保证首次创建时批量注册,避免重复注册与竞态。 | | 可以二次调用 getEntityEngine(initializer)? | 第二次会忽略传入的 initializer(已存在实例);如需重置需扩展清理逻辑。 | | 未定义视图时字段顺序如何? | 使用字段的 `order`,缺省为 0;同序时按声明顺序。 | | 查询表达式如何解析? | 核心未内置解析器,留给上层(前端构造统一结构后传至后端)。 | | 如何做权限控制? | 未来 Roadmap 中将引入权限接口,可对模型 / 字段 / 动作进行裁剪。 | --- ## 致谢 (Acknowledgements) 本项目借鉴了多种低代码 / 元数据驱动思想(如 Ad Hoc Admin、Headless CMS、tRPC 模式),感谢社区生态。 --- 如有问题或改进建议,欢迎提交 Issue / PR。 > Keep building data-driven UI with less boilerplate.