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 — это детерминированные идентификаторы, которые принадлежат вам. Сгенерируйте их один раз и сохраняйте неизменными между синхронизациями.
  • applicationVariables становятся переменными окружения для ваших функций и фронтенд-компонентов (например, DEFAULT_RECIPIENT_NAME доступна как process.env.DEFAULT_RECIPIENT_NAME).
  • defaultRoleUniversalIdentifier должен ссылаться на роль, определённую с помощью defineRole() (см. выше).
  • Предустановочные и постустановочные функции обнаруживаются автоматически во время сборки манифеста — вам не нужно указывать их в defineApplication().

Метаданные маркетплейса

Если вы планируете опубликовать приложение, эти необязательные поля определяют, как оно отображается в маркетплейсе:

Роли и разрешения

Поле defaultRoleUniversalIdentifier в application-config.ts обозначает роль по умолчанию, используемую логическими функциями и фронтенд-компонентами вашего приложения. Подробности см. в defineRole выше.
  • Токен времени выполнения, подставляемый как TWENTY_APP_ACCESS_TOKEN, формируется из этой роли.
  • Типизированный клиент ограничен правами, предоставленными этой ролью.
  • Следуйте принципу наименьших привилегий: создайте отдельную роль только с теми правами, которые нужны вашим функциям.
Роль функции по умолчанию
Когда вы генерируете новое приложение, CLI создаёт файл роли по умолчанию:
src/roles/default-role.ts
Значение universalIdentifier этой роли указывается в application-config.ts как defaultRoleUniversalIdentifier:
  • *.role.ts определяет, что может делать роль.
  • application-config.ts указывает на эту роль, чтобы ваши функции наследовали её права.
Заметки:
  • Начните со сгенерированной роли, затем постепенно ограничивайте её, следуя принципу наименьших привилегий.
  • Замените objectPermissions и fieldPermissions на объекты и поля, которые действительно нужны вашим функциям.
  • permissionFlags управляют доступом к возможностям на уровне платформы. Сведите их к минимуму.
  • См. рабочий пример: hello-world/src/roles/function-role.ts.
Пользовательские объекты описывают как схему, так и поведение записей в вашем рабочем пространстве. Используйте defineObject() для определения объектов со встроенной валидацией:
postCard.object.ts
Основные моменты:
  • Используйте defineObject() для встроенной валидации и лучшей поддержки в IDE.
  • universalIdentifier должен быть уникальным и стабильным между развёртываниями.
  • Каждому полю требуются name, type, label и собственный стабильный universalIdentifier.
  • Массив fields необязателен — вы можете определять объекты без пользовательских полей.
  • Вы можете сгенерировать новые объекты с помощью yarn twenty add, который проведёт вас через выбор именования, полей и связей.
Базовые поля создаются автоматически. Когда вы определяете пользовательский объект, Twenty автоматически добавляет стандартные поля, такие как id, name, createdAt, updatedAt, createdBy, updatedBy и deletedAt. Вам не нужно определять их в массиве fields — добавляйте только свои пользовательские поля. Вы можете переопределить поля по умолчанию, определив поле с тем же именем в массиве fields, но это не рекомендуется.
Используйте defineField() для добавления полей к объектам, которые вам не принадлежат — например, к стандартным объектам Twenty (Person, Company и т. д.). или к объектам из других приложений. В отличие от встроенных полей в defineObject(), отдельные поля требуют objectUniversalIdentifier, чтобы указать, какой объект они расширяют:
src/fields/company-loyalty-tier.field.ts
Основные моменты:
  • objectUniversalIdentifier определяет целевой объект. Для стандартных объектов используйте STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, экспортируемые из twenty-sdk.
  • При определении полей непосредственно в defineObject() вам не нужен objectUniversalIdentifier — он наследуется от родительского объекта.
  • defineField() — единственный способ добавить поля к объектам, которые вы не создавали с помощью defineObject().
Отношения связывают объекты между собой. В Twenty отношения всегда двунаправленные — вы определяете обе стороны, и каждая сторона ссылается на другую.Существуют два типа отношений:

Как работают отношения

Каждое отношение требует двух полей, которые ссылаются друг на друга:
  1. Сторона MANY_TO_ONE — находится в объекте, который содержит внешний ключ
  2. Сторона ONE_TO_MANY — находится в объекте, которому принадлежит коллекция
Оба поля используют FieldType.RELATION и ссылаются друг на друга через relationTargetFieldMetadataUniversalIdentifier.

Пример: Почтовая открытка имеет много получателей

Предположим, PostCard может быть отправлен множству записей PostCardRecipient. Каждый получатель относится ровно к одной открытке.Шаг 1: Определите сторону ONE_TO_MANY на PostCard (сторона “one”):
src/fields/post-card-recipients-on-post-card.field.ts
Шаг 2: Определите сторону MANY_TO_ONE на PostCardRecipient (сторона “many” — содержит внешний ключ):
src/fields/post-card-on-post-card-recipient.field.ts
Циклические импорты: Оба поля отношений ссылаются на universalIdentifier друг друга. Чтобы избежать проблем с циклическими импортами, экспортируйте идентификаторы полей как именованные константы из каждого файла и импортируйте их в другом файле. Система сборки разрешает это на этапе компиляции.

Связывание со стандартными объектами

Чтобы создать отношение со встроенным объектом Twenty (Person, Company и т. д.), используйте STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS:
src/fields/person-on-self-hosting-user.field.ts

Свойства поля отношения

Встроенные поля отношений в defineObject

Вы также можете определять поля отношений непосредственно внутри defineObject(). В этом случае опустите objectUniversalIdentifier — он наследуется от родительского объекта:

Создание заготовок сущностей с помощью yarn twenty add

Вместо ручного создания файлов сущностей вы можете использовать интерактивный генератор:
Он предложит выбрать тип сущности и проведёт вас по обязательным полям. Он генерирует готовый к использованию файл со стабильным universalIdentifier и корректным вызовом defineEntity(). Вы также можете передать тип сущности напрямую, чтобы пропустить первый запрос:

Доступные типы сущностей

Что генерирует скэффолдер

У каждого типа сущности есть свой шаблон. Например, yarn twenty add object запрашивает:
  1. Имя (единственное число) — например, invoice
  2. Имя (множественное число) — например, invoices
  3. Метка (единственное число) — заполняется автоматически из имени (например, Invoice)
  4. Метка (множественное число) — заполняется автоматически (например, Invoices)
  5. Создать представление и пункт навигации? — если вы ответите «да», скэффолдер также сгенерирует соответствующее представление и ссылку в боковой панели для нового объекта.
У других типов сущностей подсказки проще — в большинстве случаев запрашивается только имя. Тип сущности field более детализирован: он запрашивает имя поля, метку, тип (из списка всех доступных типов полей, таких как TEXT, NUMBER, SELECT, RELATION и т. д.), а также universalIdentifier целевого объекта.

Пользовательский путь вывода

Используйте флаг --path, чтобы поместить сгенерированный файл в пользовательское расположение: