From 12b9ab3f4bba06d7ddb1a28b11394a66b9d5e526 Mon Sep 17 00:00:00 2001 From: skycurtain Date: Thu, 9 Apr 2026 20:22:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(schema):=20=E6=96=B0=E5=A2=9E=E4=BD=8E?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=AE=BE=E8=AE=A1=E5=99=A8=E6=A0=B8=E5=BF=83?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 引入完整的低代码设计器 schema 架构,包含以下核心模块: - application: 应用级配置容器,整合所有设计时和运行时配置 - core: 基础类型定义(Entity、JsonValue、DynamicExpression 等) - component: 组件树结构,支持布局、样式、数据和交互 - page: 页面配置,支持多种布局模式(自由、弹性、流式) - route: 路由系统,支持嵌套路由和重定向 - variable: 全局状态管理 - data-source: 数据源抽象,支持 REST 和数据库协议 - query: 数据查询配置,支持静态数据、REST 等多种模式 - mutation: 数据变更操作 - filter: 数据过滤管道 - interaction: 交互系统,包含事件和动作机制 同时更新 package.json 的模块入口,并添加 AGENTS.md 项目说明文档。 --- AGENTS.md | 21 +++ package.json | 2 +- index.ts => src/index.ts | 0 .../design-mode/application/application.ts | 103 +++++++++++ src/schema/design-mode/application/index.ts | 1 + .../design-mode/component/component-data.ts | 14 ++ .../design-mode/component/component-layout.ts | 37 ++++ src/schema/design-mode/component/component.ts | 47 +++++ src/schema/design-mode/component/index.ts | 3 + src/schema/design-mode/core/condition.ts | 60 +++++++ src/schema/design-mode/core/css-properties.ts | 4 + .../design-mode/core/dynamic-expression.ts | 52 ++++++ src/schema/design-mode/core/entity.ts | 33 ++++ src/schema/design-mode/core/index.ts | 6 + src/schema/design-mode/core/json-value.ts | 24 +++ src/schema/design-mode/core/rest-request.ts | 109 ++++++++++++ .../data-source/data-source-by-database.ts | 6 + .../data-source/data-source-by-rest.ts | 67 +++++++ .../design-mode/data-source/data-source.ts | 23 +++ src/schema/design-mode/data-source/index.ts | 3 + src/schema/design-mode/filter/filter.ts | 69 ++++++++ src/schema/design-mode/filter/index.ts | 1 + src/schema/design-mode/index.ts | 0 src/schema/design-mode/interaction/action.ts | 85 +++++++++ src/schema/design-mode/interaction/index.ts | 2 + .../design-mode/interaction/interaction.ts | 64 +++++++ src/schema/design-mode/mutation/index.ts | 2 + .../design-mode/mutation/mutation-config.ts | 70 ++++++++ src/schema/design-mode/mutation/mutation.ts | 48 +++++ src/schema/design-mode/page/index.ts | 2 + src/schema/design-mode/page/page-layout.ts | 35 ++++ src/schema/design-mode/page/page.ts | 61 +++++++ src/schema/design-mode/query/index.ts | 1 + src/schema/design-mode/query/query-config.ts | 92 ++++++++++ src/schema/design-mode/query/query.ts | 108 +++++++++++ src/schema/design-mode/route/index.ts | 1 + src/schema/design-mode/route/route.ts | 79 +++++++++ src/schema/design-mode/variable/index.ts | 1 + src/schema/design-mode/variable/variable.ts | 84 +++++++++ temp.ts | 167 ------------------ 40 files changed, 1419 insertions(+), 168 deletions(-) create mode 100644 AGENTS.md rename index.ts => src/index.ts (100%) create mode 100644 src/schema/design-mode/application/application.ts create mode 100644 src/schema/design-mode/application/index.ts create mode 100644 src/schema/design-mode/component/component-data.ts create mode 100644 src/schema/design-mode/component/component-layout.ts create mode 100644 src/schema/design-mode/component/component.ts create mode 100644 src/schema/design-mode/component/index.ts create mode 100644 src/schema/design-mode/core/condition.ts create mode 100644 src/schema/design-mode/core/css-properties.ts create mode 100644 src/schema/design-mode/core/dynamic-expression.ts create mode 100644 src/schema/design-mode/core/entity.ts create mode 100644 src/schema/design-mode/core/index.ts create mode 100644 src/schema/design-mode/core/json-value.ts create mode 100644 src/schema/design-mode/core/rest-request.ts create mode 100644 src/schema/design-mode/data-source/data-source-by-database.ts create mode 100644 src/schema/design-mode/data-source/data-source-by-rest.ts create mode 100644 src/schema/design-mode/data-source/data-source.ts create mode 100644 src/schema/design-mode/data-source/index.ts create mode 100644 src/schema/design-mode/filter/filter.ts create mode 100644 src/schema/design-mode/filter/index.ts create mode 100644 src/schema/design-mode/index.ts create mode 100644 src/schema/design-mode/interaction/action.ts create mode 100644 src/schema/design-mode/interaction/index.ts create mode 100644 src/schema/design-mode/interaction/interaction.ts create mode 100644 src/schema/design-mode/mutation/index.ts create mode 100644 src/schema/design-mode/mutation/mutation-config.ts create mode 100644 src/schema/design-mode/mutation/mutation.ts create mode 100644 src/schema/design-mode/page/index.ts create mode 100644 src/schema/design-mode/page/page-layout.ts create mode 100644 src/schema/design-mode/page/page.ts create mode 100644 src/schema/design-mode/query/index.ts create mode 100644 src/schema/design-mode/query/query-config.ts create mode 100644 src/schema/design-mode/query/query.ts create mode 100644 src/schema/design-mode/route/index.ts create mode 100644 src/schema/design-mode/route/route.ts create mode 100644 src/schema/design-mode/variable/index.ts create mode 100644 src/schema/design-mode/variable/variable.ts delete mode 100644 temp.ts diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0214117 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,21 @@ +# DatAlive 低代码设计器数据结构设计 + +## 项目介绍 + +本项目的实现目标是设计一个尽可能通用的低代码设计器平台的核心数据结构,该平台能够支持以拖拽和配置组件、路由、状态、事件、过滤器、页面等元素,以可视化的交互方式搭建业务应用、数字大屏等系统。 + +所谓“核心数据结构”,主要是在平台中设计应用或大屏系统时,设计器需要持有的各类配置信息,大致可以分为设计时配置和运行时配置。 + +设计时配置指用户在设计器中拖拽、配置组件、路由、状态、事件、过滤器、页面等元素时,设计器需要保存的配置信息。 + +运行时配置指用户在设计器中完成应用或大屏系统的设计后,需要在运行时加载的配置信息。 + +现阶段的实现目标即是构造能够描述上述设计时配置和运行时配置的核心数据结构。 + +在技术栈方面,本项目希望积极拥抱 `React` + `Vite` 的最新生态体系,包括但不限于 `React`、`React Router` 或 `Tanstack Router`、`Tanstack Query`、`Zustand` 等。 + +## 项目规则 + +### 协作规则 + +1. 严禁直接修改项目代码,仅展示你的方案,由我来手动修改代码。 diff --git a/package.json b/package.json index 156d736..6b275f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "datalive-design", - "module": "index.ts", + "module": "src/index.ts", "type": "module", "private": true, "devDependencies": { diff --git a/index.ts b/src/index.ts similarity index 100% rename from index.ts rename to src/index.ts diff --git a/src/schema/design-mode/application/application.ts b/src/schema/design-mode/application/application.ts new file mode 100644 index 0000000..0218510 --- /dev/null +++ b/src/schema/design-mode/application/application.ts @@ -0,0 +1,103 @@ +import type { DataSource } from '../data-source'; +import type { Filter } from '../filter'; +import type { Interaction } from '../interaction'; +import type { Mutation } from '../mutation'; +import type { Page } from '../page'; +import type { Query } from '../query'; +import type { Route } from '../route'; +import type { Variable } from '../variable'; + +export interface Application { + /** + * 应用的唯一全局标识符 + */ + id: string; + /** + * 应用的名称 (如:"智慧能源管理平台") + * 仅用于控制台展示或浏览器 title + */ + title: string; + /** + * 创建时间 + */ + createdTime: string; + /** + * 更新时间 + */ + updatedTime: string; + /** + * 路由体系 + * + * 决定了应用由哪些页面组成,以及页面间的跳转关系(嵌套路由/重定向)。 + * + * 引擎需根据运行环境,动态采用 BrowserRouter 或 MemoryRouter 进行接管。 + */ + routes: Route[]; + /** + * 页面 + * + * 包含了该应用下所有的画板实例。 + * + * 每个 Page 决定了自身的排版模式(Free/Flex/Flow),并作为根节点挂载了一棵庞大的组件树。 + */ + pages: Page[]; + /** + * 变量 + * + * 强类型的状态容器。它是组件通信、属性动态绑定、控制流(Condition)的核心依赖。 + * + * 引擎需将其映射为一个支持响应式订阅的 Store(如 Zustand),确保一处修改,全局重绘。 + */ + variables: Variable[]; + /** + * 数据源 + * + * 定义底层的通信协议(如 RESTful API 的 baseUrl 和公共鉴权 Headers,或数据库的连接配置)。 + * + * 它是为上层的 Query/Mutation 提供网络 IO 能力的基础设施。 + */ + dataSources: DataSource[]; + /** + * 查询 + * + * 专用于向后端发起无副作用的数据获取请求(如获取列表、详情)。 + * + * 引擎实现指引: + * + * 1. 它可以享受平台提供的高级状态机特性:如页面加载预热(prefetch)、定时轮询(polling)、自动缓存(cache)。 + * 2. 它会被映射为类似 Tanstack Query 的 `useQuery` 实例。 + * 3. 组件通过 `dataBinding` 机制,声明式地订阅它的结果(data, isLoading),实现数据驱动视图。 + */ + queries: Query[]; + /** + * 变更 + * + * 专用于修改服务端状态(如提交表单、删除数据、上传文件)。 + * + * 引擎实现指引: + * + * 1. 坚决不能在页面加载时自动执行!它没有 polling 和 prefetch 配置。 + * 2. 它会被映射为类似 Tanstack Query 的 `useMutation` 实例。 + * 3. 它只能作为一种【命令式动作 (Imperative Action)】,在组件的交互事件 (Interactions) 中被手动调用。 + * 4. 调用成功后,通常需要配合触发其他 Action (如显示 Toast、或刷新某个 Query)。 + */ + mutations: Mutation[]; + /** + * 数据过滤器 + * + * 独立的纯函数字符串集合。 + * + * 专供组件在订阅 `Query` 后,进行局部的数据格式化、过滤或多源聚合操作。 + * + * 确保了一份原始数据(Query)能被洗成多种形态供不同图表复用。 + */ + filters: Filter[]; + /** + * 实体交互 + * + * 挂载于应用最顶层的交互配置。 + * + * 例如:监听 `onAppLaunch`(应用启动)事件,去触发某个全局鉴权 API,或初始化某个关键 Variable。 + */ + interactions: Interaction[]; +} diff --git a/src/schema/design-mode/application/index.ts b/src/schema/design-mode/application/index.ts new file mode 100644 index 0000000..ec34628 --- /dev/null +++ b/src/schema/design-mode/application/index.ts @@ -0,0 +1 @@ +export * from './application'; \ No newline at end of file diff --git a/src/schema/design-mode/component/component-data.ts b/src/schema/design-mode/component/component-data.ts new file mode 100644 index 0000000..41e99f5 --- /dev/null +++ b/src/schema/design-mode/component/component-data.ts @@ -0,0 +1,14 @@ +import type { JsonValue } from '../core'; + +export interface ComponentDataByStatic { + type: 'static'; + value: JsonValue; +} + +export interface ComponentDataByQuery { + type: 'query'; + queryIds: string[]; + filterIds: string[]; +} + +export type ComponentData = ComponentDataByStatic | ComponentDataByQuery; diff --git a/src/schema/design-mode/component/component-layout.ts b/src/schema/design-mode/component/component-layout.ts new file mode 100644 index 0000000..c88b43b --- /dev/null +++ b/src/schema/design-mode/component/component-layout.ts @@ -0,0 +1,37 @@ +export type ComponentLayout = + | ComponentLayoutByFree + | ComponentLayoutByFlex + | ComponentLayoutByFlow; + +export interface ComponentLayoutByFree { + mode: 'free'; + top: number; + left: number; + width: number; + height: number; + visibility: string; // 组件可见性 + opacity: number; // 组件透明度 + pointerEvents: string; // 事件穿透 + // allowDrag: boolean; // 允许拖动 + // allowDrop: boolean; // 允许放置 + /** + * 空间形变数据 (支持大屏的高级 2D/3D 视觉需求) + */ + transform?: { + rotateX?: number; // 绕X轴翻转 (3D) + rotateY?: number; // 绕Y轴翻转 (3D) + rotateZ?: number; // 平面旋转 (即原来的 rotation) + scaleX?: number; // X轴缩放 + scaleY?: number; // Y轴缩放 + }; +} + +// TODO: 弹性布局配置,暂不实现 +export interface ComponentLayoutByFlex { + mode: 'flex'; +} + +// TODO: 流式布局配置,暂不实现 +export interface ComponentLayoutByFlow { + mode: 'flow'; +} diff --git a/src/schema/design-mode/component/component.ts b/src/schema/design-mode/component/component.ts new file mode 100644 index 0000000..5e9abe8 --- /dev/null +++ b/src/schema/design-mode/component/component.ts @@ -0,0 +1,47 @@ +import type { + DynamicExpression, + Condition, + CSSProperties, + Entity, + JsonValue, +} from '../core'; +import type { Interaction } from '../interaction'; +import type { ComponentData } from './component-data'; +import type { ComponentLayout } from './component-layout'; + +export interface Component extends Entity { + /** + * 组件类型 + * + * 组件类型用于标识组件的类型,例如 `input`、`button` 等,就是组件名。 + */ + type: string; + /** + * 组件来源类型 + * + * 组件来源类型用于标识组件的来源,例如 `builtin` 表示内置组件,`remote` 表示远程组件。 + */ + sourceType: 'builtin' | 'remote'; + children?: Component[]; + /** + * 条件渲染 + */ + condition?: Condition; + layout: ComponentLayout; + style: CSSProperties; + props: Record; + /** + * 图层管理属性 + * + * 仅影响编辑器中的交互行为和图层面板状态,不影响运行时渲染。 + * e.g. 锁定、隐藏、选中策略 + */ + layer: { + locked: boolean; // 锁定后在画布中不可被选中或移动 + hidden: boolean; // 在设计器中隐藏(辅助线/临时隐藏),但运行时可能可见 + }; + data: ComponentData; + interactions: Interaction[]; +} + +export type ComponentPropValue = JsonValue | DynamicExpression | undefined; diff --git a/src/schema/design-mode/component/index.ts b/src/schema/design-mode/component/index.ts new file mode 100644 index 0000000..6e14e26 --- /dev/null +++ b/src/schema/design-mode/component/index.ts @@ -0,0 +1,3 @@ +export * from './component'; +export * from './component-data'; +export * from './component-layout'; diff --git a/src/schema/design-mode/core/condition.ts b/src/schema/design-mode/core/condition.ts new file mode 100644 index 0000000..8a8cf52 --- /dev/null +++ b/src/schema/design-mode/core/condition.ts @@ -0,0 +1,60 @@ +import type { DynamicExpression } from './dynamic-expression'; + +/** + * 核心逻辑条件分支 (Logical Condition) + * + * 【架构定位】: + * + * 低代码平台中负责“控制流拦截 (Control Flow Gatekeeping)”的最小原子单元。 + * + * 广泛被复用于: + * + * 1. Component 的 `condition` (物理级挂载/销毁,类似于 v-if 或 Conditional Rendering) + * 2. Action 的 `condition` (交互动作是否被允许执行的拦截器) + * 3. Query 的 `enabled` (串行请求的依赖挂起开关) + * + * 【双模演进路线】: + * + * - Code 模式:MVP 阶段的基石,通过纯函数赋予实施人员图灵完备的逻辑判断能力。 + * - Rules 模式:远期规划,通过基于 JSON 序列化的“可视化规则树”降低非研发人员的使用门槛。 + */ +export type Condition = ConditionByCode | ConditionByRules; + +/** + * 纯代码条件判断 (Code Mode) + * + * 赋予实施人员极其灵活且强大的逻辑判断能力。 + * + * 引擎执行指引 (JIT 编译): + * + * `code` 是一段接收 `context` 为入参的纯函数字符串。 + * 引擎必须使用 `new Function('context', code)` 动态执行它。 + * 实施人员可以从 `context` 中解构出当前应用的全局运行时状态(如 `variables`, `queries`), + * 并利用这些状态编写复杂的条件组合(如 `return context.variables.role === 'admin' && context.queries.checkAuth.data.isValid`)。 + * + * 引擎防呆校验: + * + * 该函数执行的返回值**必须被引擎强行转换为 `boolean` 类型**(如使用 `!!result`), + * 任何非布尔值的返回都会导致控制流产生不可预期的歧义行为。 + */ +export interface ConditionByCode extends DynamicExpression {} + +// TODO: 预留规则条件,用于可视化配置,暂不实现 +/** + * 可视化规则树判断 (Rules Mode - 暂不实现) + * + * 专为“无代码(No-Code)”和业务人员设计的图形化逻辑配置模式。 + * + * 业务场景: + * 当用户在设计器界面上,通过点击下拉框拼凑出诸如: + * [ “当前用户角色” “等于” “管理员” ] AND [ “当前订单状态” “不等于” “已取消” ] + * 这样的逻辑表达式时,设计器将把这些图形化选项序列化为一棵基于 JSON 的 Rule Tree。 + * + * 引擎执行指引: + * 引擎需内置一个规则解析器 (Rule Evaluator),递归遍历这棵 JSON 树, + * 提取其中的运算符 (operator) 和变量引用 (field),最终运算得出一个 `boolean` 值。 + */ +export type ConditionByRules = { + type: 'rules'; + // rules: Rule[]; +}; diff --git a/src/schema/design-mode/core/css-properties.ts b/src/schema/design-mode/core/css-properties.ts new file mode 100644 index 0000000..952bc9b --- /dev/null +++ b/src/schema/design-mode/core/css-properties.ts @@ -0,0 +1,4 @@ +// TODO: 完善 CSSProperties 接口 +export interface CSSProperties { + [key: string]: string; +} diff --git a/src/schema/design-mode/core/dynamic-expression.ts b/src/schema/design-mode/core/dynamic-expression.ts new file mode 100644 index 0000000..0d8d33a --- /dev/null +++ b/src/schema/design-mode/core/dynamic-expression.ts @@ -0,0 +1,52 @@ +/** + * 动态代码表达式 (Dynamic Expression) + * + * 【架构定位】: + * + * 这是整个低代码平台实现“图灵完备性”和“动态数据流”的终极逃生舱 (Ultimate Escape Hatch)。 + * + * 无论是组件属性 (props) 的动态求值、查询条件 (Condition) 的逻辑判断, + * 还是数据清洗管道 (Filter) 的转换逻辑,全部收敛于此接口。 + * + * 【引擎沙箱与执行指引 (Sandbox Execution Guide)】: + * + * 1. 纯函数体特性: + * `code` 字段保存的绝不是一个完整的函数定义(如 `function foo() {}`), + * 而是**函数体内部的代码片段 (Function Body)**。 + * 因此,用户在编写这段代码时,**必须显式地使用 `return` 语句返回最终的计算结果!** + * + * 2. 动态编译 (JIT Compilation): + * 渲染引擎在解析到带有 `type: 'code'` 的对象时,必须在运行时动态编译它。 + * 标准做法是使用 `new Function(paramNames, codeString)`。 + * + * 3. 全局上下文注入 (Context Injection): + * 引擎在调用编译后的函数时,必须将当前应用的全局状态快照,封装为一个 `context` 对象注入其中。 + * `context` 至少包含: + * - `variables`: 当前所有全局变量的最新值。 + * - `queries`: 当前所有数据查询的最新状态 (data, isLoading, error)。 + * - (可选) `event`: 当该表达式在交互动作 (Interaction) 中被执行时,触发它的源生事件对象。 + * + * 示例: + * + * { + * type: 'code', + * code: "const user = context.variables.currentUser; return user.role === 'admin' ? 'red' : 'blue';" + * } + */ +export interface DynamicExpression { + /** + * 引擎执行标识 (Type Discriminator) + * + * 明确告诉解析器:这是一个需要被丢进 JS 沙箱里去算的代码对象, + * 绝不是一个普通的拥有 `code` 属性的静态字典。 + */ + type: 'code'; + /** + * 动态求值的纯函数代码体 (Function Body String) + * + * 引擎必须通过形如 `const fn = new Function('context', code)` 的方式实例化。 + * + * 用户代码中必须包含合法的 `return` 语句(除非是用于执行纯副作用的 Action,但也建议 return)。 + */ + code: string; +} diff --git a/src/schema/design-mode/core/entity.ts b/src/schema/design-mode/core/entity.ts new file mode 100644 index 0000000..8448e5a --- /dev/null +++ b/src/schema/design-mode/core/entity.ts @@ -0,0 +1,33 @@ +/** + * 实体基类 (Entity Base) + * + * 【架构定位】: + * + * 它是低代码平台中所有独立配置节点(如 Component, Variable, Query 等)的万物之源。 + * + * 强制所有核心节点继承此接口,是为了确保整个 Schema 树在进行 + * 扁平化映射(Normalization)、全局事件总线寻址、以及状态树订阅时, + * 拥有一个绝对统一、可依赖的唯一标识符(ID)。 + */ +export interface Entity { + /** + * 实体的全局唯一标识符 (UUID / Unique ID) + * + * 引擎防呆指引: + * 必须保证该 ID 在整个应用 (Application) 的同类实体池中全局唯一。 + * 渲染引擎在构建 React 列表或遍历 DOM 树时,将强依赖此字段作为 `key`。 + * 同时,它也是 Action.target.id 进行 RPC 调用的核心靶点。 + */ + id: string; + /** + * 实体的显示名称 (Display Label) + * + * 核心架构边界: + * 它仅仅用于设计器左侧的“图层面板”、“变量列表”或右侧“属性面板”向实施人员展示一个友好的中文名称 + * (如:“提交按钮”、“获取用户列表查询”)。 + * + * 坚决抵制在任何底层的 JS 表达式求值或数据流绑定中引用 `label`! + * (代码逻辑的引用必须依赖不可变的 `key` 或是系统分配的 `id`) + */ + label: string; +} diff --git a/src/schema/design-mode/core/index.ts b/src/schema/design-mode/core/index.ts new file mode 100644 index 0000000..e444c6e --- /dev/null +++ b/src/schema/design-mode/core/index.ts @@ -0,0 +1,6 @@ +export * from './dynamic-expression'; +export * from './condition'; +export * from './css-properties'; +export * from './entity'; +export * from './json-value'; +export * from './rest-request'; diff --git a/src/schema/design-mode/core/json-value.ts b/src/schema/design-mode/core/json-value.ts new file mode 100644 index 0000000..0304932 --- /dev/null +++ b/src/schema/design-mode/core/json-value.ts @@ -0,0 +1,24 @@ +/** + * JSON 原生可序列化值 (Strict JSON Serializable Value) + * + * 【架构定位】: + * + * 它是低代码 Schema 抵御脏数据污染的“类型铁壁 (Type Barrier)”。 + * + * 核心背景: + * + * 低代码的 `design-mode` 配置蓝图,最终宿命是调用 `JSON.stringify` 存入数据库。 + * 如果在 Schema 的属性(如 `props`, `data`)中滥用 `any` 或 `object`, + * 实施人员极易在配置中混入 `Function`, `Date`, `RegExp` 或类实例等非法对象, + * 这将导致序列化时数据丢失(变成死字符串或 undefined),引发毁灭性的页面崩溃。 + * + * 此递归类型(Recursive Type)在 TypeScript 编译阶段, + * 就极其原教旨主义地封杀了所有非标准 JSON 类型的侵入。 + */ +export type JsonValue = + | string + | number + | boolean + | null + | JsonValue[] + | { [key: string]: JsonValue }; diff --git a/src/schema/design-mode/core/rest-request.ts b/src/schema/design-mode/core/rest-request.ts new file mode 100644 index 0000000..fc51c61 --- /dev/null +++ b/src/schema/design-mode/core/rest-request.ts @@ -0,0 +1,109 @@ +import type { DynamicExpression } from './dynamic-expression'; +import type { JsonValue } from './json-value'; + +/** + * RESTful 请求配置 + * + * 【架构定位】: + * + * - 低代码平台底层网络 I/O 的通用描述符。 + * - 广泛被 QueryConfig 和 MutationConfig 复用。 + * + * 采用“顶层双模设计”, + * 在提供常规的可视化表单配置(fixed)的同时,保留了极客级别的代码逃生舱(code)。 + */ +export type RestRequest = RestRequestByFixed | RestRequestByCode; + +/** + * 可视化表单请求配置 + * + * 适用于 90% 的标准 HTTP 请求。 + * 引擎会将这些配置项与全局的 DataSource 配置进行深度合并(Merge)。 + */ +export interface RestRequestByFixed { + type: 'fixed'; + /** + * 关联的全局数据源 ID + * + * 引擎实现指引: + * + * 如果指定了 DataSource,引擎应提取该数据源的 baseUrl、params、headers, + * 并与当前请求的配置进行合并(当前请求的优先级更高)。 + */ + dataSourceId?: string; + /** + * 请求路径 + * + * 可以是完整的绝对路径(若无 dataSourceId 或需要跨域调用第三方 API), + * 也可以是相对路径(如 `/users`,引擎会自动拼接 DataSource 的 baseUrl)。 + */ + url: string; + /** + * URL 查询参数 (Query String) + * + * 引擎防呆设计 (Poka-yoke): + * + * `params` 最终会被序列化拼接在 URL 后面(如 `?name=A&age=18`)。 + * 坚决抵制在此处传入对象或数组!如果传入 `{ a: { b: 1 } }`, + * 多数底层客户端(如原生 Fetch 或默认配置的 Axios)会将其序列化为 `?a=[object Object]`, + * 这会导致后端网关解析崩溃且极其难以排查。 + * 因此,类型被严格锁定为基础数据类型(Primitive Types)。 + * + * 【核心架构:null 的显式抹除语义 (Explicit Erasure)】 + * + * 引入 `null` 是为了解决局部请求“抹除”全局 DataSource 继承配置的刚需。 + * 如果全局配置了 `params: { tenantId: '123' }`,而当前请求不需要带此参数, + * 用户必须在当前配置中显式设置 `params: { tenantId: null }`。 + * 引擎在合并配置时,一旦遇到值为 `null` 的键,必须从最终的请求参数中将其物理删除。 + */ + params?: Record; + /** + * HTTP 请求头 (Headers) + * + * 同上理,HTTP Headers 协议本身只接受字符串。 + * 如果在 Axios 中传了一个对象(如 `headers: { Authorization: { token: '123' } }`), + * Axios 就会直接把这个对象序列化成 `[object Object]` 发给后端! + * 必须锁定为基础数据类型。 + * + * 同样支持 `null` 值的显式抹除语义(常用于抹除全局继承的 Token 以调用第三方开放 API)。 + */ + headers?: Record; + /** + * HTTP 请求体 (Payload / Body) + * + * 引擎实现指引: + * 不强制 body 一定是一个对象,用户可以根据需要自定义。 + * 当 method 为 GET 时,此字段通常被底层 HTTP 客户端忽略。 + * + * 必须使用 JsonValue 以守住存入数据库时的序列化底线,防止混入 Function 等非法对象。 + */ + body?: JsonValue; +} + +/** + * 纯代码请求配置 (Code Mode 逃生舱) + * + * 解决低代码平台“图灵完备性不足”的终极武器。 + * + * 业务场景: + * + * 当实施人员面临极度变态的鉴权或加密需求时 + * (例如:需要提取当前时间戳、加上全局变量 token,混合后进行 MD5 加密, + * 最后生成动态的 Headers、签名 Params 和变异的 URL)。 + * 这在可视化的 fixed 模式下是根本无法完成的。 + * + * 引擎实现指引: + * + * `code` 是一段纯函数字符串。它接收全局上下文 `context` 作为参数 + * (可解构出 `variables`, `queries` 等运行时状态)。 + * 它必须 `return` 一个符合 `Omit` 结构的对象。 + * 引擎在执行该函数(JIT 编译)后,拿着返回的对象去发真实的 HTTP 请求。 + */ +export interface RestRequestByCode extends DynamicExpression { + /** + * 关联的全局数据源 ID + * + * 即使在使用代码动态生成请求体时,也依然可以继承底层 DataSource 的公共配置(如 baseUrl)。 + */ + dataSourceId?: string; +} diff --git a/src/schema/design-mode/data-source/data-source-by-database.ts b/src/schema/design-mode/data-source/data-source-by-database.ts new file mode 100644 index 0000000..4a72eb5 --- /dev/null +++ b/src/schema/design-mode/data-source/data-source-by-database.ts @@ -0,0 +1,6 @@ +import type { Entity } from '../core'; + +// TODO: 数据库数据源, 暂不实现 +export interface DataSourceByDatabase extends Entity { + type: 'database'; +} diff --git a/src/schema/design-mode/data-source/data-source-by-rest.ts b/src/schema/design-mode/data-source/data-source-by-rest.ts new file mode 100644 index 0000000..4970909 --- /dev/null +++ b/src/schema/design-mode/data-source/data-source-by-rest.ts @@ -0,0 +1,67 @@ +import type { DynamicExpression, Entity } from '../core'; + +/** + * RESTful 协议数据源 + * + * 专用于配置基于 HTTP 的网络通信实体。 + */ +export interface DataSourceByRest extends Entity { + /** + * 引擎路由标识:告诉引擎必须使用 Axios 或 Fetch 客户端来处理此数据源下的请求。 + */ + type: 'rest'; + /** + * RESTful 数据源的核心配置 + * + * 采用“顶层双模设计”: + * 既支持常规的可视化表单配置,也支持代码模式(应对极其复杂的动态鉴权计算,如时间戳签名)。 + */ + config: DataSourceConfigByRest; +} + +export type DataSourceConfigByRest = + | DataSourceConfigByRestWithFixed + | DataSourceConfigByRestWithCode; + +/** + * REST 数据源的常规可视化配置 + * + * 适用于 90% 的标准 HTTP 请求场景。 + */ +export interface DataSourceConfigByRestWithFixed { + type: 'fixed'; + /** + * 基础路径 (Base URL) + * + * 必须是一个完整的绝对 URL(如 `https://api.datalive.com/v1`)。 + * + * 保持 URL 的绝对独立性是防呆设计的关键,避免相对路径引发的拼接灾难。 + */ + baseUrl: string; + /** + * 全局默认的 URL Query 参数 + * + * 引擎在发送该数据源下的任何 Query 时,都会自动将此对象拼接到 URL 后。 + * + * 为确保 Axios/Fetch 序列化的稳定性,仅允许基础数据类型(Primitive Types),坚决抵制对象或数组嵌套。 + */ + params?: Record; + /** + * 全局默认的请求头 + * + * 常用于存放如 `Authorization: Bearer ` 或 `x-tenant-id` 等鉴权信息。 + * + * 同样仅限基础数据类型。 + */ + headers?: Record; +} + +/** + * REST 数据源的代码配置模式 + * + * 引擎执行指引: + * + * 传入的 code 函数必须 return 一个符合 `Omit` 结构的对象。 + * 这赋予了实施人员在发请求前执行极度复杂的加密运算的终极权力。 + */ +export interface DataSourceConfigByRestWithCode extends DynamicExpression {} diff --git a/src/schema/design-mode/data-source/data-source.ts b/src/schema/design-mode/data-source/data-source.ts new file mode 100644 index 0000000..ad81a38 --- /dev/null +++ b/src/schema/design-mode/data-source/data-source.ts @@ -0,0 +1,23 @@ +import type { DataSourceByDatabase } from './data-source-by-database'; +import type { DataSourceByRest } from './data-source-by-rest'; + +/** + * 数据源池 + * + * 【架构定位】: + * + * 它是低代码数据流“三层漏斗”模型的最底层(第一层)。 + * + * 核心职责: + * + * 1. 隔离通信协议:将 RESTful、GraphQL、Database(MySQL/PG) 等底层的网络协议或连接池配置, + * 与上层的业务逻辑(Query)彻底解耦。 + * 2. 跨查询复用:为多个属于同一业务域的 Query 提供统一的 `baseUrl` 和鉴权 `headers` (如 Token), + * 实现一处配置,全局生效。 + * + * 【引擎实现指引】: + * + * 当应用启动时,渲染引擎应扫描此列表,并在内存中为每个 DataSource 实例化一个专门的网络客户端 + * (如针对 type: 'rest' 实例化一个配置了拦截器的 Axios 实例),以供上层 Query 调用。 + */ +export type DataSource = DataSourceByRest | DataSourceByDatabase; diff --git a/src/schema/design-mode/data-source/index.ts b/src/schema/design-mode/data-source/index.ts new file mode 100644 index 0000000..cd70f79 --- /dev/null +++ b/src/schema/design-mode/data-source/index.ts @@ -0,0 +1,3 @@ +export * from './data-source'; +export * from './data-source-by-database'; +export * from './data-source-by-rest'; diff --git a/src/schema/design-mode/filter/filter.ts b/src/schema/design-mode/filter/filter.ts new file mode 100644 index 0000000..ad53627 --- /dev/null +++ b/src/schema/design-mode/filter/filter.ts @@ -0,0 +1,69 @@ +import type { DynamicExpression, Entity } from '../core'; + +/** + * 数据过滤器 + * + * 【架构定位】: + * + * 低代码数据流管线中的“纯函数转换层 (Pure Function Transformer)”。 + * + * 核心价值: + * + * 1. 解决后端接口返回的数据结构(如嵌套极深的 JSON)与前端图表组件要求的数据结构(如平铺的 [{x, y}] 数组)不匹配的终极痛点。 + * 2. 它是全局实体,意味着写好一个“通用时间格式化”的 Filter,可以被大屏上 100 个不同的图表同时挂载复用。 + * + * 【引擎实现指引】: + * + * 1. 它通常被组件的 `ComponentDataQuery` 通过 `filterIds` 数组引用。 + * 2. 引擎在处理包含多个 Filter 的数组时,必须采用“管道模式 (Pipeline)”: + * 前一个 Filter 的返回值,必须作为后一个 Filter 的入参 `data` 传入。 + * 即:finalData = filter3(filter2(filter1(rawData)))。 + */ +export type Filter = FilterByCode | FilterByRules; + +/** + * 纯代码过滤器 + * + * 赋予实施人员处理复杂数据转换(如 map, reduce, 多数组 join)的图灵完备能力。 + * + * 引擎执行指引 (JIT 编译): + * + * 引擎在执行该 `code` 时,注入的 `context` 参数必须比普通情况多出一个极其关键的属性:`data`。 + * + * 注入上下文示例: + * + * - `context.data`: 当前需要被清洗的原始数据(或者是上一个 Filter 吐出来的数据)。 + * - `context.variables`: 全局变量(允许 Filter 根据当前状态动态调整清洗逻辑,例如:`if (variables.isMetric) return data * 1000`)。 + * + * 引擎防呆校验: + * + * 该函数执行后,**必须有明确的 `return` 返回值**。 + * 引擎若检测到返回 `undefined`,应考虑抛出警告,防止开发者漏写 return 导致数据流在管道中断裂。 + */ +export interface FilterByCode extends Entity, DynamicExpression {} + +// TODO: 预留规则过滤器,用于可视化配置,暂不实现 +/** + * 可视化规则过滤器 + * + * 专为“无代码(No-Code)”和业务人员设计的图形化清洗管道。 + * + * 业务场景: + * + * 用户不需要写 JS 代码,而是通过点击界面上的算子(Operators)进行串联。 + * 例如配置一个数组: + * [ + * { action: 'pick', field: 'records' }, // 第一步:提取 records 字段 + * { action: 'filter', field: 'age', op: '>', val: 18 }, // 第二步:过滤成年人 + * { action: 'map', from: 'userName', to: 'label' } // 第三步:字段映射重命名 + * ] + * + * 引擎执行指引: + * + * 引擎需内置一系列标准的清洗算子处理函数,根据上述 JSON 规则树, + * 在内存中动态组装并执行数据转换逻辑。 + */ +export interface FilterByRules extends Entity { + type: 'rules'; + // rules: Rule[]; +} diff --git a/src/schema/design-mode/filter/index.ts b/src/schema/design-mode/filter/index.ts new file mode 100644 index 0000000..998668a --- /dev/null +++ b/src/schema/design-mode/filter/index.ts @@ -0,0 +1 @@ +export * from './filter'; \ No newline at end of file diff --git a/src/schema/design-mode/index.ts b/src/schema/design-mode/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/schema/design-mode/interaction/action.ts b/src/schema/design-mode/interaction/action.ts new file mode 100644 index 0000000..cace6c4 --- /dev/null +++ b/src/schema/design-mode/interaction/action.ts @@ -0,0 +1,85 @@ +import type { Entity, Condition, DynamicExpression, JsonValue } from '../core'; + +/** + * 执行动作的目标实体 + * + * 泛化 RPC 命令动作 (Generalized RPC Command Action) + * + * 【架构定位】: + * + * 这是整个交互体系的巅峰设计。 + * 我们坚决拒绝将 Action 枚举化为 `ShowToast`, `Navigate`, `SetVariable` 等海量具体类型。 + * 相反,我们采用极其抽象的“一切皆实体(Entity),一切皆方法(Method)”模型。 + * + * 架构优势: + * + * 这种极致抽象保持了 Schema 结构的极简与统一。未来设计器新增几百种组件或系统能力时, + * Schema 结构一行代码都不用改;渲染引擎只需要一个通用的“方法派发器(Method Dispatcher)”即可支撑无限的扩展能力。 + */ +export interface Action extends Entity { + /** + * 命令目标 (Target Entity) + * + * 告诉引擎:这个命令是发给谁的? + * + * 目标类型举例: + * - `type: 'component'`: 调用画布上的组件(如让图表刷新,或动态修改其 layout)。 + * - `type: 'variable'`: 调用全局变量(如修改当前用户的值)。 + * - `type: 'query'`: 调用全局查询(如手动触发一次 `execute()` 发起网络请求)。 + * - `type: 'system'`: 极其关键!调用引擎提供的全局系统服务(如 `id: 'router'` 进行跳转,或 `id: 'message'` 弹出 Toast)。 + */ + target: { + type: string; + id: string; + }; + /** + * 目标方法名 (Method Name) + * + * 目标实体向外暴露的、可供执行的函数名。 + * + * 例:组件暴露的 `updateLayout`,系统路由服务暴露的 `navigateTo`。 + */ + method: string; + /** + * 执行动作的条件 + * + * 动作级拦截器 (Action Execution Condition) + * + * 在执行此单一动作前的高阶逻辑判断。 + * + * 若存在此字段且求值为 false,引擎将静默跳过此动作的执行,继续执行下一个动作。 + */ + condition?: Condition; + /** + * 方法调用入参 (Method Parameters Payload) + * + * 传递给 `method` 的额外数据。 + * + * 采用“双模设计 (Dual-Mode)”: + * 既支持固定的 JSON 字典(fixed),也支持极其强大的代码逃生舱(code), + * 允许在触发动作的一瞬间,通过 JS 代码动态拼装入参(如提取事件源 event 的值)。 + */ + params?: ActionParams; +} + +export type ActionParams = ActionParamsByFixed | ActionParamsByCode; + +/** + * 静态表单参数模式 + * + * 适用于大部分常规场景(如打开弹窗时传个死参数)。 + * payload 里的值在设计时即已确定。 + */ +export interface ActionParamsByFixed { + type: 'fixed'; + payload: Record; +} + +/** + * 动态代码参数模式 (代码逃生舱) + * + * 业务场景: + * 当你需要“点击表格的某一行,提取这一行的数据 ID,传给下一个页面或 Mutation”时。 + * 你必须在此时动态执行一段 JS 函数,从上下文中提取事件源的数据,返回一个 params 对象。 + */ +export interface ActionParamsByCode extends DynamicExpression {} diff --git a/src/schema/design-mode/interaction/index.ts b/src/schema/design-mode/interaction/index.ts new file mode 100644 index 0000000..75ad327 --- /dev/null +++ b/src/schema/design-mode/interaction/index.ts @@ -0,0 +1,2 @@ +export * from './action'; +export * from './interaction'; \ No newline at end of file diff --git a/src/schema/design-mode/interaction/interaction.ts b/src/schema/design-mode/interaction/interaction.ts new file mode 100644 index 0000000..7541da1 --- /dev/null +++ b/src/schema/design-mode/interaction/interaction.ts @@ -0,0 +1,64 @@ +import type { Entity } from '../core'; +import type { Action } from './action'; + +/** + * 实体交互 + * + * 泛化事件侦听器 + * + * 【架构定位】: + * + * 低代码平台中负责响应任何“状态突变”的唯一入口。 + * 它的名字虽叫 Interaction(人机交互),但在架构底层,它不仅监听物理鼠标键盘, + * 更监听系统级的数据流转。 + * + * 【引擎实现指引】: + * + * 根据它所挂载的宿主实体不同,引擎应将其映射为不同的事件绑定: + * 1. 挂载于 Component:映射为 DOM 原生事件或 React 组件暴露的业务事件(如 `onClick`, `onScroll`, `onRowSelect`)。 + * 2. 挂载于 Route/Page:映射为生命周期钩子(如 `onEnter`, `onLoad`)。 + * 3. 挂载于 Query/Mutation/Variable:映射为数据流的状态回调(如 `onSuccess`, `onError`, `onChange`)。 + */ +export interface Interaction extends Entity { + /** + * 触发时机/事件名称 (Event Name) + * + * 引擎会根据此名称去注册对应的监听器。 + */ + event: string; + /** + * 触发频率控制 (Debounce & Throttle) + * + * 处理高频数据流(如 Input 搜索框的 `onChange` 联动触发后端 Query)的核心防御机制。 + * + * 引擎实现指引: + * + * 如果 enabled 为 true,引擎在派发底层的 `actions` 数组前, + * 必须先经过 Lodash 或 es-toolkit 的 `debounce` 或 `throttle` 包装器。 + */ + control: { + enabled: boolean; + type: 'debounce' | 'throttle'; + delay: number; + }; + /** + * 原生事件修饰符 (Event Modifiers) + * + * 引擎实现指引: + * + * 仅当该 Interaction 是由原生 DOM 事件(如 onClick 冒泡或 Form 默认提交)触发时生效。 + * 如果是 `Query.onSuccess` 这种纯逻辑事件,此配置应被忽略。 + */ + modifiers: { + preventDefault: boolean; + stopPropagation: boolean; + }; + /** + * 动作执行序列 (Action Chain) + * + * 当事件触发(且通过频率控制与修饰符拦截)后,引擎需要按顺序串行(或并行)执行的命令集合。 + * + * 必须为一个数组,允许一个按钮点击同时触发“保存数据”和“跳转页面”两个动作。 + */ + actions: Action[]; +} diff --git a/src/schema/design-mode/mutation/index.ts b/src/schema/design-mode/mutation/index.ts new file mode 100644 index 0000000..1c516ee --- /dev/null +++ b/src/schema/design-mode/mutation/index.ts @@ -0,0 +1,2 @@ +export * from './mutation'; +export * from './mutation-config'; diff --git a/src/schema/design-mode/mutation/mutation-config.ts b/src/schema/design-mode/mutation/mutation-config.ts new file mode 100644 index 0000000..ff920b1 --- /dev/null +++ b/src/schema/design-mode/mutation/mutation-config.ts @@ -0,0 +1,70 @@ +import type { DynamicExpression, RestRequest } from '../core'; + +/** + * 数据变更核心配置引擎 + * + * 【架构定位】: + * + * 这是一个多态联合类型 (Polymorphism Union Type)。 + * 它决定了当前这个 `Mutation` 到底是通过什么“物理手段”去修改服务端状态的。 + * + * 核心架构差异(与 QueryConfig 的对比): + * + * 坚决不允许存在 `MutationConfigByStatic`! + * 因为修改一个内存里的死数据(Mock)是没有任何业务意义的,写操作必定伴随着网络 I/O 或数据库操作。 + */ +export type MutationConfig = + | MutationConfigByRest + | MutationConfigByGraphql + | MutationConfigByDatabase + | MutationConfigByCode; + +/** + * 1. RESTful 协议变更配置 (REST API) + * + * 占据了低代码数据变更 90% 以上场景的主力网络通信配置。 + */ +export interface MutationConfigByRest { + type: 'rest'; + /** + * HTTP 请求方法 + * + * 引擎防呆指引: + * 既然是写操作(Mutation),绝大部分符合 REST 语义的设计必须是以下四种动词。 + * 坚决不要在此处允许出现 GET 请求! + */ + method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; + /** + * 请求体配置 + * + * 极其关键的双模逃生舱设计 (Dual-Mode Design): + * 它引入了定义在 `core/rest-request.ts` 中的 `RestRequest` 类型 + * (`RestRequestByFixed | RestRequestByCode`)。 + * + * 这是低代码平台处理极度复杂的表单提交数据组装(如从多个 Variables 里提取数据并加密)的终极战场。 + */ + request: RestRequest; + /** + * 接口超时时间 (毫秒 ms) + * + * 变更操作(如上传大文件或复杂计算)往往比查询需要更长的超时容忍度。 + * 如果启用,引擎应使用全局 DataSource 配置的超时,或默认的浏览器超时。 + */ + timeout: { + enabled: boolean; + duration: number; + }; +} + +// TODO: GraphQL 变更配置,暂不实现 +export interface MutationConfigByGraphql { + type: 'graphql'; +} + +// TODO: 数据库变更配置,暂不实现 +export interface MutationConfigByDatabase { + type: 'database'; +} + +// TODO: 代码变更配置,暂不实现 +export interface MutationConfigByCode extends DynamicExpression {} diff --git a/src/schema/design-mode/mutation/mutation.ts b/src/schema/design-mode/mutation/mutation.ts new file mode 100644 index 0000000..35f3657 --- /dev/null +++ b/src/schema/design-mode/mutation/mutation.ts @@ -0,0 +1,48 @@ +import type { Entity, RestRequest } from '../core'; +import type { Interaction } from '../interaction'; +import type { MutationConfig } from './mutation-config'; + +/** + * 数据变更操作 + * + * 【架构定位】: + * + * 1. 它是低代码数据流“三层漏斗”模型的写操作层(Write/Command)。 + * 2. 遵循 CQRS 架构,专职处理具有副作用(Side-effects)的操作(如:提交表单、删除数据、点赞)。 + * + * 【引擎实现指引】: + * + * 渲染引擎应将其映射为一个手动触发的执行器(如 TanStack Query 的 `useMutation` 实例)。 + * 它绝对没有自动触发的能力(没有 prefetch,没有 polling,没有 enabled 挂起), + * 它只能在用户发生了交互动作(Interaction)后,通过派发 `invokeMethod` Action 被动调用执行。 + */ +export interface Mutation extends Entity { + /** + * 具体的请求协议配置 + * + * 采用极其强大的“多态联合类型(Polymorphism Union Type)”。 + * 决定了这到底是一个通过 HTTP 调用的 REST 请求(Rest), + * 抑或是一个直连数据库执行的 INSERT/UPDATE/DELETE 语句(Database)。 + * + * 引擎通过读取 `config.type` 决定派发给哪个底层的 DataSource 客户端去执行。 + */ + config: MutationConfig; + /** + * 交互 + * + * 请求生命周期与副作用控制流 + * + * 挂载于数据变更操作成功或失败时的高阶事件侦听器。 + * 这是整个低代码业务逻辑编排中最核心的“指挥中心”! + * + * 引擎实现指引: + * + * 1. 极其典型的业务串联 (Success Chain): + * 当表单提交成功 (`onSuccess`) -> 派发 Action 触发“获取用户列表(Query)”重新请求(刷新数据) + * -> 派发 Action 显示“提交成功”的全局 Toast -> 派发 Action 清空绑定的输入框变量。 + * 2. `onError` (请求失败):用于统一拦截接口异常,并在前端触发容错或告警流程。 + * + * 若无副作用,必须为一个显式的空数组 `[]`。 + */ + interactions: Interaction[]; +} diff --git a/src/schema/design-mode/page/index.ts b/src/schema/design-mode/page/index.ts new file mode 100644 index 0000000..813c257 --- /dev/null +++ b/src/schema/design-mode/page/index.ts @@ -0,0 +1,2 @@ +export * from './page'; +export * from './page-layout'; diff --git a/src/schema/design-mode/page/page-layout.ts b/src/schema/design-mode/page/page-layout.ts new file mode 100644 index 0000000..0e86630 --- /dev/null +++ b/src/schema/design-mode/page/page-layout.ts @@ -0,0 +1,35 @@ +/** + * 自由布局,用于数字大屏或看板页面 + */ +export interface FreeLayout { + mode: 'free'; + width: number; + height: number; + /** + * 缩放模式 + * + * - `fit`: 等比缩放,确保大屏内容完整显示 + * - `fill`: 放弃长宽比,强行拉伸大屏填满容器 + * - `flow`: 流式布局,宽度填满,纵向滚动 + * - `none`: 无视屏幕尺寸,不进行缩放,以真实像素值进行渲染 + */ + scaleMode: 'fit' | 'fill' | 'flow' | 'none'; // 等比缩放/全屏拉伸/纵向滚动/保持比例 +} + +/** + * 弹性布局,用于中后台页面 + */ +export interface FlexLayout { + mode: 'flex'; + minWidth: number; +} + +/** + * 流式布局,用于门户/官网场景 + */ +export interface FlowLayout { + mode: 'flow'; + maxWidth: number; +} + +export type PageLayout = FreeLayout | FlexLayout | FlowLayout; diff --git a/src/schema/design-mode/page/page.ts b/src/schema/design-mode/page/page.ts new file mode 100644 index 0000000..ecbe80e --- /dev/null +++ b/src/schema/design-mode/page/page.ts @@ -0,0 +1,61 @@ +import type { Component } from '../component'; +import type { CSSProperties, Entity } from '../core'; +import type { Interaction } from '../interaction'; +import type { PageLayout } from './page-layout'; + +/** + * 页面容器 + * + * 【架构定位】: + * + * 它是低代码视图层(View Layer)的顶级画板。 + * + * 负责统领全局的排版模式(Layout)、提供背景画板(Style)、并挂载整棵组件树(Components)。 + */ +export interface Page extends Entity { + /** + * 页面排版模式 + * + * 决定了当前页面的渲染法则(大屏绝对定位 vs 中后台自适应)。 + */ + layout: PageLayout; + /** + * 页面级静态样式 + * + * 核心架构指引: + * + * 坚决抵制“在画布底层垫一个巨大的图片/色块组件来充当背景”的反模式! + * 那会导致极其严重的误触(需要频繁锁定)、层叠上下文污染(Z-index 战争)和流式响应式坍塌。 + * + * 必须由引擎将此处的 `style`(如 backgroundColor, backgroundImage, padding) + * 原生地挂载到包裹整个页面的最外层 HTML `
` 容器上,实现真正的、零成本的背景渲染。 + */ + style: CSSProperties; + /** + * 组件树 + * + * 核心架构指引: + * + * 1. 多根节点支持:必须是数组,允许用户在白板上自由平铺多个逻辑根节点。 + * 引擎在底层会自动将 Page 实例映射为一个真实的 DOM 容器(如 `
`)来包裹这棵树。 + * 这避免了强制要求唯一 `rootComponent` 带来的无谓 DOM 嵌套(如强迫套一个 Group)和反直觉体验。 + * 2. 嵌套结构:为了保持 Schema 作为“蓝图”的直观可读性,这里存储的是完整的嵌套组件树。 + * 将组件打平为全局字典 (Normalization) 仅是引擎在运行时 Zustand Store 里的性能优化手段, + * 绝不应污染此处持久化的静态 Schema。 + */ + components: Component[]; + /** + * 交互 + * + * 页面级生命周期与事件 + * + * 挂载于当前页面的高阶控制流枢纽。 + * + * 引擎实现指引: + * + * 1. 常见的触发事件 (event):`onLoad` (页面加载完成), `onUnload` (离开页面), `onResize` (尺寸变化)。 + * 2. 典型的业务场景:在 `onLoad` 时,派发 Action 去触发某些没有开启 prefetch 的懒加载 Query, + * 或者初始化/重置某些局部的 Variables。 + */ + interactions: Interaction[]; +} diff --git a/src/schema/design-mode/query/index.ts b/src/schema/design-mode/query/index.ts new file mode 100644 index 0000000..cb67e4a --- /dev/null +++ b/src/schema/design-mode/query/index.ts @@ -0,0 +1 @@ +export * from './query'; \ No newline at end of file diff --git a/src/schema/design-mode/query/query-config.ts b/src/schema/design-mode/query/query-config.ts new file mode 100644 index 0000000..b599a45 --- /dev/null +++ b/src/schema/design-mode/query/query-config.ts @@ -0,0 +1,92 @@ +import type { DynamicExpression, JsonValue, RestRequest } from '../core'; + +/** + * 数据查询核心配置 + * + * 【架构定位】: + * + * 这是一个极其强大的多态联合类型 (Polymorphism Union Type)。 + * 它决定了当前这个 `Query` 到底是通过什么“物理手段”去获取数据的。 + * + * 引擎实现指引: + * + * 渲染引擎(或数据请求层)在执行 `Query` 时,必须写一个 `switch (config.type)`。 + * + * - 如果是 'static',O(1) 复杂度直接返回内存里的死数据。 + * - 如果是 'rest',提取其绑定的 dataSourceId,组装 Axios 请求发给网关。 + * - 未来如果是 'database',则通过 WebSocket 或专用代理发给后端执行 SQL。 + */ +export type QueryConfig = + | QueryConfigByStatic + | QueryConfigByRest + | QueryConfigByGraphql + | QueryConfigByDatabase + | QueryConfigByCode; + +/** + * 静态查询配置 + * + * 核心价值: + * + * 1. 跨组件复用:定义如“全国省份列表”等被多个组件同时绑定的全局字典数据。 + * 2. 接口 Mock:当后端真实 API 尚未开发完毕时,作为临时数据源占位。 + * 一旦后端就绪,只需在设计器将此配置一键切换为 `QueryConfigByRest`, + * 所有绑定了此 Query 的前端组件无需任何修改即可平滑接入真数据。 + */ +export interface QueryConfigByStatic { + type: 'static'; + /** + * 静态的、硬编码的全局数据。 + * 必须是合法的纯净 JSON(使用 JsonValue 锁死序列化底线,坚决不用 any)。 + */ + data: JsonValue; +} + +/** + * RESTful 查询配置 + * + * 占据了低代码数据获取 90% 以上场景的主力网络通信配置。 + */ +export interface QueryConfigByRest { + type: 'rest'; + /** + * HTTP 请求方法 + * + * 注:在 CQRS 架构中,即使是一个 POST 请求(如 GraphQL 的查询,或带有复杂查询体的高级搜索), + * 只要它是幂等的且不修改服务端状态,它在低代码中依然属于 `Query`(查询),而非 `Mutation`(变更)。 + */ + method: 'GET' | 'POST'; + /** + * 请求体配置 + * + * 极其关键的双模逃生舱设计 (Dual-Mode Design): + * 它引入了定义在 `core/rest-request.ts` 中的 `RestRequest` 类型 + * (`RestRequestByFixed | RestRequestByCode`)。 + * + * 允许用户既能通过可视化表单填死 url/params, + * 又能通过手写 JS 代码,根据全局变量动态拼装出极其复杂的请求头和加密签名。 + */ + request: RestRequest; + /** + * 接口超时时间 (毫秒 ms) + * + * 如果不启用,引擎应使用全局 DataSource 配置的超时,或默认的浏览器超时(如 30s)。 + */ + timeout: { + enabled: boolean; + duration: number; + }; +} + +// TODO: GraphQL 查询配置,暂不实现 +export interface QueryConfigByGraphql { + type: 'graphql'; +} + +// TODO: 数据库查询配置,暂不实现 +export interface QueryConfigByDatabase { + type: 'database'; +} + +// TODO: 代码查询配置,暂不实现 +export interface QueryConfigByCode extends DynamicExpression {} diff --git a/src/schema/design-mode/query/query.ts b/src/schema/design-mode/query/query.ts new file mode 100644 index 0000000..7e14fa2 --- /dev/null +++ b/src/schema/design-mode/query/query.ts @@ -0,0 +1,108 @@ +import type { Entity, Condition } from '../core'; +import type { Interaction } from '../interaction'; +import type { QueryConfig } from './query-config'; + +/** + * 数据查询配置 + * + * 【架构定位】: + * + * 1. 它是低代码数据流“三层漏斗”模型的中间层(核心业务层)。 + * 2. 遵循 CQRS 架构,专职处理幂等的“读操作(Read)”。 + * 3. 它是连接底层 DataSource(协议层)与顶层 Component(视图层)的桥梁。 + * + * 【引擎实现指引】: + * + * 渲染引擎应将其映射为一个强大的状态机(如 TanStack Query 的 `useQuery` 实例)。 + * + * 它不应将 `isLoading`, `data`, `error` 等运行时快照存入此 Schema, + * 而是由引擎在全局内存(QueryClient)中动态维护这些衍生状态,供组件声明式地订阅。 + */ +export interface Query extends Entity { + /** + * 执行条件 + * + * 决定了该查询是否允许被发送(无论是因为页面加载、轮询还是组件订阅)。 + * + * 核心场景(串行依赖): + * + * 当 Query B 需要用到 Query A 的返回结果作为参数时, + * 可在此配置 `enabled: { type: 'code', code: 'return !!context.queries.QueryA.data' }`。 + * 引擎会挂起 Query B,直到 Query A 成功返回并填充了数据,Query B 才会被自动放行。 + * + * 如果不填,默认允许执行。 + */ + enabled?: Condition; + /** + * 页面加载时自动执行 + * + * 性能优化的杀手锏。 + * + * 如果为 true,引擎在路由命中(或应用启动)的极早期阶段, + * 在 UI 组件树还未开始渲染时,就提前向服务器发起请求。 + * 这样当底层图表组件渲染完毕并尝试订阅此 Query 时,数据可能已经“热乎”地躺在缓存里了, + * 从而彻底消灭组件的初始白屏 Loading 时间。 + */ + prefetch: boolean; + /** + * 定时轮询策略 + * + * 数字大屏(Dashboard)的灵魂特性。赋予数据流实时刷新的能力。 + */ + polling: { + /** + * 是否开启轮询 + */ + enabled: boolean; + /** + * 轮询间隔时间(单位:毫秒 ms) + * + * 引擎实现指引:在页面不可见(浏览器标签页隐藏)时,引擎应智能暂停轮询以节省性能。 + */ + interval: number; + }; + /** + * 过滤器管道 + * + * 极其关键的数据转换层(Transform Layer)。 + * 包含了一系列被引用的全局 Filter ID。 + * + * 引擎实现指引: + * + * 当底层 API 返回原始 JSON 后,在存入 Query 内存缓存池(或喂给组件)之前, + * 必须将原始数据像接水管一样,串行流过这些 Filter 函数。 + * data = filter3(filter2(filter1(rawData))) + * + * 优势:让组件拿到的永远是极其纯净、格式规整的业务数据(如 `[{ x, y }]` 数组), + * 实现了“一份原始接口数据,清洗出多种衍生视图”的极致复用。 + */ + filterIds: string[]; + /** + * 交互 + * + * 请求生命周期与副作用控制流 + * + * 挂载于查询状态突变时的高阶事件侦听器。 + * + * 引擎实现指引: + * + * 1. `onSuccess` (请求成功):常用于触发数据获取后的连锁反应(如更新某个全局 Variable,或弹出“数据同步成功”的 Toast)。 + * 2. `onError` (请求失败):常用于统一拦截错误,触发异常上报或显示全局 Error Modal。 + * + * 这将原本散落在组件里的异步回调地狱,完美收拢到了数据请求自身的配置中。 + * 若无副作用,必须为一个显式的空数组 `[]`。 + */ + interactions: Interaction[]; + /** + * 具体的请求协议配置 + * + * 采用极其强大的“多态联合类型(Polymorphism Union Type)”。 + * + * 它决定了这究竟是一个静态的 Mock 数据(Static), + * 还是一个通过 HTTP 调用的 REST 请求(Rest), + * 抑或是一个直连数据库执行的 SQL 语句(Database)。 + * + * 引擎通过读取 `config.type` 决定派发给哪个底层的 DataSource 客户端去执行。 + */ + config: QueryConfig; +} diff --git a/src/schema/design-mode/route/index.ts b/src/schema/design-mode/route/index.ts new file mode 100644 index 0000000..a4f5efa --- /dev/null +++ b/src/schema/design-mode/route/index.ts @@ -0,0 +1 @@ +export * from './route'; \ No newline at end of file diff --git a/src/schema/design-mode/route/route.ts b/src/schema/design-mode/route/route.ts new file mode 100644 index 0000000..88bc1c3 --- /dev/null +++ b/src/schema/design-mode/route/route.ts @@ -0,0 +1,79 @@ +import type { Entity } from '../core'; +import type { Interaction } from '../interaction'; + +/** + * 路由 + * + * 【架构定位】: + * + * 1. 它是连接 URL 与 Page(页面实例)的唯一桥梁。 + * 2. 它是构建复杂应用(如中后台系统、多页官网)的核心导航骨架。 + * + * 【引擎实现指引】 + * + * 1. 隔离策略: + * - 设计模式 (design) 下,渲染引擎必须使用 `Memory Router`(或重写 `history.pushState`), + * 以防止用户在配置路由跳转时,导致整个设计器外壳发生灾难性的真刷新或沙盒逃逸。 + * - 运行/预览模式 (runtime/preview) 下,引擎切换为原生的 `Browser Router`。 + * 2. 嵌套挂载: + * 当遇到带有 `children` 的路由时,引擎会将其对应的 `pageId` 视为一个“父级布局骨架(Layout Skeleton)”。 + * 引擎在该骨架中必须寻找一个特殊的内置组件(如 `type: 'Outlet'` 或 `RouterView`), + * 以作为子路由对应 `Page` 的挂载插槽。 + */ +export interface Route extends Entity { + /** + * 路由路径 + * + * 定义了匹配当前路由的 URL 片段。 + * + * 支持静态路径(如 '/home'、'/dashboard')和参数路径(如 '/user/:id')。 + * + * 在根数组中,通常包含一个 '/' 作为默认首页。 + */ + path: string; + /** + * 路由重定向 + * + * 如果配置了此字段,当 URL 匹配到当前 path 时,引擎不渲染组件, + * 而是立即执行 301/302 级别的重定向,跳转到目标 path。 + * + * 常见于将根路径 '/' 重定向到具体的首页 '/home'。 + */ + redirect?: string; + /** + * 绑定的页面 ID + * + * 核心映射:当前路由命中时,引擎将去全局的 `Application.pages` 数组中 + * 查找对应的 Page 实例,并渲染该 Page 携带的庞大组件树。 + * + * 注:如果这是一个纯粹的重定向路由 (redirect存在) 或父级空壳路由,此项可为空。 + */ + pageId?: string; + /** + * 子路由 + * + * 构建复杂应用(如带侧边栏、顶部导航的后台框架)的核心。 + * + * 架构规约: + * + * 如果此数组不为空,则当前路由的 `pageId` 对应的 Page,不再是一个终端页面, + * 而是一个带有“插槽”的布局容器。它的内部组件树中必须包含一个 `type: 'Outlet'` 的组件, + * 用于动态渲染其子孙 Route 所绑定的 Page。 + */ + children?: Route[]; + /** + * 交互 + * + * 路由级生命周期与拦截器 + * + * 在进入或离开该路由时触发的高阶控制流。 + * + * 引擎实现指引: + * 1. 常用于“路由守卫 (Navigation Guards)”:例如在进入 '/admin' 前, + * 执行一个校验 Token 的 Action,若校验失败,则执行一个 Redirect Action 踢回登录页。 + * 2. 常见事件如:`onEnter` (进入前), `onLeave` (离开前)。 + * + * 如果当前路由无拦截逻辑,则必须为空数组 `[]`(而非 undefined)。 + */ + interactions: Interaction[]; +} diff --git a/src/schema/design-mode/variable/index.ts b/src/schema/design-mode/variable/index.ts new file mode 100644 index 0000000..4f2671e --- /dev/null +++ b/src/schema/design-mode/variable/index.ts @@ -0,0 +1 @@ +export * from './variable'; \ No newline at end of file diff --git a/src/schema/design-mode/variable/variable.ts b/src/schema/design-mode/variable/variable.ts new file mode 100644 index 0000000..b034265 --- /dev/null +++ b/src/schema/design-mode/variable/variable.ts @@ -0,0 +1,84 @@ +import type { Entity, JsonValue } from '../core'; +import type { Interaction } from '../interaction'; + +/** + * 全局变量 + * + * 【架构定位】: + * + * 1. 它是低代码平台的“响应式状态中枢(Reactive State Hub)”。 + * 2. 它是连接组件(Component)的输入/输出、驱动查询(Query)动态刷新的核心媒介。 + * + * 【引擎实现指引】: + * + * 渲染引擎启动时,必须扫描所有 Variable 节点,提取其 `key` 和初始 `value`, + * 并在内存中构建一个支持深度订阅的全局 Store(如 Zustand)。 + * + * 当任何 Action(如 `setVariable`)修改了该 Store 中的值时, + * 引擎的响应式机制会自动触发所有绑定了该变量的组件重绘或 Query 重新请求。 + */ +export interface Variable extends Entity { + /** + * 变量的键名,用于在表达式中作为变量名来引用 + * + * 核心架构边界: + * + * 与仅用于 UI 展示的 `label` (如:"当前选中用户") 不同,`key` 是供 JIT 编译器在代码块中求值的真实引用名。 + * (例如:在代码式的配置中通过 `context.variables.currentUser` 来访问它) + * + * 引擎约束: + * + * 1. 必须严格符合 JavaScript 变量命名规范(正则:/^[a-zA-Z_$][a-zA-Z0-9_$]*$/)。 + * 2. 在当前应用 (Application) 的所有 Variables 列表中必须全局唯一。 + * 3. 它是不可变的“主键”级数据,修改它可能导致海量已绑定的组件或查询报错(引用失效)。 + */ + key: string; + /** + * 变量的类型,运行时类型守卫 + * + * 决定了该变量在内存中允许存储的合法数据类型。 + * + * 引擎在执行 `setVariable` Action 时,必须先根据此字段进行运行时类型校验 (Runtime Type Checking), + * 以防止用户将一个对象强行赋给一个声明为 'number' 的变量,从而引发灾难性的级联崩溃。 + */ + type: 'string' | 'number' | 'boolean' | 'object' | 'array'; + /** + * 是否为可选变量,默认false + */ + optional: boolean; + /** + * 是否允许变量为null,默认false + */ + nullable: boolean; + /** + * 变量保存的值(初始值) + * + * 在 Schema 中,它仅仅代表应用首次加载时(或变量被重置时)的一个“静态快照”。 + * + * 引擎约束: + * + * 为确保该初始快照可以被安全地序列化存入数据库,它必须是一个合法的纯净 JSON 结构, + * 坚决抵制在此处使用 Function, Date 等无法被 `JSON.stringify` 转换的类型。 + */ + value: JsonValue; + /** + * 交互 + * + * 变量级生命周期与状态监听 + * + * 挂载于状态节点本身的高阶控制流。 + * + * 引擎实现指引: + * + * 这是低代码中最强大的“连锁反应触发器(Chain Reaction Trigger)”。 + * 当变量的值发生突变时触发(如 `event: 'onChange'`)。 + * + * 典型场景: + * + * 1. 表单联动:当 `selectedProvince` 变量改变时,触发 Action 去清空 `selectedCity` 变量。 + * 2. 异常监控:当 `errorCount` 变量突破阈值时,触发 Action 弹出一个警告对话框 (Modal)。 + * + * 如果该变量无任何监听逻辑,必须为一个显式的空数组 `[]`。 + */ + interactions: Interaction[]; +} diff --git a/temp.ts b/temp.ts deleted file mode 100644 index 16f44d7..0000000 --- a/temp.ts +++ /dev/null @@ -1,167 +0,0 @@ -// 大屏设计时的配置,可以被导出/导入 -export interface PageDesignModeConfig { - id: string; - name: string; - author: string; - createdTime: string; - updatedTime: string; - // 大屏样式 - style: { - width: number; - height: number; - background: string; - stretchMode: string; // 拉伸模式 - }; - // 组件配置 - // 可以支持组件包含子组件 (对于地图组件,它的子组件是逻辑层面的而并非大屏上的真实组件,因此不算作子组件) - // 例如可以设计对话框和悬浮窗组件,例如它们的type分别为 Modal 和 FloatingPanel, - // 当在物料区的图层编辑器中点击编辑该组件时,会打开一个新的屏幕设计器,物料区中加载子组件。 - // 将分组(group)也视为一种特殊的组件,统一在components数组中管理,从而实现统一的图层排序能力。 - components: Array<{ - id: string; // 组件id(唯一) - type: string; // 组件类型 - name: string; // 组件名称(可由用户定义) - parentId?: string; // 组件的父组件ID(如果存在),没有parentId的组件称为顶层组件 - // 当多个顶层组件成组时,组件的层级会被修改,也就是说分组并不是组件层级的参考系,组件的层级永远只与大屏和父组件有关 - zIndex?: number; // 组件在大屏/父组件中的层级 - children: Array; // 子组件id - // 设计时属性,运行时会忽略 - design: { - hidden: boolean; - locked: boolean; - }; - // 组件在画布上的布局和样式 - style: { - left: number; // 组件在画布上的X轴距离 - top: number; // 组件在画布上的Y轴距离 - width: number; // 组件宽度 - height: number; // 组件高度 - rotate: number; // 组件旋转角度 - visibility: string; // 组件可见性 - opacity: number; // 组件透明度 - pointerEvents: string; // 事件穿透 - // allowDrag: boolean; // 允许拖动 - // allowDrop: boolean; // 允许放置 - }; - props: { - [name: string]: any; - }; - data: any; // 组件数据 - // 数据源配置 - // 如何使用?提供编辑器给用户编写查询内容,允许使用mustache模板语法 - // 来引用全局变量,且支持路径访问。 - // 例如:现有全局变量 user ,内容是 { id: '1', name: 'zhangsan' }, - // 则可以使用 {{ user.id }} 来引用 user.id ,即 '1' - dataSource: { - // 支持多数据源查询 - queries: Array<{ - id: string; // 查询ID,例如 'A', 'B' - name: string; // 查询名称 - type: 'static' | 'variable' | 'api' | 'graphql'; - config: { - value?: any; - variableNames?: Array; // 关联的全局变量名称 - api?: { - url?: string; - method?: 'GET' | 'POST'; - headers?: Record; - query?: Record; - body?: Record; - }; - graphql?: { - query?: string; - variables?: Record; - }; - }; - cors?: boolean; // 服务器代理请求 - }>; - // 过滤器现在可以接收多个查询的结果,例如 { A: data, B: data } - filterIds: Array; - }; - // 组件交互 - interaction: { - // 下游交互事件 - events: Array<{ - id: string; - name: string; - // 判断条件 - conditions: Array<{ - // 每个判断条件可以由一系列过滤器组合而成 - filterIds: Array; - // 交互动作 - // 在配置事件动作时,先选择交互对象,再选择交互动作 - // 交互对象允许多选,多选后,交互动作的范围收缩到所选交互对象共有的动作 - // 如何判断一个动作是否为指定的多个交互对象所共有?动作名称与动作参数都相同 - // 为什么要添加beforeAction字段?计划支持分组向内部组件分派数据, - // 即组件允许实现一个名为【更新数据】的动作(区别于【请求数据】), - // 该动作接收参数并将其直接赋值给组件数据,为了规范数据,在设计时需要在动作执行前做一些预处理 - actions: { - components: Array<{ - id: string; // 下游交互组件ID - name: string; - filtersReturn: any; // 与判断条件的返回值相对应 - beforeAction: Array; // 交互前动作(过滤器ID) - actionName: string; // 交互动作名称 - params: any; // 交互动作参数 - }>; - variables: Array<{ - id: string; - name: string; - beforeAction: Array; // 交互前动作(过滤器ID) - actionName: string; // 交互动作名称 - params: any; // 交互动作参数 - }>; - }; - }>; - }>; - }; - }>; - // 全局变量 - // 如果一个全局变量的数据源是静态数据源,则只通过组件的事件去更新变量 - // 如果一个全局变量的数据源是API,则尽量不要由组件事件去更新这个变量 - // 进阶场景:全局变量可以定时请求数据,并配置“当数据更新时”事件, - // 对应的交互行为是使某个分组下的组件更新数据,这些组件也同时配置了数据源为该全局变量, - // 进而达到降低请求量的目的。 - // (在选择下游交互组件时,应当是“分组-组件”的级联选择) - variables: Array<{ - id: string; - name: string; - description?: string; - data: any; - dataSource: { - type: 'static' | 'api'; - // 数据源配置 - config: { - value?: any; - url?: string; - method?: 'GET' | 'POST'; - headers?: Record; - query?: Record; - body?: Record; - }; - cors: boolean; - filterIds: Array; - }; - }>; - // 过滤器 - filters: Array<{ - id: string; - name: string; - payload: string; // (data: { queryId: string; queryData: any }[], variables: any) => Promise - }>; -} - -export interface AppDesignModeConfig { - id: string; - name: string; - routes: Array<{ - path: string; - name?: string; - pageId: string; - children?: AppDesignModeConfig['routes']; - }>; -} - -// export interface PagePreviewModeConfig {} - -// export interface PagePublishModeConfig {}