Skip to main content
The twenty-sdk package provides defineEntity functions to declare your app’s data model. 你必须使用 export default defineEntity({...}),这样 SDK 才能检测到你的实体。 这些函数会在构建时校验你的配置,并提供 IDE 自动补全和类型安全。
文件组织由你决定。 实体检测基于 AST——无论文件位于何处,SDK 都能找到 export default defineEntity(...) 的调用。 按类型对文件分组(例如 logic-functions/roles/)只是代码组织的一种约定,并非必需。
角色封装了对你的工作空间对象与操作的权限。
restricted-company-role.ts
每个应用必须且只能有一个 defineApplication 调用,用于描述:
  • 应用的身份:标识符、显示名称和描述。
  • 权限:其函数和前端组件所使用的角色。
  • (可选)变量:以环境变量形式提供给函数的键值对。
  • (可选)安装前/安装后函数:在安装之前或之后运行的逻辑函数。
src/application-config.ts
备注:
  • universalIdentifier 字段是你拥有的确定性 ID。 只需生成一次,并在多次同步过程中保持稳定不变。
  • applicationVariables 会变成你的函数和前端组件可用的环境变量(例如,DEFAULT_RECIPIENT_NAME 可作为 process.env.DEFAULT_RECIPIENT_NAME 使用)。
  • defaultRoleUniversalIdentifier 必须引用使用 defineRole() 定义的角色(见上文)。
  • 在构建清单时会自动检测安装前/安装后函数——无需在 defineApplication() 中引用它们。

应用市场元数据

如果你计划发布你的应用,这些可选字段将控制你的应用在应用市场中的展示:

角色和权限

application-config.ts 中的 defaultRoleUniversalIdentifier 字段指定你的应用的逻辑函数和前端组件所使用的默认角色。 详见上文的 defineRole
  • 作为 TWENTY_APP_ACCESS_TOKEN 注入的运行时令牌来源于该角色。
  • 类型化客户端将受限于该角色授予的权限。
  • 遵循最小权限原则:创建一个仅包含你的函数所需权限的专用角色。
默认函数角色
当你使用脚手架创建新应用时,CLI 会创建一个默认角色文件:
src/roles/default-role.ts
该角色的 universalIdentifier 会在 application-config.ts 中被引用为 defaultRoleUniversalIdentifier
  • *.role.ts 定义该角色可以执行的操作。
  • application-config.ts 指向该角色,使你的函数继承其权限。
备注:
  • 从脚手架生成的角色开始,然后按照最小权限原则逐步收紧权限。
  • objectPermissionsfieldPermissions 替换为你的函数所需的对象/字段。
  • permissionFlags 控制对平台级能力的访问。 尽量保持最小化。
  • 查看一个可运行示例:hello-world/src/roles/function-role.ts
自定义对象同时描述工作空间中记录的架构与行为。 使用 defineObject() 以内置校验定义对象:
postCard.object.ts
关键点:
  • 使用 defineObject() 以获得内置校验和更好的 IDE 支持。
  • universalIdentifier 必须在各次部署间保持唯一且稳定。
  • 每个字段都需要 nametypelabel 以及其自身稳定的 universalIdentifier
  • fields 数组是可选的——你可以定义没有自定义字段的对象。
  • 你可以使用 yarn twenty add 脚手架创建新对象,它会引导你完成命名、字段和关系。
基础字段会自动创建。 当你定义自定义对象时,Twenty 会自动添加标准字段 例如 idnamecreatedAtupdatedAtcreatedByupdatedBydeletedAt。 你无需在 fields 数组中定义这些字段——只需添加你的自定义字段。 你可以通过在你的 fields 数组中定义一个同名字段来覆盖默认字段, 但不建议这样做。
使用 defineField() 向你不拥有的对象添加字段——例如标准的 Twenty 对象(Person、Company 等)。 或来自其他应用的对象。 与在 defineObject() 中的内联字段不同,独立字段需要一个 objectUniversalIdentifier 来指定它们要扩展的对象:
src/fields/company-loyalty-tier.field.ts
关键点:
  • objectUniversalIdentifier 用于标识目标对象。 对于标准对象,请使用从 twenty-sdk 导出的 STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS
  • defineObject() 中以内联方式定义字段时,你不需要 objectUniversalIdentifier——它会从父对象继承。
  • defineField() 是为非通过 defineObject() 创建的对象添加字段的唯一方式。
关系用于将对象彼此连接。 在 Twenty 中,关系始终是双向的——你需要定义两侧,每一侧都引用另一侧。关系有两种类型:

关系如何工作

每个关系都需要两个相互引用的字段:
  1. MANY_TO_ONE 侧——位于持有外键的对象上
  2. ONE_TO_MANY 侧——位于拥有集合的对象上
两个字段都使用 FieldType.RELATION,并通过 relationTargetFieldMetadataUniversalIdentifier 相互交叉引用。

示例:Post Card 拥有多个收件人

假设一个 PostCard 可以发送到多个 PostCardRecipient 记录。 每个收件人只隶属于一张 Post Card。步骤 1:在 PostCard 上定义 ONE_TO_MANY 侧(“一”侧):
src/fields/post-card-recipients-on-post-card.field.ts
步骤 2:在 PostCardRecipient 上定义 MANY_TO_ONE 侧(“多”侧——持有外键):
src/fields/post-card-on-post-card-recipient.field.ts
**循环导入:**两个关系字段相互引用彼此的 universalIdentifier。 为避免循环导入问题,请在各自文件中将字段 ID 作为具名常量导出,并在另一个文件中导入它们。 构建系统会在编译时解析这些引用。

与标准对象建立关系

要与内置的 Twenty 对象(Person、Company 等)建立关系,请使用 STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS
src/fields/person-on-self-hosting-user.field.ts

关系字段属性

在 defineObject 中内联关系字段

你也可以直接在 defineObject() 内定义关系字段。 在这种情况下,省略 objectUniversalIdentifier——它会从父对象继承:

Scaffolding entities with yarn twenty add

Instead of creating entity files by hand, you can use the interactive scaffolder:
This prompts you to pick an entity type and walks you through the required fields. It generates a ready-to-use file with a stable universalIdentifier and the correct defineEntity() call. You can also pass the entity type directly to skip the first prompt:

可用的实体类型

脚手架生成的内容

每种实体类型都有其自己的模板。 例如,yarn twenty add object 会询问:
  1. 名称(单数)——例如,invoice
  2. 名称(复数)——例如,invoices
  3. 标签(单数)——根据名称自动填充(例如,Invoice
  4. 标签(复数)——自动填充(例如,Invoices
  5. 创建视图和导航项?——如果你选择是,脚手架还会为新对象生成相应的视图和侧边栏链接。
其他实体类型的提示更简单——大多只会询问名称。 field 实体类型更为详细:它会询问字段名称、标签、类型(从所有可用字段类型列表中选择,如 TEXTNUMBERSELECTRELATION 等),以及目标对象的 universalIdentifier

自定义输出路径

使用 --path 标志将生成的文件放置在自定义位置: