NocoBase RunJS 上下文实战:用 ctx.dataSource 访问数据源、数据表与字段元数据
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
在 NocoBase 的 RunJS 执行环境中,ctx.dataSource是连接“无代码界面”与“数据模型元数据”的关键入口:它是当前执行上下文绑定的数据源实例(DataSource),让你在已知当前数据源时直接获取数据表(Collection)、字段定义以及关联字段(Association),并管理当前数据源下的数据表配置。读完本文,你将掌握ctx.dataSource的全部常用属性与方法、collectionName.fieldPath路径语法的工作原理、它与ctx.dataSourceManager(跨数据源入口)的分工边界,并能结合 flow-engine 中的 DataSource 实现 理解其底层机制,从而在 RunJS 脚本中完成数据表枚举、动态校验、按路径取字段等实战场景。
一、什么是 ctx.dataSource
ctx.dataSource的类型是DataSource,表示当前 RunJS 执行上下文绑定的数据源实例。通常对应当前页/区块选中的数据源(如主库main)。
| 属性/成员 | 类型 | 说明 |
|---|---|---|
key | string | 数据源 key,如'main' |
name | string | 同 key |
displayName | string | 显示名称(支持 i18n) |
flowEngine | FlowEngine | 当前 FlowEngine 实例 |
从源码实现看,这些属性都是DataSource类上的 getter:key与name直接返回options.key;displayName会先通过flowEngine.translate(this.options.displayName, { ns: 'lm-collections' })做多语言翻译,翻译不到时才回退为 key。相关实现见 DataSource 类定义:
get displayName() { return this.flowEngine.translate(this.options.displayName, { ns: 'lm-collections' }) || this.key; } get key() { return this.options.key; } get name() { return this.options.key; }这说明displayName是可以被界面多语言覆盖的展示名,而key才是程序访问数据源时应当使用的稳定标识。
适用场景
| 场景 | 说明 |
|---|---|
| 单数据源操作 | 在已知当前数据源时,获取数据表、字段元数据 |
| 数据表管理 | 获取/添加/更新/删除当前数据源下的数据表 |
| 按路径获取字段 | 使用collectionName.fieldPath格式获取字段定义(支持关联路径) |
注意:
ctx.dataSource表示当前上下文的单一数据源;若要枚举或访问其他数据源,请使用 ctx.dataSourceManager。
二、类型定义与 API 全貌
DataSource在 RunJS 上下文中暴露的核心 API 如下:
dataSource: DataSource; class DataSource { constructor(options?: Record<string, any>); // 只读属性 get flowEngine(): FlowEngine; // 当前 FlowEngine 实例 get displayName(): string; // 显示名称(支持 i18n) get key(): string; // 数据源 key,如 'main' get name(): string; // 同 key // 数据表读取 getCollections(): Collection[]; // 获取所有数据表 getCollection(name: string): Collection | undefined; // 按名称获取数据表 getAssociation(associationName: string): CollectionField | undefined; // 获取关联字段(如 users.roles) // 数据表管理 addCollection(collection: Collection | CollectionOptions): void; updateCollection(newOptions: CollectionOptions): void; upsertCollection(options: CollectionOptions): Collection | undefined; upsertCollections(collections: CollectionOptions[], options?: { clearFields?: boolean }): void; removeCollection(name: string): void; clearCollections(): void; // 字段元数据 getCollectionField(fieldPath: string): CollectionField | undefined; }与类型声明一一对应的是 flow-engine 中 DataSource 的源码:这些方法大多是对内部CollectionManager的委托,例如getCollections()返回this.collectionManager.getCollections(),getCollection(name)返回this.collectionManager.getCollection(name)。这带来两个实践结论:
- 所有数据表状态都保存在
CollectionManager中。CollectionManager使用observable.shallow<Map<string, Collection>>存放数据表(见 CollectionManager 构造函数),对数据表进行增删改时会自动重置继承链缓存(resetCaches),因此在 RunJS 中调用upsertCollections等写方法后,后续读取的元数据立即可见且保持响应式。 - 旧拼写
getAssocation已标记废弃:源码中保留了getAssocation(associationName)作为兼容别名并转发到getAssociation(见 getAssociation 实现)。新脚本请统一使用getAssociation。
常用方法速查
| 方法 | 说明 |
|---|---|
getCollections() | 获取当前数据源下所有数据表(已排序、过滤隐藏) |
getCollection(name) | 按名称获取数据表;name可为collectionName.fieldName获取关联目标数据表 |
getAssociation(associationName) | 按collectionName.fieldName获取关联字段定义 |
getCollectionField(fieldPath) | 按collectionName.fieldPath获取字段定义,支持关联路径如users.profile.avatar |
三、getCollectionField:路径解析的工作原理
getCollectionField(fieldPath)的入参格式为collectionName.fieldPath:第一段为数据表名,后续为字段路径(支持关联,如user.name)。其解析逻辑在源码中非常清晰(getCollectionField 实现):
getCollectionField(fieldPath: string) { const [collectionName, ...otherKeys] = fieldPath.split('.'); const fieldName = otherKeys.join('.'); const collection = this.getCollection(collectionName); if (!collection) { return; } const field = collection.getFieldByPath(fieldName); if (!field) { return; } return field; }可以看出三步解析过程:
- 按
.拆分,第一段解析出collectionName,其余拼回为字段路径; - 通过
this.getCollection(collectionName)拿到数据表——如果数据表不存在则直接返回undefined; - 调用数据表的
getFieldByPath(fieldName)沿字段路径逐段查找,中途任一段不存在都会返回undefined。
因此调用方必须对返回结果做空值判断。与它相对的是跨数据源版本 DataSourceManager.getCollectionField:先拆出第一段作为数据源 key 取出对应数据源,再把剩余路径交给该数据源的getCollectionField处理,即main.users.profile.avatar等价于“在 main 数据源内执行getCollectionField('users.profile.avatar')”。
四、实战示例
以下示例继承自官方文档,均假设 RunJS 上下文已绑定当前数据源。
4.1 获取数据表及字段
// 获取所有数据表 const collections = ctx.dataSource.getCollections(); // 按名称获取数据表 const users = ctx.dataSource.getCollection('users'); const primaryKey = users?.filterTargetKey ?? 'id'; // 按「数据表.字段路径」获取字段定义(支持关联) const field = ctx.dataSource.getCollectionField('users.profile.avatar'); const userNameField = ctx.dataSource.getCollectionField('orders.createdBy.name');要点:filterTargetKey是数据表的主键字段名(filterTargetKey在 CollectionOptions 类型 中定义为string | Array<string>),未配置时应回退到默认的'id'。
4.2 获取关联字段
// 按 collectionName.fieldName 获取关联字段定义 const rolesField = ctx.dataSource.getAssociation('users.roles'); if (rolesField?.isAssociationField()) { const targetCol = rolesField.targetCollection; // 按目标数据表结构处理 }getAssociation('users.roles')返回的是roles这个关联字段的定义本身,targetCollection则指向关联指向的目标数据表,便于你按目标表结构继续做元数据操作。
4.3 遍历数据表做动态处理
const collections = ctx.dataSource.getCollections(); for (const col of collections) { const fields = col.getFields(); const requiredFields = fields.filter((f) => f.options?.required); // ... }从 ICollection 接口定义 可以看到,数据表对象提供getFields()、getField(name)、getFieldByField(field)、setField、removeField、updateOptions等能力,且每个字段都带有options: FieldOptions。FieldOptions中除name、type、interface外,还包含primaryKey、unique、allowNull、autoIncrement、defaultValue等数据库语义字段(见 FieldOptions 类型),这些正是遍历数据表做“必填项统计”“主键推断”等动态处理时的判断依据。
4.4 根据字段元数据做校验或动态 UI
const field = ctx.dataSource.getCollectionField('users.status'); if (field) { const options = field.enum ?? []; const operators = field.getFilterOperators(); // 根据 interface、enum、validation 等做 UI 或校验 }典型应用:读取field.interface决定渲染哪种输入控件,读取field.enum生成下拉选项,调用field.getFilterOperators()获取该字段支持的筛选运算符集合,从而在 RunJS 驱动的表单/筛选器中自动生成校验规则。
五、ctx.dataSource 与 ctx.dataSourceManager 的分工
两者同属 RunJS 上下文,但职责边界不同,选择错误的入口是常见的脚本错误来源:
| 需求 | 推荐用法 |
|---|---|
| 当前上下文绑定的单一数据源 | ctx.dataSource |
| 所有数据源入口 | ctx.dataSourceManager |
| 当前数据源内获取数据表 | ctx.dataSource.getCollection(name) |
| 跨数据源获取数据表 | ctx.dataSourceManager.getCollection(dataSourceKey, collectionName) |
| 当前数据源内获取字段 | ctx.dataSource.getCollectionField('users.profile.avatar') |
| 跨数据源获取字段 | ctx.dataSourceManager.getCollectionField('main.users.profile.avatar') |
从源码结构看,DataSourceManager内部维护dataSources: Map<string, DataSource>这一注册表(见 DataSourceManager 类),并提供addDataSource/upsertDataSource/removeDataSource/clearDataSources等管理方法。两点值得注意:
addDataSource在 key 已存在时会直接抛出DataSource with name ${ds.key} already exists异常,需要“覆盖或新增”语义时应改用upsertDataSource;getDataSource(key)在数据源不存在时返回undefined,使用前建议做空值判断;- 数据源还支持通过
registerLoader/ensureLoaded异步加载元数据并维护status(loading/loaded/loading-failed等)状态,DataSource.status与ErrorMessagegetter 也对应暴露了这些状态(见 status/errorMessage getter)。这意味着在数据源尚未加载完成时读取元数据可能拿到空结果,编写健壮脚本时应留意数据源状态。
六、注意事项与健壮性建议
- 路径格式:
getCollectionField(fieldPath)的路径格式为collectionName.fieldPath,第一段为数据表名,后续为字段路径(支持关联,如user.name)。 - 关联目标数据表:
getCollection(name)支持collectionName.fieldName形式,返回关联字段的目标数据表。 - 可能为 undefined:
ctx.dataSource在 RunJS 上下文中通常由当前区块/页面的数据源决定;若上下文无绑定数据源,可能为undefined,使用前建议做空值判断。同理,getCollection、getCollectionField、getAssociation在找不到对象时都会返回undefined,链式访问时应使用?.与回退值。
七、延伸阅读
- ctx.dataSourceManager:数据源管理器,管理所有数据源
- ctx.collection:当前上下文关联的数据表
- ctx.collectionField:当前字段的数据表字段定义
- DataSource 源码实现:RunJS 所用
DataSource/DataSourceManager的完整实现 - 数据源核心包:服务端
DataSource抽象类,展示ctx.dataSource在请求中间件中的注入方式(middleware 注入逻辑)
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考