From cd73088be632d151cbc32a250eec906117bf897f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:57:27 +0200 Subject: [PATCH] i18n - docs translations (#19925) Created by Github action Co-authored-by: github-actions --- packages/twenty-docs/docs.json | 94 +- .../backend-development/server-commands.mdx | 1 + .../capabilities/bug-and-requests.mdx | 1 + .../best-practices-front.mdx | 1 + .../folder-architecture-front.mdx | 1 + .../frontend-commands.mdx | 1 + .../frontend-development/style-guide.mdx | 1 + .../contribute/capabilities/local-setup.mdx | 1 + .../l/ar/developers/contribute/commands.mdx | 77 + .../ar/developers/contribute/style-guide.mdx | 176 ++ .../l/ar/developers/extend/api.mdx | 142 +- .../l/ar/developers/extend/apps/building.mdx | 2134 +--------------- .../extend/apps/cli-and-testing.mdx | 434 ++++ .../ar/developers/extend/apps/data-model.mdx | 494 ++++ .../extend/apps/front-components.mdx | 419 ++++ .../extend/apps/getting-started.mdx | 5 +- .../l/ar/developers/extend/apps/layout.mdx | 131 + .../extend/apps/logic-functions.mdx | 559 +++++ .../ar/developers/extend/apps/publishing.mdx | 5 +- .../extend/apps/skills-and-agents.mdx | 69 + .../l/ar/developers/extend/oauth.mdx | 189 ++ .../l/ar/developers/extend/webhooks.mdx | 5 +- .../l/ar/developers/introduction.mdx | 27 +- .../capabilities/cloud-providers.mdx | 1 + .../self-host/capabilities/docker-compose.mdx | 3 +- .../self-host/capabilities/setup.mdx | 1 + .../capabilities/troubleshooting.mdx | 1 + .../self-host/capabilities/upgrade-guide.mdx | 361 +-- packages/twenty-docs/l/ar/navigation.json | 131 +- .../l/ar/twenty-ui/display/app-tooltip.mdx | 1 + .../l/ar/twenty-ui/display/checkmark.mdx | 1 + .../l/ar/twenty-ui/display/icons.mdx | 1 + .../l/ar/twenty-ui/display/soon-pill.mdx | 1 - .../l/ar/twenty-ui/display/tag.mdx | 2 +- .../l/ar/twenty-ui/input/buttons.mdx | 1 + .../l/ar/twenty-ui/input/checkbox.mdx | 1 + .../l/ar/twenty-ui/input/color-scheme.mdx | 1 + .../l/ar/twenty-ui/input/radio.mdx | 1 + .../l/ar/twenty-ui/input/toggle.mdx | 2 +- .../l/ar/twenty-ui/introduction.mdx | 1 + .../twenty-docs/l/ar/twenty-ui/navigation.mdx | 1 + .../l/ar/twenty-ui/navigation/links.mdx | 1 + .../l/ar/twenty-ui/navigation/menu-item.mdx | 2 +- .../twenty-ui/navigation/navigation-bar.mdx | 2 +- .../l/ar/twenty-ui/progress-bar.mdx | 1 - .../l/ar/user-guide/billing/overview.mdx | 1 - .../user-guide/calendar-emails/overview.mdx | 1 - .../l/ar/user-guide/dashboards/overview.mdx | 1 - .../ar/user-guide/data-migration/overview.mdx | 1 - .../l/ar/user-guide/data-model/overview.mdx | 1 - .../l/ar/user-guide/introduction.mdx | 13 +- .../layout/capabilities/navigation.mdx | 32 + .../layout/capabilities/record-pages.mdx | 51 + .../l/ar/user-guide/layout/overview.mdx | 45 + .../permissions-access/overview.mdx | 1 - .../l/ar/user-guide/settings/overview.mdx | 1 - .../user-guide/views-pipelines/overview.mdx | 1 - .../l/ar/user-guide/workflows/overview.mdx | 1 - .../backend-development/server-commands.mdx | 1 + .../capabilities/bug-and-requests.mdx | 1 + .../best-practices-front.mdx | 1 + .../folder-architecture-front.mdx | 1 + .../frontend-commands.mdx | 1 + .../frontend-development/style-guide.mdx | 1 + .../contribute/capabilities/local-setup.mdx | 1 + .../l/cs/developers/contribute/commands.mdx | 77 + .../cs/developers/contribute/style-guide.mdx | 176 ++ .../l/cs/developers/extend/api.mdx | 142 +- .../l/cs/developers/extend/apps/building.mdx | 2135 +---------------- .../extend/apps/cli-and-testing.mdx | 434 ++++ .../cs/developers/extend/apps/data-model.mdx | 494 ++++ .../extend/apps/front-components.mdx | 419 ++++ .../extend/apps/getting-started.mdx | 5 +- .../l/cs/developers/extend/apps/layout.mdx | 131 + .../extend/apps/logic-functions.mdx | 560 +++++ .../cs/developers/extend/apps/publishing.mdx | 5 +- .../extend/apps/skills-and-agents.mdx | 69 + .../l/cs/developers/extend/oauth.mdx | 189 ++ .../l/cs/developers/extend/webhooks.mdx | 5 +- .../l/cs/developers/introduction.mdx | 27 +- .../capabilities/cloud-providers.mdx | 1 + .../self-host/capabilities/docker-compose.mdx | 3 +- .../self-host/capabilities/setup.mdx | 1 + .../capabilities/troubleshooting.mdx | 1 + .../self-host/capabilities/upgrade-guide.mdx | 361 +-- packages/twenty-docs/l/cs/navigation.json | 131 +- .../l/cs/twenty-ui/display/app-tooltip.mdx | 1 + .../l/cs/twenty-ui/display/checkmark.mdx | 1 + .../l/cs/twenty-ui/display/icons.mdx | 1 + .../l/cs/twenty-ui/display/soon-pill.mdx | 1 - .../l/cs/twenty-ui/display/tag.mdx | 2 +- .../l/cs/twenty-ui/input/buttons.mdx | 1 + .../l/cs/twenty-ui/input/checkbox.mdx | 1 + .../l/cs/twenty-ui/input/color-scheme.mdx | 1 + .../l/cs/twenty-ui/input/radio.mdx | 1 + .../l/cs/twenty-ui/input/toggle.mdx | 2 +- .../l/cs/twenty-ui/introduction.mdx | 1 + .../twenty-docs/l/cs/twenty-ui/navigation.mdx | 1 + .../l/cs/twenty-ui/navigation/links.mdx | 1 + .../l/cs/twenty-ui/navigation/menu-item.mdx | 2 +- .../twenty-ui/navigation/navigation-bar.mdx | 2 +- .../l/cs/twenty-ui/progress-bar.mdx | 1 - .../l/cs/user-guide/billing/overview.mdx | 1 - .../user-guide/calendar-emails/overview.mdx | 1 - .../l/cs/user-guide/dashboards/overview.mdx | 1 - .../cs/user-guide/data-migration/overview.mdx | 1 - .../l/cs/user-guide/data-model/overview.mdx | 1 - .../l/cs/user-guide/introduction.mdx | 13 +- .../layout/capabilities/navigation.mdx | 32 + .../layout/capabilities/record-pages.mdx | 51 + .../l/cs/user-guide/layout/overview.mdx | 45 + .../permissions-access/overview.mdx | 1 - .../l/cs/user-guide/settings/overview.mdx | 1 - .../user-guide/views-pipelines/overview.mdx | 1 - .../l/cs/user-guide/workflows/overview.mdx | 1 - .../backend-development/server-commands.mdx | 1 + .../capabilities/bug-and-requests.mdx | 1 + .../best-practices-front.mdx | 1 + .../folder-architecture-front.mdx | 1 + .../frontend-commands.mdx | 1 + .../frontend-development/style-guide.mdx | 1 + .../contribute/capabilities/local-setup.mdx | 1 + .../l/de/developers/contribute/commands.mdx | 77 + .../de/developers/contribute/style-guide.mdx | 176 ++ .../l/de/developers/extend/api.mdx | 142 +- .../l/de/developers/extend/apps/building.mdx | 2134 +--------------- .../extend/apps/cli-and-testing.mdx | 434 ++++ .../de/developers/extend/apps/data-model.mdx | 494 ++++ .../extend/apps/front-components.mdx | 419 ++++ .../extend/apps/getting-started.mdx | 5 +- .../l/de/developers/extend/apps/layout.mdx | 131 + .../extend/apps/logic-functions.mdx | 559 +++++ .../de/developers/extend/apps/publishing.mdx | 5 +- .../extend/apps/skills-and-agents.mdx | 69 + .../l/de/developers/extend/oauth.mdx | 189 ++ .../l/de/developers/extend/webhooks.mdx | 5 +- .../l/de/developers/introduction.mdx | 27 +- .../capabilities/cloud-providers.mdx | 1 + .../self-host/capabilities/docker-compose.mdx | 3 +- .../self-host/capabilities/setup.mdx | 1 + .../capabilities/troubleshooting.mdx | 1 + .../self-host/capabilities/upgrade-guide.mdx | 361 +-- packages/twenty-docs/l/de/navigation.json | 131 +- .../l/de/twenty-ui/display/app-tooltip.mdx | 1 + .../l/de/twenty-ui/display/checkmark.mdx | 1 + .../l/de/twenty-ui/display/icons.mdx | 1 + .../l/de/twenty-ui/display/soon-pill.mdx | 1 - .../l/de/twenty-ui/display/tag.mdx | 2 +- .../l/de/twenty-ui/input/buttons.mdx | 1 + .../l/de/twenty-ui/input/checkbox.mdx | 1 + .../l/de/twenty-ui/input/color-scheme.mdx | 1 + .../l/de/twenty-ui/input/radio.mdx | 1 + .../l/de/twenty-ui/input/toggle.mdx | 2 +- .../l/de/twenty-ui/introduction.mdx | 1 + .../twenty-docs/l/de/twenty-ui/navigation.mdx | 1 + .../l/de/twenty-ui/navigation/links.mdx | 1 + .../l/de/twenty-ui/navigation/menu-item.mdx | 2 +- .../twenty-ui/navigation/navigation-bar.mdx | 2 +- .../l/de/twenty-ui/progress-bar.mdx | 1 - .../l/de/user-guide/billing/overview.mdx | 1 - .../user-guide/calendar-emails/overview.mdx | 1 - .../l/de/user-guide/dashboards/overview.mdx | 1 - .../de/user-guide/data-migration/overview.mdx | 1 - .../l/de/user-guide/data-model/overview.mdx | 1 - .../l/de/user-guide/introduction.mdx | 13 +- .../layout/capabilities/navigation.mdx | 32 + .../layout/capabilities/record-pages.mdx | 51 + .../l/de/user-guide/layout/overview.mdx | 45 + .../permissions-access/overview.mdx | 1 - .../l/de/user-guide/settings/overview.mdx | 1 - .../user-guide/views-pipelines/overview.mdx | 1 - .../l/de/user-guide/workflows/overview.mdx | 1 - .../backend-development/server-commands.mdx | 1 + .../capabilities/bug-and-requests.mdx | 1 + .../best-practices-front.mdx | 1 + .../folder-architecture-front.mdx | 1 + .../frontend-commands.mdx | 1 + .../frontend-development/style-guide.mdx | 1 + .../contribute/capabilities/local-setup.mdx | 1 + .../l/it/developers/contribute/commands.mdx | 77 + .../it/developers/contribute/style-guide.mdx | 176 ++ .../l/it/developers/extend/api.mdx | 142 +- .../l/it/developers/extend/apps/building.mdx | 2134 +--------------- .../extend/apps/cli-and-testing.mdx | 434 ++++ .../it/developers/extend/apps/data-model.mdx | 494 ++++ .../extend/apps/front-components.mdx | 419 ++++ .../extend/apps/getting-started.mdx | 5 +- .../l/it/developers/extend/apps/layout.mdx | 131 + .../extend/apps/logic-functions.mdx | 559 +++++ .../it/developers/extend/apps/publishing.mdx | 5 +- .../extend/apps/skills-and-agents.mdx | 69 + .../l/it/developers/extend/oauth.mdx | 189 ++ .../l/it/developers/extend/webhooks.mdx | 5 +- .../l/it/developers/introduction.mdx | 27 +- .../capabilities/cloud-providers.mdx | 1 + .../self-host/capabilities/docker-compose.mdx | 3 +- .../self-host/capabilities/setup.mdx | 1 + .../capabilities/troubleshooting.mdx | 1 + .../self-host/capabilities/upgrade-guide.mdx | 361 +-- packages/twenty-docs/l/it/navigation.json | 131 +- .../l/it/twenty-ui/display/app-tooltip.mdx | 1 + .../l/it/twenty-ui/display/checkmark.mdx | 1 + .../l/it/twenty-ui/display/icons.mdx | 1 + .../l/it/twenty-ui/display/soon-pill.mdx | 1 - .../l/it/twenty-ui/display/tag.mdx | 2 +- .../l/it/twenty-ui/input/buttons.mdx | 1 + .../l/it/twenty-ui/input/checkbox.mdx | 1 + .../l/it/twenty-ui/input/color-scheme.mdx | 1 + .../l/it/twenty-ui/input/radio.mdx | 1 + .../l/it/twenty-ui/input/toggle.mdx | 2 +- .../l/it/twenty-ui/introduction.mdx | 1 + .../twenty-docs/l/it/twenty-ui/navigation.mdx | 1 + .../l/it/twenty-ui/navigation/links.mdx | 1 + .../l/it/twenty-ui/navigation/menu-item.mdx | 2 +- .../twenty-ui/navigation/navigation-bar.mdx | 2 +- .../l/it/twenty-ui/progress-bar.mdx | 1 - .../l/it/user-guide/billing/overview.mdx | 1 - .../user-guide/calendar-emails/overview.mdx | 1 - .../l/it/user-guide/dashboards/overview.mdx | 1 - .../it/user-guide/data-migration/overview.mdx | 1 - .../l/it/user-guide/data-model/overview.mdx | 1 - .../l/it/user-guide/introduction.mdx | 13 +- .../layout/capabilities/navigation.mdx | 32 + .../layout/capabilities/record-pages.mdx | 51 + .../l/it/user-guide/layout/overview.mdx | 45 + .../permissions-access/overview.mdx | 1 - .../l/it/user-guide/settings/overview.mdx | 1 - .../user-guide/views-pipelines/overview.mdx | 1 - .../l/it/user-guide/workflows/overview.mdx | 1 - .../backend-development/server-commands.mdx | 1 + .../capabilities/bug-and-requests.mdx | 1 + .../best-practices-front.mdx | 1 + .../folder-architecture-front.mdx | 1 + .../frontend-commands.mdx | 1 + .../frontend-development/style-guide.mdx | 1 + .../contribute/capabilities/local-setup.mdx | 1 + .../l/pt/developers/contribute/commands.mdx | 77 + .../pt/developers/contribute/style-guide.mdx | 176 ++ .../l/pt/developers/extend/api.mdx | 142 +- .../l/pt/developers/extend/apps/building.mdx | 2134 +--------------- .../extend/apps/cli-and-testing.mdx | 434 ++++ .../pt/developers/extend/apps/data-model.mdx | 494 ++++ .../extend/apps/front-components.mdx | 419 ++++ .../extend/apps/getting-started.mdx | 5 +- .../l/pt/developers/extend/apps/layout.mdx | 131 + .../extend/apps/logic-functions.mdx | 559 +++++ .../pt/developers/extend/apps/publishing.mdx | 5 +- .../extend/apps/skills-and-agents.mdx | 69 + .../l/pt/developers/extend/oauth.mdx | 189 ++ .../l/pt/developers/extend/webhooks.mdx | 5 +- .../l/pt/developers/introduction.mdx | 27 +- .../capabilities/cloud-providers.mdx | 1 + .../self-host/capabilities/docker-compose.mdx | 3 +- .../self-host/capabilities/setup.mdx | 1 + .../capabilities/troubleshooting.mdx | 1 + .../self-host/capabilities/upgrade-guide.mdx | 361 +-- packages/twenty-docs/l/pt/navigation.json | 131 +- .../l/pt/twenty-ui/display/app-tooltip.mdx | 1 + .../l/pt/twenty-ui/display/checkmark.mdx | 1 + .../l/pt/twenty-ui/display/icons.mdx | 1 + .../l/pt/twenty-ui/display/soon-pill.mdx | 1 - .../l/pt/twenty-ui/display/tag.mdx | 2 +- .../l/pt/twenty-ui/input/buttons.mdx | 1 + .../l/pt/twenty-ui/input/checkbox.mdx | 1 + .../l/pt/twenty-ui/input/color-scheme.mdx | 1 + .../l/pt/twenty-ui/input/radio.mdx | 1 + .../l/pt/twenty-ui/input/toggle.mdx | 2 +- .../l/pt/twenty-ui/introduction.mdx | 1 + .../twenty-docs/l/pt/twenty-ui/navigation.mdx | 1 + .../l/pt/twenty-ui/navigation/links.mdx | 1 + .../l/pt/twenty-ui/navigation/menu-item.mdx | 2 +- .../twenty-ui/navigation/navigation-bar.mdx | 2 +- .../l/pt/twenty-ui/progress-bar.mdx | 1 - .../l/pt/user-guide/billing/overview.mdx | 1 - .../user-guide/calendar-emails/overview.mdx | 1 - .../l/pt/user-guide/dashboards/overview.mdx | 1 - .../pt/user-guide/data-migration/overview.mdx | 1 - .../l/pt/user-guide/data-model/overview.mdx | 1 - .../l/pt/user-guide/introduction.mdx | 13 +- .../layout/capabilities/navigation.mdx | 32 + .../layout/capabilities/record-pages.mdx | 51 + .../l/pt/user-guide/layout/overview.mdx | 45 + .../permissions-access/overview.mdx | 1 - .../l/pt/user-guide/settings/overview.mdx | 1 - .../user-guide/views-pipelines/overview.mdx | 1 - .../l/pt/user-guide/workflows/overview.mdx | 1 - .../backend-development/server-commands.mdx | 1 + .../capabilities/bug-and-requests.mdx | 1 + .../best-practices-front.mdx | 1 + .../folder-architecture-front.mdx | 1 + .../frontend-commands.mdx | 1 + .../frontend-development/style-guide.mdx | 1 + .../contribute/capabilities/local-setup.mdx | 1 + .../l/ru/developers/contribute/commands.mdx | 77 + .../ru/developers/contribute/style-guide.mdx | 176 ++ .../l/ru/developers/extend/api.mdx | 142 +- .../l/ru/developers/extend/apps/building.mdx | 2134 +--------------- .../extend/apps/cli-and-testing.mdx | 434 ++++ .../ru/developers/extend/apps/data-model.mdx | 494 ++++ .../extend/apps/front-components.mdx | 419 ++++ .../extend/apps/getting-started.mdx | 5 +- .../l/ru/developers/extend/apps/layout.mdx | 131 + .../extend/apps/logic-functions.mdx | 559 +++++ .../ru/developers/extend/apps/publishing.mdx | 5 +- .../extend/apps/skills-and-agents.mdx | 69 + .../l/ru/developers/extend/oauth.mdx | 189 ++ .../l/ru/developers/extend/webhooks.mdx | 5 +- .../l/ru/developers/introduction.mdx | 27 +- .../capabilities/cloud-providers.mdx | 1 + .../self-host/capabilities/docker-compose.mdx | 3 +- .../self-host/capabilities/setup.mdx | 1 + .../capabilities/troubleshooting.mdx | 1 + .../self-host/capabilities/upgrade-guide.mdx | 361 +-- packages/twenty-docs/l/ru/navigation.json | 131 +- .../l/ru/twenty-ui/display/app-tooltip.mdx | 1 + .../l/ru/twenty-ui/display/checkmark.mdx | 1 + .../l/ru/twenty-ui/display/icons.mdx | 1 + .../l/ru/twenty-ui/display/soon-pill.mdx | 1 - .../l/ru/twenty-ui/display/tag.mdx | 2 +- .../l/ru/twenty-ui/input/buttons.mdx | 1 + .../l/ru/twenty-ui/input/checkbox.mdx | 1 + .../l/ru/twenty-ui/input/color-scheme.mdx | 1 + .../l/ru/twenty-ui/input/radio.mdx | 1 + .../l/ru/twenty-ui/input/toggle.mdx | 2 +- .../l/ru/twenty-ui/introduction.mdx | 1 + .../twenty-docs/l/ru/twenty-ui/navigation.mdx | 1 + .../l/ru/twenty-ui/navigation/links.mdx | 1 + .../l/ru/twenty-ui/navigation/menu-item.mdx | 2 +- .../twenty-ui/navigation/navigation-bar.mdx | 2 +- .../l/ru/twenty-ui/progress-bar.mdx | 1 - .../l/ru/user-guide/billing/overview.mdx | 1 - .../user-guide/calendar-emails/overview.mdx | 1 - .../l/ru/user-guide/dashboards/overview.mdx | 1 - .../ru/user-guide/data-migration/overview.mdx | 1 - .../l/ru/user-guide/data-model/overview.mdx | 1 - .../l/ru/user-guide/introduction.mdx | 13 +- .../layout/capabilities/navigation.mdx | 32 + .../layout/capabilities/record-pages.mdx | 51 + .../l/ru/user-guide/layout/overview.mdx | 45 + .../permissions-access/overview.mdx | 1 - .../l/ru/user-guide/settings/overview.mdx | 1 - .../user-guide/views-pipelines/overview.mdx | 1 - .../l/ru/user-guide/workflows/overview.mdx | 1 - .../backend-development/server-commands.mdx | 1 + .../capabilities/bug-and-requests.mdx | 1 + .../best-practices-front.mdx | 1 + .../folder-architecture-front.mdx | 1 + .../frontend-commands.mdx | 1 + .../frontend-development/style-guide.mdx | 1 + .../contribute/capabilities/local-setup.mdx | 1 + .../l/tr/developers/contribute/commands.mdx | 77 + .../tr/developers/contribute/style-guide.mdx | 176 ++ .../l/tr/developers/extend/api.mdx | 142 +- .../l/tr/developers/extend/apps/building.mdx | 2135 +---------------- .../extend/apps/cli-and-testing.mdx | 434 ++++ .../tr/developers/extend/apps/data-model.mdx | 494 ++++ .../extend/apps/front-components.mdx | 419 ++++ .../extend/apps/getting-started.mdx | 5 +- .../l/tr/developers/extend/apps/layout.mdx | 131 + .../extend/apps/logic-functions.mdx | 560 +++++ .../tr/developers/extend/apps/publishing.mdx | 5 +- .../extend/apps/skills-and-agents.mdx | 69 + .../l/tr/developers/extend/oauth.mdx | 189 ++ .../l/tr/developers/extend/webhooks.mdx | 5 +- .../l/tr/developers/introduction.mdx | 23 +- .../capabilities/cloud-providers.mdx | 1 + .../self-host/capabilities/docker-compose.mdx | 3 +- .../self-host/capabilities/setup.mdx | 1 + .../capabilities/troubleshooting.mdx | 1 + .../self-host/capabilities/upgrade-guide.mdx | 354 +-- packages/twenty-docs/l/tr/navigation.json | 131 +- .../l/tr/twenty-ui/display/app-tooltip.mdx | 1 + .../l/tr/twenty-ui/display/checkmark.mdx | 1 + .../l/tr/twenty-ui/display/icons.mdx | 1 + .../l/tr/twenty-ui/display/soon-pill.mdx | 1 - .../l/tr/twenty-ui/display/tag.mdx | 2 +- .../l/tr/twenty-ui/input/buttons.mdx | 1 + .../l/tr/twenty-ui/input/checkbox.mdx | 1 + .../l/tr/twenty-ui/input/color-scheme.mdx | 1 + .../l/tr/twenty-ui/input/radio.mdx | 1 + .../l/tr/twenty-ui/input/toggle.mdx | 2 +- .../l/tr/twenty-ui/introduction.mdx | 1 + .../twenty-docs/l/tr/twenty-ui/navigation.mdx | 1 + .../l/tr/twenty-ui/navigation/links.mdx | 1 + .../l/tr/twenty-ui/navigation/menu-item.mdx | 2 +- .../twenty-ui/navigation/navigation-bar.mdx | 2 +- .../l/tr/twenty-ui/progress-bar.mdx | 1 - .../l/tr/user-guide/billing/overview.mdx | 1 - .../user-guide/calendar-emails/overview.mdx | 1 - .../l/tr/user-guide/dashboards/overview.mdx | 1 - .../tr/user-guide/data-migration/overview.mdx | 1 - .../l/tr/user-guide/data-model/overview.mdx | 1 - .../l/tr/user-guide/introduction.mdx | 13 +- .../layout/capabilities/navigation.mdx | 32 + .../layout/capabilities/record-pages.mdx | 51 + .../l/tr/user-guide/layout/overview.mdx | 45 + .../permissions-access/overview.mdx | 1 - .../l/tr/user-guide/settings/overview.mdx | 1 - .../user-guide/views-pipelines/overview.mdx | 1 - .../l/tr/user-guide/workflows/overview.mdx | 1 - .../backend-development/server-commands.mdx | 8 + .../capabilities/bug-and-requests.mdx | 1 + .../best-practices-front.mdx | 1 + .../folder-architecture-front.mdx | 1 + .../frontend-commands.mdx | 1 + .../frontend-development/style-guide.mdx | 1 + .../contribute/capabilities/local-setup.mdx | 1 + .../l/zh/developers/contribute/commands.mdx | 77 + .../zh/developers/contribute/style-guide.mdx | 176 ++ .../l/zh/developers/extend/api.mdx | 142 +- .../l/zh/developers/extend/apps/building.mdx | 1783 +------------- .../extend/apps/cli-and-testing.mdx | 434 ++++ .../zh/developers/extend/apps/data-model.mdx | 494 ++++ .../extend/apps/front-components.mdx | 419 ++++ .../extend/apps/getting-started.mdx | 317 +-- .../l/zh/developers/extend/apps/layout.mdx | 131 + .../extend/apps/logic-functions.mdx | 560 +++++ .../zh/developers/extend/apps/publishing.mdx | 85 +- .../extend/apps/skills-and-agents.mdx | 69 + .../l/zh/developers/extend/oauth.mdx | 189 ++ .../l/zh/developers/extend/webhooks.mdx | 5 +- .../l/zh/developers/introduction.mdx | 27 +- .../capabilities/cloud-providers.mdx | 1 + .../self-host/capabilities/docker-compose.mdx | 3 +- .../self-host/capabilities/setup.mdx | 1 + .../capabilities/troubleshooting.mdx | 1 + .../self-host/capabilities/upgrade-guide.mdx | 359 +-- packages/twenty-docs/l/zh/navigation.json | 131 +- .../l/zh/twenty-ui/display/app-tooltip.mdx | 1 + .../l/zh/twenty-ui/display/checkmark.mdx | 1 + .../l/zh/twenty-ui/display/icons.mdx | 1 + .../l/zh/twenty-ui/display/soon-pill.mdx | 1 - .../l/zh/twenty-ui/display/tag.mdx | 2 +- .../l/zh/twenty-ui/input/buttons.mdx | 1 + .../l/zh/twenty-ui/input/checkbox.mdx | 1 + .../l/zh/twenty-ui/input/color-scheme.mdx | 1 + .../l/zh/twenty-ui/input/radio.mdx | 1 + .../l/zh/twenty-ui/input/toggle.mdx | 2 +- .../l/zh/twenty-ui/introduction.mdx | 1 + .../twenty-docs/l/zh/twenty-ui/navigation.mdx | 1 + .../l/zh/twenty-ui/navigation/links.mdx | 1 + .../l/zh/twenty-ui/navigation/menu-item.mdx | 2 +- .../twenty-ui/navigation/navigation-bar.mdx | 2 +- .../l/zh/twenty-ui/progress-bar.mdx | 1 - .../billing/capabilities/credits.mdx | 27 +- .../billing/how-tos/billing-faq.mdx | 4 +- .../l/zh/user-guide/billing/overview.mdx | 1 - .../user-guide/calendar-emails/overview.mdx | 1 - .../l/zh/user-guide/dashboards/overview.mdx | 1 - .../how-tos/fix-import-errors.mdx | 8 +- .../how-tos/prepare-your-csv-files.mdx | 2 - .../zh/user-guide/data-migration/overview.mdx | 1 - .../l/zh/user-guide/data-model/overview.mdx | 1 - .../l/zh/user-guide/introduction.mdx | 13 +- .../layout/capabilities/navigation.mdx | 32 + .../layout/capabilities/record-pages.mdx | 51 + .../l/zh/user-guide/layout/overview.mdx | 45 + .../permissions-access/overview.mdx | 1 - .../l/zh/user-guide/settings/overview.mdx | 1 - .../user-guide/views-pipelines/overview.mdx | 1 - .../capabilities/workflow-credits.mdx | 10 +- .../l/zh/user-guide/workflows/overview.mdx | 1 - 462 files changed, 23407 insertions(+), 21106 deletions(-) create mode 100644 packages/twenty-docs/l/ar/developers/contribute/commands.mdx create mode 100644 packages/twenty-docs/l/ar/developers/contribute/style-guide.mdx create mode 100644 packages/twenty-docs/l/ar/developers/extend/apps/cli-and-testing.mdx create mode 100644 packages/twenty-docs/l/ar/developers/extend/apps/data-model.mdx create mode 100644 packages/twenty-docs/l/ar/developers/extend/apps/front-components.mdx create mode 100644 packages/twenty-docs/l/ar/developers/extend/apps/layout.mdx create mode 100644 packages/twenty-docs/l/ar/developers/extend/apps/logic-functions.mdx create mode 100644 packages/twenty-docs/l/ar/developers/extend/apps/skills-and-agents.mdx create mode 100644 packages/twenty-docs/l/ar/developers/extend/oauth.mdx create mode 100644 packages/twenty-docs/l/ar/user-guide/layout/capabilities/navigation.mdx create mode 100644 packages/twenty-docs/l/ar/user-guide/layout/capabilities/record-pages.mdx create mode 100644 packages/twenty-docs/l/ar/user-guide/layout/overview.mdx create mode 100644 packages/twenty-docs/l/cs/developers/contribute/commands.mdx create mode 100644 packages/twenty-docs/l/cs/developers/contribute/style-guide.mdx create mode 100644 packages/twenty-docs/l/cs/developers/extend/apps/cli-and-testing.mdx create mode 100644 packages/twenty-docs/l/cs/developers/extend/apps/data-model.mdx create mode 100644 packages/twenty-docs/l/cs/developers/extend/apps/front-components.mdx create mode 100644 packages/twenty-docs/l/cs/developers/extend/apps/layout.mdx create mode 100644 packages/twenty-docs/l/cs/developers/extend/apps/logic-functions.mdx create mode 100644 packages/twenty-docs/l/cs/developers/extend/apps/skills-and-agents.mdx create mode 100644 packages/twenty-docs/l/cs/developers/extend/oauth.mdx create mode 100644 packages/twenty-docs/l/cs/user-guide/layout/capabilities/navigation.mdx create mode 100644 packages/twenty-docs/l/cs/user-guide/layout/capabilities/record-pages.mdx create mode 100644 packages/twenty-docs/l/cs/user-guide/layout/overview.mdx create mode 100644 packages/twenty-docs/l/de/developers/contribute/commands.mdx create mode 100644 packages/twenty-docs/l/de/developers/contribute/style-guide.mdx create mode 100644 packages/twenty-docs/l/de/developers/extend/apps/cli-and-testing.mdx create mode 100644 packages/twenty-docs/l/de/developers/extend/apps/data-model.mdx create mode 100644 packages/twenty-docs/l/de/developers/extend/apps/front-components.mdx create mode 100644 packages/twenty-docs/l/de/developers/extend/apps/layout.mdx create mode 100644 packages/twenty-docs/l/de/developers/extend/apps/logic-functions.mdx create mode 100644 packages/twenty-docs/l/de/developers/extend/apps/skills-and-agents.mdx create mode 100644 packages/twenty-docs/l/de/developers/extend/oauth.mdx create mode 100644 packages/twenty-docs/l/de/user-guide/layout/capabilities/navigation.mdx create mode 100644 packages/twenty-docs/l/de/user-guide/layout/capabilities/record-pages.mdx create mode 100644 packages/twenty-docs/l/de/user-guide/layout/overview.mdx create mode 100644 packages/twenty-docs/l/it/developers/contribute/commands.mdx create mode 100644 packages/twenty-docs/l/it/developers/contribute/style-guide.mdx create mode 100644 packages/twenty-docs/l/it/developers/extend/apps/cli-and-testing.mdx create mode 100644 packages/twenty-docs/l/it/developers/extend/apps/data-model.mdx create mode 100644 packages/twenty-docs/l/it/developers/extend/apps/front-components.mdx create mode 100644 packages/twenty-docs/l/it/developers/extend/apps/layout.mdx create mode 100644 packages/twenty-docs/l/it/developers/extend/apps/logic-functions.mdx create mode 100644 packages/twenty-docs/l/it/developers/extend/apps/skills-and-agents.mdx create mode 100644 packages/twenty-docs/l/it/developers/extend/oauth.mdx create mode 100644 packages/twenty-docs/l/it/user-guide/layout/capabilities/navigation.mdx create mode 100644 packages/twenty-docs/l/it/user-guide/layout/capabilities/record-pages.mdx create mode 100644 packages/twenty-docs/l/it/user-guide/layout/overview.mdx create mode 100644 packages/twenty-docs/l/pt/developers/contribute/commands.mdx create mode 100644 packages/twenty-docs/l/pt/developers/contribute/style-guide.mdx create mode 100644 packages/twenty-docs/l/pt/developers/extend/apps/cli-and-testing.mdx create mode 100644 packages/twenty-docs/l/pt/developers/extend/apps/data-model.mdx create mode 100644 packages/twenty-docs/l/pt/developers/extend/apps/front-components.mdx create mode 100644 packages/twenty-docs/l/pt/developers/extend/apps/layout.mdx create mode 100644 packages/twenty-docs/l/pt/developers/extend/apps/logic-functions.mdx create mode 100644 packages/twenty-docs/l/pt/developers/extend/apps/skills-and-agents.mdx create mode 100644 packages/twenty-docs/l/pt/developers/extend/oauth.mdx create mode 100644 packages/twenty-docs/l/pt/user-guide/layout/capabilities/navigation.mdx create mode 100644 packages/twenty-docs/l/pt/user-guide/layout/capabilities/record-pages.mdx create mode 100644 packages/twenty-docs/l/pt/user-guide/layout/overview.mdx create mode 100644 packages/twenty-docs/l/ru/developers/contribute/commands.mdx create mode 100644 packages/twenty-docs/l/ru/developers/contribute/style-guide.mdx create mode 100644 packages/twenty-docs/l/ru/developers/extend/apps/cli-and-testing.mdx create mode 100644 packages/twenty-docs/l/ru/developers/extend/apps/data-model.mdx create mode 100644 packages/twenty-docs/l/ru/developers/extend/apps/front-components.mdx create mode 100644 packages/twenty-docs/l/ru/developers/extend/apps/layout.mdx create mode 100644 packages/twenty-docs/l/ru/developers/extend/apps/logic-functions.mdx create mode 100644 packages/twenty-docs/l/ru/developers/extend/apps/skills-and-agents.mdx create mode 100644 packages/twenty-docs/l/ru/developers/extend/oauth.mdx create mode 100644 packages/twenty-docs/l/ru/user-guide/layout/capabilities/navigation.mdx create mode 100644 packages/twenty-docs/l/ru/user-guide/layout/capabilities/record-pages.mdx create mode 100644 packages/twenty-docs/l/ru/user-guide/layout/overview.mdx create mode 100644 packages/twenty-docs/l/tr/developers/contribute/commands.mdx create mode 100644 packages/twenty-docs/l/tr/developers/contribute/style-guide.mdx create mode 100644 packages/twenty-docs/l/tr/developers/extend/apps/cli-and-testing.mdx create mode 100644 packages/twenty-docs/l/tr/developers/extend/apps/data-model.mdx create mode 100644 packages/twenty-docs/l/tr/developers/extend/apps/front-components.mdx create mode 100644 packages/twenty-docs/l/tr/developers/extend/apps/layout.mdx create mode 100644 packages/twenty-docs/l/tr/developers/extend/apps/logic-functions.mdx create mode 100644 packages/twenty-docs/l/tr/developers/extend/apps/skills-and-agents.mdx create mode 100644 packages/twenty-docs/l/tr/developers/extend/oauth.mdx create mode 100644 packages/twenty-docs/l/tr/user-guide/layout/capabilities/navigation.mdx create mode 100644 packages/twenty-docs/l/tr/user-guide/layout/capabilities/record-pages.mdx create mode 100644 packages/twenty-docs/l/tr/user-guide/layout/overview.mdx create mode 100644 packages/twenty-docs/l/zh/developers/contribute/commands.mdx create mode 100644 packages/twenty-docs/l/zh/developers/contribute/style-guide.mdx create mode 100644 packages/twenty-docs/l/zh/developers/extend/apps/cli-and-testing.mdx create mode 100644 packages/twenty-docs/l/zh/developers/extend/apps/data-model.mdx create mode 100644 packages/twenty-docs/l/zh/developers/extend/apps/front-components.mdx create mode 100644 packages/twenty-docs/l/zh/developers/extend/apps/layout.mdx create mode 100644 packages/twenty-docs/l/zh/developers/extend/apps/logic-functions.mdx create mode 100644 packages/twenty-docs/l/zh/developers/extend/apps/skills-and-agents.mdx create mode 100644 packages/twenty-docs/l/zh/developers/extend/oauth.mdx create mode 100644 packages/twenty-docs/l/zh/user-guide/layout/capabilities/navigation.mdx create mode 100644 packages/twenty-docs/l/zh/user-guide/layout/capabilities/record-pages.mdx create mode 100644 packages/twenty-docs/l/zh/user-guide/layout/overview.mdx diff --git a/packages/twenty-docs/docs.json b/packages/twenty-docs/docs.json index 1e90a6bc506..e844fab9ee8 100644 --- a/packages/twenty-docs/docs.json +++ b/packages/twenty-docs/docs.json @@ -802,7 +802,7 @@ "language": "ar", "tabs": [ { - "tab": "Getting Started", + "tab": "البدء", "groups": [ { "group": "Welcome", @@ -831,7 +831,7 @@ "tab": "دليل المستخدم", "groups": [ { - "group": "Overview", + "group": "نظرة عامة", "pages": [ "l/ar/user-guide/introduction" ] @@ -1004,7 +1004,7 @@ ] }, { - "group": "Layout", + "group": "التخطيط", "icon": "table-columns", "pages": [ "l/ar/user-guide/layout/overview", @@ -1013,7 +1013,7 @@ "pages": [ "l/ar/user-guide/layout/capabilities/navigation", { - "group": "Views", + "group": "العروض", "pages": [ "l/ar/user-guide/views-pipelines/capabilities/table-views", "l/ar/user-guide/views-pipelines/capabilities/kanban-views", @@ -1027,7 +1027,7 @@ ] }, { - "group": "How-Tos", + "group": "الإرشادات", "pages": [ "l/ar/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping", "l/ar/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects", @@ -1132,7 +1132,7 @@ "tab": "المطورون", "groups": [ { - "group": "Overview", + "group": "نظرة عامة", "pages": [ "l/ar/developers/introduction" ] @@ -1152,7 +1152,7 @@ ] }, { - "group": "API", + "group": "واجهة برمجة التطبيقات", "pages": [ "l/ar/developers/extend/api", "l/ar/developers/extend/webhooks", @@ -1185,7 +1185,7 @@ "language": "cs", "tabs": [ { - "tab": "Getting Started", + "tab": "Začínáme", "groups": [ { "group": "Welcome", @@ -1214,7 +1214,7 @@ "tab": "Uživatelská příručka", "groups": [ { - "group": "Overview", + "group": "Přehled", "pages": [ "l/cs/user-guide/introduction" ] @@ -1387,7 +1387,7 @@ ] }, { - "group": "Layout", + "group": "Rozvržení", "icon": "table-columns", "pages": [ "l/cs/user-guide/layout/overview", @@ -1396,7 +1396,7 @@ "pages": [ "l/cs/user-guide/layout/capabilities/navigation", { - "group": "Views", + "group": "Zobrazení", "pages": [ "l/cs/user-guide/views-pipelines/capabilities/table-views", "l/cs/user-guide/views-pipelines/capabilities/kanban-views", @@ -1410,7 +1410,7 @@ ] }, { - "group": "How-Tos", + "group": "Návody", "pages": [ "l/cs/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping", "l/cs/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects", @@ -1515,7 +1515,7 @@ "tab": "Vývojáři", "groups": [ { - "group": "Overview", + "group": "Přehled", "pages": [ "l/cs/developers/introduction" ] @@ -1568,7 +1568,7 @@ "language": "de", "tabs": [ { - "tab": "Getting Started", + "tab": "Erste Schritte", "groups": [ { "group": "Welcome", @@ -1597,7 +1597,7 @@ "tab": "Benutzerhandbuch", "groups": [ { - "group": "Overview", + "group": "Übersicht", "pages": [ "l/de/user-guide/introduction" ] @@ -1779,7 +1779,7 @@ "pages": [ "l/de/user-guide/layout/capabilities/navigation", { - "group": "Views", + "group": "Ansichten", "pages": [ "l/de/user-guide/views-pipelines/capabilities/table-views", "l/de/user-guide/views-pipelines/capabilities/kanban-views", @@ -1793,7 +1793,7 @@ ] }, { - "group": "How-Tos", + "group": "Anleitungen", "pages": [ "l/de/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping", "l/de/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects", @@ -1898,7 +1898,7 @@ "tab": "Entwickler", "groups": [ { - "group": "Overview", + "group": "Übersicht", "pages": [ "l/de/developers/introduction" ] @@ -2334,7 +2334,7 @@ "language": "it", "tabs": [ { - "tab": "Getting Started", + "tab": "Per iniziare", "groups": [ { "group": "Welcome", @@ -2363,7 +2363,7 @@ "tab": "Guida utente", "groups": [ { - "group": "Overview", + "group": "Panoramica", "pages": [ "l/it/user-guide/introduction" ] @@ -2536,7 +2536,7 @@ ] }, { - "group": "Layout", + "group": "Disposizione", "icon": "table-columns", "pages": [ "l/it/user-guide/layout/overview", @@ -2545,7 +2545,7 @@ "pages": [ "l/it/user-guide/layout/capabilities/navigation", { - "group": "Views", + "group": "Viste", "pages": [ "l/it/user-guide/views-pipelines/capabilities/table-views", "l/it/user-guide/views-pipelines/capabilities/kanban-views", @@ -2559,7 +2559,7 @@ ] }, { - "group": "How-Tos", + "group": "Guide pratiche", "pages": [ "l/it/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping", "l/it/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects", @@ -2664,7 +2664,7 @@ "tab": "Sviluppatori", "groups": [ { - "group": "Overview", + "group": "Panoramica", "pages": [ "l/it/developers/introduction" ] @@ -3483,7 +3483,7 @@ "language": "pt", "tabs": [ { - "tab": "Getting Started", + "tab": "Primeiros passos", "groups": [ { "group": "Welcome", @@ -3512,7 +3512,7 @@ "tab": "User Guide", "groups": [ { - "group": "Overview", + "group": "Visão Geral", "pages": [ "l/pt/user-guide/introduction" ] @@ -3694,7 +3694,7 @@ "pages": [ "l/pt/user-guide/layout/capabilities/navigation", { - "group": "Views", + "group": "Visualizações", "pages": [ "l/pt/user-guide/views-pipelines/capabilities/table-views", "l/pt/user-guide/views-pipelines/capabilities/kanban-views", @@ -3813,7 +3813,7 @@ "tab": "Programadores", "groups": [ { - "group": "Overview", + "group": "Visão Geral", "pages": [ "l/pt/developers/introduction" ] @@ -4249,7 +4249,7 @@ "language": "ru", "tabs": [ { - "tab": "Getting Started", + "tab": "Начало работы", "groups": [ { "group": "Welcome", @@ -4278,7 +4278,7 @@ "tab": "Руководство пользователя", "groups": [ { - "group": "Overview", + "group": "Обзор", "pages": [ "l/ru/user-guide/introduction" ] @@ -4451,7 +4451,7 @@ ] }, { - "group": "Layout", + "group": "Макет", "icon": "table-columns", "pages": [ "l/ru/user-guide/layout/overview", @@ -4460,7 +4460,7 @@ "pages": [ "l/ru/user-guide/layout/capabilities/navigation", { - "group": "Views", + "group": "Представления", "pages": [ "l/ru/user-guide/views-pipelines/capabilities/table-views", "l/ru/user-guide/views-pipelines/capabilities/kanban-views", @@ -4474,7 +4474,7 @@ ] }, { - "group": "How-Tos", + "group": "Инструкции", "pages": [ "l/ru/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping", "l/ru/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects", @@ -4579,7 +4579,7 @@ "tab": "Разработчики", "groups": [ { - "group": "Overview", + "group": "Обзор", "pages": [ "l/ru/developers/introduction" ] @@ -4632,7 +4632,7 @@ "language": "tr", "tabs": [ { - "tab": "Getting Started", + "tab": "Başlarken", "groups": [ { "group": "Welcome", @@ -4661,7 +4661,7 @@ "tab": "Kullanıcı Rehberi", "groups": [ { - "group": "Overview", + "group": "Genel Bakış", "pages": [ "l/tr/user-guide/introduction" ] @@ -4834,7 +4834,7 @@ ] }, { - "group": "Layout", + "group": "Düzen", "icon": "table-columns", "pages": [ "l/tr/user-guide/layout/overview", @@ -4843,7 +4843,7 @@ "pages": [ "l/tr/user-guide/layout/capabilities/navigation", { - "group": "Views", + "group": "Görünümler", "pages": [ "l/tr/user-guide/views-pipelines/capabilities/table-views", "l/tr/user-guide/views-pipelines/capabilities/kanban-views", @@ -4857,7 +4857,7 @@ ] }, { - "group": "How-Tos", + "group": "Nasıl Yapılırlar", "pages": [ "l/tr/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping", "l/tr/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects", @@ -4962,7 +4962,7 @@ "tab": "Geliştiriciler", "groups": [ { - "group": "Overview", + "group": "Genel Bakış", "pages": [ "l/tr/developers/introduction" ] @@ -5015,7 +5015,7 @@ "language": "zh", "tabs": [ { - "tab": "Getting Started", + "tab": "开始使用", "groups": [ { "group": "Welcome", @@ -5044,7 +5044,7 @@ "tab": "用户指南", "groups": [ { - "group": "Overview", + "group": "概览", "pages": [ "l/zh/user-guide/introduction" ] @@ -5217,7 +5217,7 @@ ] }, { - "group": "Layout", + "group": "布局", "icon": "table-columns", "pages": [ "l/zh/user-guide/layout/overview", @@ -5226,7 +5226,7 @@ "pages": [ "l/zh/user-guide/layout/capabilities/navigation", { - "group": "Views", + "group": "视图", "pages": [ "l/zh/user-guide/views-pipelines/capabilities/table-views", "l/zh/user-guide/views-pipelines/capabilities/kanban-views", @@ -5240,7 +5240,7 @@ ] }, { - "group": "How-Tos", + "group": "操作指南", "pages": [ "l/zh/user-guide/views-pipelines/how-tos/create-a-table-view-with-grouping", "l/zh/user-guide/views-pipelines/how-tos/create-a-kanban-view-for-projects", @@ -5345,7 +5345,7 @@ "tab": "开发者", "groups": [ { - "group": "Overview", + "group": "概览", "pages": [ "l/zh/developers/introduction" ] @@ -5365,7 +5365,7 @@ ] }, { - "group": "API", + "group": "接口", "pages": [ "l/zh/developers/extend/api", "l/zh/developers/extend/webhooks", diff --git a/packages/twenty-docs/l/ar/developers/contribute/capabilities/backend-development/server-commands.mdx b/packages/twenty-docs/l/ar/developers/contribute/capabilities/backend-development/server-commands.mdx index 6b034942053..d054ea44c43 100644 --- a/packages/twenty-docs/l/ar/developers/contribute/capabilities/backend-development/server-commands.mdx +++ b/packages/twenty-docs/l/ar/developers/contribute/capabilities/backend-development/server-commands.mdx @@ -1,5 +1,6 @@ --- title: الأوامر الخلفية +icon: terminal --- ## الأوامر المفيدة diff --git a/packages/twenty-docs/l/ar/developers/contribute/capabilities/bug-and-requests.mdx b/packages/twenty-docs/l/ar/developers/contribute/capabilities/bug-and-requests.mdx index a7b0e365f97..ef778447f76 100644 --- a/packages/twenty-docs/l/ar/developers/contribute/capabilities/bug-and-requests.mdx +++ b/packages/twenty-docs/l/ar/developers/contribute/capabilities/bug-and-requests.mdx @@ -1,5 +1,6 @@ --- title: الأخطاء والطلبات وطلبات السحب +icon: bug info: أبلغ عن المشكلات، واطلب الميزات، وساهم بالشفرة البرمجية --- diff --git a/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/best-practices-front.mdx b/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/best-practices-front.mdx index 4a28b9bb9c6..cb8e7e8eee0 100644 --- a/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/best-practices-front.mdx +++ b/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/best-practices-front.mdx @@ -1,5 +1,6 @@ --- title: أفضل الممارسات +icon: star --- تحدد هذه الوثيقة أفضل الممارسات التي يجب اتباعها عند العمل في الواجهة الأمامية. diff --git a/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx b/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx index d0754be85e8..2ea6a0b4afc 100644 --- a/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx +++ b/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx @@ -1,5 +1,6 @@ --- title: هيكلية المجلدات +icon: folder-tree info: نظرة مفصلة على هيكل المجلدات الخاصة بنا --- diff --git a/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/frontend-commands.mdx b/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/frontend-commands.mdx index 2c231823dbf..55b1bdb4a1c 100644 --- a/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/frontend-commands.mdx +++ b/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/frontend-commands.mdx @@ -1,5 +1,6 @@ --- title: أوامر الواجهة الأمامية +icon: terminal --- ## الأوامر المفيدة diff --git a/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/style-guide.mdx b/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/style-guide.mdx index 137457c257d..55fde3dae2d 100644 --- a/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/style-guide.mdx +++ b/packages/twenty-docs/l/ar/developers/contribute/capabilities/frontend-development/style-guide.mdx @@ -1,5 +1,6 @@ --- title: دليل الأسلوب +icon: paintbrush --- تشمل هذه الوثيقة القواعد التي يجب اتباعها عند كتابة التعليمات البرمجية. diff --git a/packages/twenty-docs/l/ar/developers/contribute/capabilities/local-setup.mdx b/packages/twenty-docs/l/ar/developers/contribute/capabilities/local-setup.mdx index e63bdbc0196..db9f06702a8 100644 --- a/packages/twenty-docs/l/ar/developers/contribute/capabilities/local-setup.mdx +++ b/packages/twenty-docs/l/ar/developers/contribute/capabilities/local-setup.mdx @@ -1,5 +1,6 @@ --- title: الإعداد المحلي +icon: laptop-code description: الدليل للمساهمين (أو المطورين الفضوليين) الذين يرغبون في تشغيل Twenty محلياً. --- diff --git a/packages/twenty-docs/l/ar/developers/contribute/commands.mdx b/packages/twenty-docs/l/ar/developers/contribute/commands.mdx new file mode 100644 index 00000000000..ea6ff7d83a0 --- /dev/null +++ b/packages/twenty-docs/l/ar/developers/contribute/commands.mdx @@ -0,0 +1,77 @@ +--- +title: Commands +icon: terminal +description: Useful commands for developing Twenty. +--- + +Commands can be run from the repository root using `npx nx`. Use `npx nx run {project}:{command}` for explicit targeting. + +## Starting the App + +```bash +npx nx start twenty-front # Frontend dev server (http://localhost:3001) +npx nx start twenty-server # Backend server (http://localhost:3000) +npx nx run twenty-server:worker # Background worker +``` + +## Database + +```bash +npx nx database:reset twenty-server # Reset and seed database +npx nx run twenty-server:database:migrate:prod # Run migrations +npx nx run twenty-server:database:migrate:generate --name --type # Generate a migration +``` + +## Linting + +```bash +npx nx lint:diff-with-main twenty-front # Lint changed files (fastest) +npx nx lint:diff-with-main twenty-server +npx nx lint twenty-front --configuration=fix # Auto-fix +``` + +## Type Checking + +```bash +npx nx typecheck twenty-front +npx nx typecheck twenty-server +``` + +## "الاختبار" + +```bash +# Frontend +npx nx test twenty-front # Jest unit tests +npx nx storybook:build twenty-front # Build Storybook +npx nx storybook:test twenty-front # Storybook tests + +# Backend +npx nx run twenty-server:test:unit # Unit tests +npx nx run twenty-server:test:integration # Integration tests +npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset + +# Single file (fastest) +npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs +``` + +## جراف كيو إل + +```bash +npx nx run twenty-front:graphql:generate # Regenerate types +npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema +``` + +## "الترجمات" + +```bash +npx nx run twenty-front:lingui:extract # Extract strings +npx nx run twenty-front:lingui:compile # Compile translations +``` + +## Build + +```bash +npx nx build twenty-shared # Must be built first +npx nx build twenty-front +npx nx build twenty-server +``` diff --git a/packages/twenty-docs/l/ar/developers/contribute/style-guide.mdx b/packages/twenty-docs/l/ar/developers/contribute/style-guide.mdx new file mode 100644 index 00000000000..625b3a341ab --- /dev/null +++ b/packages/twenty-docs/l/ar/developers/contribute/style-guide.mdx @@ -0,0 +1,176 @@ +--- +title: دليل الأسلوب +icon: paintbrush +description: Code conventions and best practices for contributing to Twenty. +--- + +## React + +### Functional components only + +Always use TSX functional components with named exports. + +```tsx +// ❌ Bad +const MyComponent = () => { + return
Hello World
; +}; +export default MyComponent; + +// ✅ Good +export function MyComponent() { + return
Hello World
; +}; +``` + +### الإزاحة + +Create a type named `{ComponentName}Props`. Use destructuring. Don't use `React.FC`. + +```tsx +type MyComponentProps = { + name: string; +}; + +export const MyComponent = ({ name }: MyComponentProps) =>
Hello {name}
; +``` + +### No single-variable prop spreading + +```tsx +// ❌ Bad +const MyComponent = (props: MyComponentProps) => ; + +// ✅ Good +const MyComponent = ({ prop1, prop2 }: MyComponentProps) => ; +``` + +## "إدارة الحالة" + +### Jotai atoms for global state + +```tsx +import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; +import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; + +export const myAtomState = createAtomState({ + key: 'myAtomState', + defaultValue: 'default value', +}); +``` + +* Prefer atoms over prop drilling +* Don't use `useRef` for state — use `useState` or atoms +* Use atom families and selectors for lists + +### Avoid unnecessary re-renders + +* Extract `useEffect` and data fetching into sibling sidecar components +* Prefer event handlers (`handleClick`, `handleChange`) over `useEffect` +* Don't use `React.memo()` — fix the root cause instead +* Limit `useCallback` / `useMemo` usage + +```tsx +// ❌ Bad — useEffect in the same component causes re-renders +export const Page = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return
{data}
; +}; + +// ✅ Good — extract into sibling +export const PageData = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return <>; +}; +export const Page = () => { + const [data] = useAtomState(dataState); + return
{data}
; +}; +``` + +## TypeScript + +* **`type` over `interface`** — more flexible, easier to compose +* **String literals over enums** — except for GraphQL codegen enums and internal library APIs +* **No `any`** — strict TypeScript enforced +* **No type imports** — use regular imports (enforced by Oxlint `typescript/consistent-type-imports`) +* **Use [Zod](https://github.com/colinhacks/zod)** for runtime validation of untyped objects + +## JavaScript + +```tsx +// Use nullish-coalescing (??) instead of || +const value = process.env.MY_VALUE ?? 'default'; + +// Use optional chaining +onClick?.(); +``` + +## التسمية + +* **Variables**: camelCase, descriptive (`email` not `value`, `fieldMetadata` not `fm`) +* **Constants**: SCREAMING_SNAKE_CASE +* **Types/Classes**: PascalCase +* **Files/directories**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`) +* **Event handlers**: `handleClick` (not `onClick` for the handler function) +* **Component props**: prefix with component name (`ButtonProps`) +* **Styled components**: prefix with `Styled` (`StyledTitle`) + +## التنسيق + +Use [Linaria](https://github.com/callstack/linaria) styled components. Use theme values — avoid hardcoded `px`, `rem`, or colors. + +```tsx +// ❌ Bad +const StyledButton = styled.button` + color: #333333; + font-size: 1rem; + margin-left: 4px; +`; + +// ✅ Good +const StyledButton = styled.button` + color: ${({ theme }) => theme.font.color.primary}; + font-size: ${({ theme }) => theme.font.size.md}; + margin-left: ${({ theme }) => theme.spacing(1)}; +`; +``` + +## استيرادات + +Use aliases instead of relative paths: + +```tsx +// ❌ Bad +import { Foo } from '../../../../../testing/decorators/Foo'; + +// ✅ Good +import { Foo } from '~/testing/decorators/Foo'; +import { Bar } from '@/modules/bar/components/Bar'; +``` + +## Folder Structure + +``` +front +└── modules/ # Feature modules +│ └── module1/ +│ ├── components/ +│ ├── constants/ +│ ├── contexts/ +│ ├── graphql/ (fragments, queries, mutations) +│ ├── hooks/ +│ ├── states/ (atoms, selectors) +│ ├── types/ +│ └── utils/ +└── pages/ # Route-level components +└── ui/ # Reusable UI components (display, input, feedback, ...) +``` + +* Modules can import from other modules, but `ui/` should stay dependency-free +* Use `internal/` subfolders for module-private code +* Components under 300 lines, services under 500 lines diff --git a/packages/twenty-docs/l/ar/developers/extend/api.mdx b/packages/twenty-docs/l/ar/developers/extend/api.mdx index b43b0e375e0..6b95caed7c1 100644 --- a/packages/twenty-docs/l/ar/developers/extend/api.mdx +++ b/packages/twenty-docs/l/ar/developers/extend/api.mdx @@ -1,147 +1,55 @@ --- title: واجهات برمجة التطبيقات -description: استعلم وعدّل بيانات إدارة علاقات العملاء (CRM) لديك برمجياً باستخدام REST أو GraphQL. +icon: plug +description: REST and GraphQL APIs generated from your workspace schema. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -تم تصميم Twenty ليكون صديقًا للمطورين، حيث يوفر واجهات برمجة قوية تتكيف مع نموذج البيانات المخصص. نحن نوفر أربعة أنواع متميزة من واجهات برمجة التطبيقات لتلبية احتياجات التكامل المختلفة. +## Schema-per-tenant APIs -## نهج المطوّر أولاً +There is no static API reference for Twenty. Each workspace has its own schema — when you add a custom object (say `Invoice`), it immediately gets REST and GraphQL endpoints identical to built-in objects like `Company` or `Person`. The API is generated from the schema, so endpoints use your object and field names directly — no opaque IDs. -تقوم Twenty بإنشاء واجهات برمجة التطبيقات خصيصاً لنموذج بياناتك: +Your workspace-specific API documentation is available under **Settings → API & Webhooks** after creating an API key. It includes an interactive playground where you can execute real calls against your data. -* **لا حاجة إلى معرفات طويلة**: استخدم أسماء الكائنات والحقول مباشرة في نقاط النهاية -* **معاملة متساوية للكائنات القياسية والمخصصة**: تحصل كائناتك المخصصة على نفس معاملة واجهة برمجة التطبيقات كما هو الحال مع الكائنات المضمنة -* **نقاط نهاية مخصصة**: يحصل كل كائن وحقل على نقطة نهاية API الخاصة به -* **وثائق مخصصة**: يتم إنشاؤها خصيصًا لنموذج بيانات مساحة عملك +## Two APIs - -وثائق واجهة برمجة التطبيقات المخصصة لك متاحة ضمن **الإعدادات → واجهات برمجة التطبيقات وخطافات الويب** بعد إنشاء مفتاح API. نظرًا لأن Twenty تُنشئ واجهات برمجة تطبيقات تتطابق مع نموذج البيانات المخصص لديك، فإن الوثائق فريدة لمساحة عملك. - +**Core API** — `/rest/` and `/graphql/` -## نوعا واجهات برمجة التطبيقات +CRUD on records: People, Companies, Opportunities, your custom objects. Query, filter, traverse relations. -### واجهة برمجة التطبيقات الأساسية +**Metadata API** — `/rest/metadata/` and `/metadata/` -يتم الوصول إليها عبر `/rest/` أو `/graphql/` +Schema management: create/modify/delete objects, fields, and relations. This is how you programmatically change your data model. -تعامَل مع **السجلات** الفعلية لديك (البيانات): +Both are available as REST and GraphQL. GraphQL adds batch upserts and the ability to traverse relations in a single query. Same underlying data either way. -* إنشاء وقراءة وتحديث وحذف الأشخاص والشركات والفرص، إلخ. -* استعلام وتصفية البيانات -* إدارة العلاقات بين السجلات +## Base URLs -### واجهة برمجة البيانات الوصفية - -يتم الوصول إليها عبر `/rest/metadata/` أو `/metadata/` - -إدارة **مساحة العمل ونموذج البيانات** لديك: - -* إنشاء أو تعديل أو حذف الكائنات والحقول -* تكوين إعدادات مساحة العمل -* تعريف العلاقات بين الكائنات - -## REST مقابل GraphQL - -تتوفر واجهات برمجة التطبيقات الأساسية وواجهات البيانات الوصفية بصيغتي REST وGraphQL: - -| التنسيق | العمليات المتاحة | -| ----------- | ----------------------------------------------------------------------------- | -| **REST** | CRUD، عمليات الدفعات، إدراج/تحديث | -| **GraphQL** | نفس الشيء + **عمليات إدراج/تحديث مجمعة**، واستعلامات العلاقات في استدعاء واحد | - -اختر بناءً على احتياجاتك — كلا الصيغتين تصلان إلى البيانات نفسها. - -## نقاط نهاية API - -| البيئة | عنوان URL الأساسي | -| --------------------- | ------------------------- | -| **السحابة** | `https://api.twenty.com/` | -| **الاستضافة الذاتية** | `https://{your-domain}/` | +| البيئة | عنوان URL الأساسي | +| ----------- | ------------------------- | +| Cloud | `https://api.twenty.com/` | +| Self-Hosted | `https://{your-domain}/` | ## المصادقة -كل طلب API يتطلب تضمين مفتاح API في رأس الطلب: - ``` Authorization: Bearer YOUR_API_KEY ``` -### قم بإنشاء مفتاح API - -1. انتقل إلى **الإعدادات → واجهات برمجة التطبيقات وخطافات الويب** -2. انقر على **+ إنشاء مفتاح** -3. التكوين: - * **الاسم**: اسم وصفي للمفتاح - * **تاريخ الانتهاء**: متى تنتهي صلاحية المفتاح -4. انقر على **حفظ** -5. **انسخه فوراً** — يظهر المفتاح مرة واحدة فقط +Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access. - -يمنح مفتاح API الخاص بك الوصول إلى بيانات حساسة. لا تشاركه مع خدمات غير موثوقة. إذا تم اختراقه، عطّلْه فوراً وأنشئ مفتاحاً جديداً. - +For OAuth-based access (external apps acting on behalf of users), see [OAuth](/l/ar/developers/extend/oauth). -### تعيين دور لمفتاح API +## Batch operations -لتحسين الأمان، عيّن دوراً محدداً لتقييد الوصول: +Both REST and GraphQL support batching up to 60 records per request — create, update, or delete. GraphQL also supports batch upsert (create-or-update in one call) using plural names like `CreateCompanies`. -1. اذهب إلى **الإعدادات → الأدوار** -2. انقر على الدور الذي ترغب في تعيينه -3. افتح علامة التبويب **التعيين** -4. ضمن **مفاتيح API**، انقر على **+ تعيين إلى مفتاح API** -5. حدد مفتاح API +## Rate limits -سيرث المفتاح أذونات ذلك الدور. راجع [الأذونات](/l/ar/user-guide/permissions-access/capabilities/permissions) للحصول على التفاصيل. - -### إدارة مفاتيح API - -**إعادة التوليد**: الإعدادات → واجهات برمجة التطبيقات وخطافات الويب → انقر على المفتاح → **إعادة التوليد** - -**حذف**: الإعدادات → واجهات برمجة التطبيقات وخطافات الويب → انقر على المفتاح → **حذف** - -## ملعب واجهة برمجة التطبيقات - -اختبر واجهات برمجة التطبيقات لديك مباشرة في المتصفح باستخدام الملعب المدمج لدينا — متاح لكلٍ من **REST** و**GraphQL**. - -### الوصول إلى الملعب - -1. انتقل إلى **الإعدادات → واجهات برمجة التطبيقات وخطافات الويب** -2. أنشئ مفتاح API (مطلوب) -3. انقر على **REST API** أو **GraphQL API** لفتح الملعب - -### ما الذي ستحصل عليه - -* **وثائق تفاعلية**: يتم إنشاؤها لنموذج البيانات المحدد لديك -* **اختبارات حيّة**: تنفيذ استدعاءات API فعلية على مساحة عملك -* **مستكشف المخطط**: تصفح الكائنات والحقول والعلاقات المتاحة -* **منشئ الطلبات**: أنشئ الاستعلامات مع الإكمال التلقائي - -يعكس الملعب الكائنات والحقول المخصصة لديك، لذا تكون الوثائق دائماً دقيقة لمساحة عملك. - -## عمليات الدفعات - -كلٌ من REST وGraphQL يدعمان عمليات الدفعات: - -* **حجم الدفعة**: حتى 60 سجل لكل طلب -* **العمليات**: إنشاء وتحديث وحذف سجلات متعددة - -**ميزات خاصة بـ GraphQL:** - -* **إدراج/تحديث دفعي**: إنشاء أو تحديث في استدعاء واحد -* استخدم الأسماء الجمع للكائنات (على سبيل المثال، `CreateCompanies` بدلاً من `CreateCompany`) - -## حدود المعدل - -يتم تنظيم طلبات API لضمان استقرار المنصة: - -| الحد | القيمة | -| -------------- | ---------------------- | -| **الطلبات** | 100 استدعاء في الدقيقة | -| **حجم الدفعة** | 60 سجل لكل استدعاء | - - -استخدم عمليات الدفعات لزيادة الإنتاجية — عالج ما يصل إلى 60 سجلًا في استدعاء API واحد بدلاً من إجراء طلبات فردية. - +| الحد | القيمة | +| ---------- | ------------------ | +| Requests | 100 per minute | +| Batch size | 60 سجل لكل استدعاء | diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/building.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/building.mdx index 5b04b4eb11d..61e2d1426ce 100644 --- a/packages/twenty-docs/l/ar/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/ar/developers/extend/apps/building.mdx @@ -1,2062 +1,104 @@ --- -title: بناء التطبيقات -description: عرّف الكائنات، والدوال المنطقية، ومكوّنات الواجهة الأمامية، وغير ذلك باستخدام Twenty SDK. +title: '"الهيكلية"' +description: How Twenty apps work — sandboxing, lifecycle, and the building blocks. +icon: sitemap --- - - التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور. - +Twenty apps are TypeScript packages that extend your workspace with custom objects, logic, UI components, and AI capabilities. They run on the Twenty platform with full sandboxing and permission controls. -توفر حزمة `twenty-sdk` لبنات بناء مضبوطة الأنواع لإنشاء تطبيقك. تغطي هذه الصفحة كل نوع كيان وكل عميل واجهة برمجة تطبيقات متاح في SDK. +## How apps work -## دوال DefineEntity +An app is a collection of **entities** declared using `defineEntity()` functions from the `twenty-sdk` package. The SDK detects these declarations via AST analysis at build time and produces a **manifest** — a complete description of what your app adds to a workspace. -يوفّر SDK دوالًا لتعريف كيانات تطبيقك. يجب عليك استخدام `export default defineEntity({...})` لكي يكتشف SDK الكيانات الخاصة بك. تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع. - - - **تنظيم الملفات يعود إليك.** - يعتمد اكتشاف الكيانات على AST — حيث يعثر SDK على استدعاءات `export default defineEntity(...)` بغض النظر عن مكان وجود الملف. تجميع الملفات حسب النوع (مثلًا، `logic-functions/` و`roles/`) هو مجرّد عرف، وليس متطلبًا. - - - - - -تُغلّف الأدوار الصلاحيات على كائنات وإجراءات مساحة العمل لديك. - -```ts restricted-company-role.ts -import { - defineRole, - PermissionFlag, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -export default defineRole({ - universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', - label: 'My new role', - description: 'A role that can be used in your workspace', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - fieldUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); ``` - - - - -يجب أن يحتوي كل تطبيق على استدعاء واحد فقط لـ `defineApplication` يصف: - -* **الهوية**: المعرّفات، اسم العرض، والوصف. -* **الأذونات**: أيُّ دورٍ تستخدمه وظائفه ومكوّناته الأمامية. -* **(اختياري) المتغيرات**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة. -* **(اختياري) دوال ما قبل التثبيت/ما بعد التثبيت**: دوال منطقية تعمل قبل التثبيت أو بعده. - -```ts src/application-config.ts -import { defineApplication } from 'twenty-sdk/define'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -الملاحظات: -* حقول `universalIdentifier` هي معرّفات حتمية تملكها أنت. أنشِئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة. -* `applicationVariables` تصبح متغيرات بيئة لوظائفك ومكوّناتك الأمامية (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` يجب أن يُشير إلى دور مُعرَّف باستخدام `defineRole()` (انظر أعلاه). -* يتم اكتشاف دوال ما قبل التثبيت وما بعده تلقائيًا أثناء بناء البيان — لا حاجة للإشارة إليها في `defineApplication()`. - -#### بيانات التعريف لسوق التطبيقات - -إذا كنت تخطط لـ [نشر تطبيقك](/l/ar/developers/extend/apps/publishing)، فإن هذه الحقول الاختيارية تتحكّم في كيفية ظهوره في السوق: - -| الحقل | الوصف | -| ------------------ | ------------------------------------------------------------------------------------------------------------ | -| `author` | اسم المؤلف أو الشركة | -| `category` | فئة التطبيق لتصفية سوق التطبيقات | -| `logoUrl` | مسار شعار تطبيقك (مثلًا، `public/logo.png`) | -| `screenshots` | مصفوفة لمسارات لقطات الشاشة (مثلًا، `public/screenshot-1.png`) | -| `aboutDescription` | وصف ماركداون أطول لعلامة التبويب "حول". إذا لم يتم تضمينه، يستخدم السوق ملف `README.md` الخاص بالحزمة من npm | -| `websiteUrl` | رابط إلى موقعك الإلكتروني | -| `termsUrl` | رابط إلى شروط الخدمة | -| `emailSupport` | عنوان البريد الإلكتروني للدعم | -| `issueReportUrl` | رابط إلى متتبّع المشاكل | - -#### الأدوار والصلاحيات - -يُحدّد الحقل `defaultRoleUniversalIdentifier` في `application-config.ts` الدور الافتراضي الذي تستخدمه وظائف المنطق والمكوّنات الأمامية في تطبيقك. راجع `defineRole` أعلاه للحصول على التفاصيل. - -* رمز وقت التشغيل المحقون باسم `TWENTY_APP_ACCESS_TOKEN` مستمد من هذا الدور. -* العميل مضبوط الأنواع مقيَّد بالأذونات الممنوحة لذلك الدور. -* اتبع مبدأ أقل الامتياز: أنشئ دورًا مخصصًا يضم فقط الأذونات التي تحتاجها وظائفك. - -##### الدور الافتراضي للوظيفة - -عند توليد تطبيق جديد بالقالب، ينشئ CLI ملفّ دور افتراضي: - -```ts src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk/define'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: true, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [], - fieldPermissions: [], - permissionFlags: [], -}); -``` - -يُشار إلى `universalIdentifier` لهذا الدور في `application-config.ts` باسم `defaultRoleUniversalIdentifier`: - -* **\*.role.ts** يحدد ما يمكن أن يفعله الدور. -* **application-config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك أذوناته. - -الملاحظات: -* ابدأ من الدور المُنشأ بالقالب، ثم قيّده تدريجيًا باتباع مبدأ أقل الامتياز. -* استبدل `objectPermissions` و`fieldPermissions` بالكائنات والحقول التي تحتاجها وظائفك فعليًا. -* `permissionFlags` تتحكم في الوصول إلى القدرات على مستوى المنصة. اجعلها في حدّها الأدنى. -* اطّلع على مثال عملي: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - - - - -تصف الكائنات المخصصة كلًا من المخطط والسلوك للسجلات في مساحة عملك. استخدم `defineObject()` لتعريف كائنات مع تحقق مدمج: - -```ts postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk/define'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -النقاط الرئيسية: - -* استخدم `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, etc.) أو كائنات من تطبيقات أخرى. على خلاف الحقول المضمّنة في `defineObject()`، تتطلّب الحقول المستقلة `objectUniversalIdentifier` لتحديد الكائن الذي تقوم بتوسيعه: - -```ts src/fields/company-loyalty-tier.field.ts -import { defineField, FieldType } from 'twenty-sdk/define'; - -export default defineField({ - universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', - objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object - name: 'loyaltyTier', - type: FieldType.SELECT, - label: 'Loyalty Tier', - icon: 'IconStar', - options: [ - { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, - { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, - { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, - ], -}); -``` - -النقاط الرئيسية: -* `objectUniversalIdentifier` يحدّد الكائن الهدف. بالنسبة للكائنات القياسية، استخدم `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` المُصدَّر من `twenty-sdk`. -* عند تعريف الحقول بشكل مضمّن في `defineObject()`، **لا** تحتاج إلى `objectUniversalIdentifier` — إذ يُورَّث من الكائن الأب. -* `defineField()` هي الطريقة الوحيدة لإضافة حقول إلى كائنات لم تُنشئها باستخدام `defineObject()`. - - - - -تربط العلاقات الكائنات معًا. في Twenty، تكون العلاقات دائمًا **ثنائية الاتجاه** — حيث تعرّف الجانبين، ويشير كل جانب إلى الآخر. - -هناك نوعان من العلاقات: - -| نوع العلاقة | الوصف | هل لديه مفتاح خارجي؟ | -| ------------- | ------------------------------------------------------ | ---------------------- | -| `MANY_TO_ONE` | تشير العديد من سجلات هذا الكائن إلى سجل واحد من الهدف | نعم (`joinColumnName`) | -| `ONE_TO_MANY` | يحتوي سجل واحد من هذا الكائن على العديد من سجلات الهدف | لا (الجانب العكسي) | - -#### كيف تعمل العلاقات - -تتطلّب كل علاقة **حقلين** يشيران إلى بعضهما البعض: - -1. جانب **MANY_TO_ONE** — يوجد على الكائن الذي يحمل المفتاح الخارجي -2. جانب **ONE_TO_MANY** — يوجد على الكائن الذي يملك المجموعة - -يستخدم كلا الحقلين `FieldType.RELATION` ويُحيل كلٌ منهما إلى الآخر عبر `relationTargetFieldMetadataUniversalIdentifier`. - -#### مثال: البطاقة البريدية لديها العديد من المستلمين - -افترض أن `PostCard` يمكن إرسالها إلى العديد من سجلات `PostCardRecipient`. ينتمي كل مستلم إلى بطاقة بريدية واحدة بالضبط. - -**الخطوة 1: عرّف جانب ONE_TO_MANY على PostCard** (جانب "الواحد"): - -```ts src/fields/post-card-recipients-on-post-card.field.ts -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; -// Import from the other side -import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; - -export default defineField({ - universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCardRecipients', - label: 'Post Card Recipients', - icon: 'IconUsers', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); -``` - -**الخطوة 2: عرّف جانب MANY_TO_ONE على PostCardRecipient** (جانب "العديد" — يحمل المفتاح الخارجي): - -```ts src/fields/post-card-on-post-card-recipient.field.ts -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; -// Import from the other side -import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; - -export default defineField({ - universalIdentifier: POST_CARD_FIELD_ID, - objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - icon: 'IconMail', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, -}); +your-app/ +├── src/ +│ ├── application-config.ts ← defineApplication (required, one per app) +│ ├── roles/ ← defineRole +│ ├── objects/ ← defineObject +│ ├── fields/ ← defineField +│ ├── logic-functions/ ← defineLogicFunction +│ ├── front-components/ ← defineFrontComponent +│ ├── skills/ ← defineSkill +│ ├── agents/ ← defineAgent +│ ├── views/ ← defineView +│ ├── navigation-menu-items/ ← defineNavigationMenuItem +│ └── page-layouts/ ← definePageLayout +├── public/ ← Static assets (images, icons) +└── package.json ``` -**الاستيرادات الدائرية:** كلا حقلي العلاقة يُحيل كلٌ منهما إلى `universalIdentifier` الخاص بالآخر. لتجنّب مشكلات الاستيراد الدائري، صدّر معرّفات الحقول كثوابت مسمّاة من كل ملف، واستوردها في الملف الآخر. يقوم نظام البناء بحلّها في وقت الترجمة. + **File organization is up to you.** Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. The folder structure above is a convention, not a requirement. -#### الربط مع الكائنات القياسية - -لإنشاء علاقة مع كائن Twenty مضمّن (Person, Company, etc.)، استخدم `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: - -```ts src/fields/person-on-self-hosting-user.field.ts -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; -import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; - -export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; -export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; - -export default defineField({ - universalIdentifier: PERSON_FIELD_ID, - objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - description: 'Person matching with the self hosting user', - isNullable: true, - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'personId', - }, -}); -``` - -#### خصائص حقل العلاقة - -| الخاصية | مطلوب | الوصف | -| ------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------- | -| `type` | نعم | يجب أن يكون `FieldType.RELATION` | -| `relationTargetObjectMetadataUniversalIdentifier` | نعم | قيمة `universalIdentifier` للكائن الهدف | -| `relationTargetFieldMetadataUniversalIdentifier` | نعم | قيمة `universalIdentifier` للحقل المطابق على الكائن الهدف | -| `universalSettings.relationType` | نعم | `RelationType.MANY_TO_ONE` أو `RelationType.ONE_TO_MANY` | -| `universalSettings.onDelete` | MANY_TO_ONE فقط | ماذا يحدث عند حذف السجل المشار إليه: `CASCADE`، `SET_NULL`، `RESTRICT`، أو `NO_ACTION` | -| `universalSettings.joinColumnName` | MANY_TO_ONE فقط | اسم عمود قاعدة البيانات للمفتاح الخارجي (مثل `postCardId`) | - -#### حقول العلاقات المضمّنة في defineObject - -يمكنك أيضًا تعريف حقول العلاقات مباشرةً داخل `defineObject()`. في هذه الحالة، احذف `objectUniversalIdentifier` — إذ يُورَّث من الكائن الأب: - -```ts -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCardRecipient', - // ... - fields: [ - { - universalIdentifier: POST_CARD_FIELD_ID, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, - }, - // ... other fields - ], -}); -``` - - - -كل ملف وظيفة يستخدم `defineLogicFunction()` لتصدير تكوين مع معالج ومشغّلات اختيارية. - -```ts src/logic-functions/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - httpRouteTriggerSettings: { - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: true, - }, - /*databaseEventTriggerSettings: { - eventName: 'people.created', - },*/ - /*cronTriggerSettings: { - pattern: '0 0 1 1 *', - },*/ -}); -``` - -أنواع المشغّلات المتاحة: -* **httpRoute**: يعرِض وظيفتك على مسار وطريقة HTTP **تحت نقطة النهاية `/s/`**: -> مثال: `path: '/post-card/create'` يمكن استدعاؤه عبر `https://your-twenty-server.com/s/post-card/create` -* **cron**: يشغّل وظيفتك على جدول باستخدام تعبير CRON. -* **databaseEvent**: يعمل على أحداث دورة حياة كائنات مساحة العمل. عندما تكون عملية الحدث هي `updated`، يمكن تحديد الحقول المحددة المراد الاستماع إليها في مصفوفة `updatedFields`. إذا تُركت غير معرّفة أو فارغة، فسيؤدي أي تحديث إلى تشغيل الدالة. -> مثال: `person.updated`، `*.created`، `company.*` - - -يمكنك أيضًا تنفيذ دالة يدويًا باستخدام CLI: - -```bash filename="Terminal" -yarn twenty exec -n create-new-post-card -p '{"key": "value"}' -``` - -```bash filename="Terminal" -yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -يمكنك متابعة السجلات باستخدام: - -```bash filename="Terminal" -yarn twenty logs -``` - - -#### حمولة مشغل المسار - -عندما يستدعي مُشغِّل المسار وظيفتك المنطقية، فإنها تتلقّى كائن `RoutePayload` الذي يتبع [صيغة AWS HTTP API v2](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). -استورد نوع `RoutePayload` من `twenty-sdk`: - -```ts -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; - -const handler = async (event: RoutePayload) => { - const { headers, queryStringParameters, pathParameters, body } = event; - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -يحتوي نوع `RoutePayload` على البنية التالية: - - | الخاصية | النوع | الوصف | مثال | - | ---------------------------- | ------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- | - | `headers` | `Record\` | رؤوس HTTP (فقط تلك المدرجة في `forwardedRequestHeaders`) | انظر القسم أدناه | - | `queryStringParameters` | `Record\` | معلمات سلسلة الاستعلام (تُضمّ القيم المتعددة باستخدام فواصل) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | - | `pathParameters` | `Record\` | معلمات المسار المستخرجة من نمط المسار | `/users/:id`, `/users/123` -> `{ id: '123' }` | - | `المحتوى` | `object \| null` | جسم الطلب المُحلَّل (JSON) | `{ id: 1 }` -> `{ id: 1 }` | - | `isBase64Encoded` | `قيمة منطقية` | ما إذا كان جسم الطلب مُرمَّزًا بترميز base64 | | - | `requestContext.http.method` | `string` | طريقة HTTP (GET, POST, PUT, PATCH, DELETE) | | - | `requestContext.http.path` | `string` | المسار الخام للطلب | | - - -#### forwardedRequestHeaders - -افتراضيًا، **لا** تُمرَّر رؤوس HTTP من الطلبات الواردة إلى دالتك المنطقية لأسباب أمنية. -للوصول إلى رؤوس محددة، أدرِجها في مصفوفة `forwardedRequestHeaders`: - -```ts -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - httpRouteTriggerSettings: { - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, -}); -``` - -في معالجك، يمكنك الوصول إلى الرؤوس المُمرَّرة بهذه الطريقة: - -```ts -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - -تُحوَّل أسماء الرؤوس إلى أحرف صغيرة. يمكنك الوصول إليها باستخدام مفاتيح بأحرف صغيرة (على سبيل المثال، `event.headers['content-type']`). - - -#### إتاحة دالة كأداة - -يمكن إتاحة الدوال المنطقية بوصفها **أدوات** لوكلاء الذكاء الاصطناعي وسير العمل. عند تمييز دالة كأداة، تصبح قابلة للاكتشاف بواسطة ميزات الذكاء الاصطناعي في Twenty ويمكن استخدامها في أتمتة سير العمل. - -لتمييز دالة منطقية كأداة، عيِّن `isTool: true`: - -```ts src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, -}); -``` - -النقاط الرئيسية: - -* يمكنك دمج `isTool` مع المشغِّلات — إذ يمكن للدالة أن تكون أداة (قابلة للاستدعاء من قِبل وكلاء الذكاء الاصطناعي) وأن تُشغَّل بواسطة الأحداث في الوقت نفسه. -* **`toolInputSchema`** (اختياري): كائن JSON Schema يصف المعلمات التي تقبلها دالتك. يُحسَب المخطط تلقائيًا من خلال تحليل ساكن للشيفرة المصدرية، ولكن يمكنك تعيينه صراحةً: - -```ts -export default defineLogicFunction({ - ..., - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - - -**اكتب `description` جيدًا.** يعتمد وكلاء الذكاء الاصطناعي على حقل `description` الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها. - - - - - -دالة ما بعد التثبيت هي دالة منطقية تعمل تلقائيًا بعد تثبيت تطبيقك على مساحة عمل. ينفّذه الخادم **بعد** مزامنة البيانات الوصفية للتطبيق وإنشاء عميل SDK، بحيث تكون مساحة العمل جاهزة تمامًا للاستخدام ويكون المخطط الجديد مطبَّقًا. تشمل حالات الاستخدام النموذجية تهيئة البيانات الافتراضية، وإنشاء السجلات الأولية، وتكوين إعدادات مساحة العمل، أو توفير الموارد على خدمات جهات خارجية. - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - shouldRunSynchronously: false, - handler, -}); -``` - -يمكنك أيضًا تنفيذ دالة ما بعد التثبيت يدويًا في أي وقت باستخدام CLI: - -```bash filename="Terminal" -yarn twenty exec --postInstall -``` - -النقاط الرئيسية: -* تستخدم دوال ما بعد التثبيت `definePostInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`). -* يتلقى المعالج `InstallPayload` يحتوي على `{ previousVersion?: string; newVersion: string }` — حيث إن `newVersion` هو الإصدار الجاري تثبيته، و`previousVersion` هو الإصدار الذي كان مُثبّتًا سابقًا (أو `undefined` عند التثبيت الأولي). استخدم هذه القيم للتمييز بين عمليات التثبيت الجديدة والترقيات ولتشغيل منطق الترحيل الخاص بالإصدار. -* **موعد تشغيل الخطاف**: في عمليات التثبيت الجديدة فقط، افتراضيًا. مرّر `shouldRunOnVersionUpgrade: true` إذا كنت تريد تشغيله أيضًا عند ترقية التطبيق من إصدار سابق. عند إغفاله، تكون القيمة الافتراضية للعلم `false`، وتتجاوز الترقيات هذا الخطاف. -* **نموذج التنفيذ — غير متزامن افتراضيًا، والتزامني اختياري**: يتحكّم العلم `shouldRunSynchronously` في كيفية تنفيذ ما بعد التثبيت. - * `shouldRunSynchronously: false` *(الإعداد الافتراضي)* — يتم **إدراج الخطاف في قائمة الرسائل** مع `retryLimit: 3` ويعمل بشكل غير متزامن داخل عامل عمل. يعود ردّ التثبيت بمجرد وضع المهمة في الطابور، لذا فإن معالجًا بطيئًا أو متعطلًا لا يحجب المستدعي. سيُجرِّب العامل إعادة المحاولة حتى ثلاث مرات. **استخدم هذا للمهام طويلة التشغيل** — بَذر مجموعات بيانات كبيرة، استدعاء واجهات برمجة تطبيقات خارجية بطيئة، تهيئة موارد خارجية، أو أي شيء قد يتجاوز نافذة استجابة HTTP المعقولة. - * `shouldRunSynchronously: true` — يُنفّذ الخطاف **ضمن تدفّق التثبيت مباشرةً** (نفس المنفِّذ كما قبل التثبيت). يَحجُب طلب التثبيت حتى ينتهي المعالج، وإذا رمى استثناءً، سيتلقى مستدعي التثبيت `POST_INSTALL_ERROR`. لا توجد محاولات إعادة تلقائية. **استخدم هذا للمهام السريعة التي يجب إكمالها قبل الاستجابة** — مثل إظهار خطأ تحقق للمستخدم، أو إعداد سريع سيعتمد عليه العميل مباشرةً بعد عودة نداء التثبيت. ضع في اعتبارك أن ترحيل البيانات الوصفية يكون قد طُبِّق بالفعل عند تشغيل ما بعد التثبيت، لذلك فإن فشل الوضع المتزامن **لا** يعيد التغييرات على المخطط إلى الوراء — بل يكتفي بإبراز الخطأ. -* تأكّد من أن معالجك قابل للتنفيذ المتكرر دون آثار جانبية. في الوضع غير المتزامن قد تُعيد قائمة الانتظار المحاولة حتى ثلاث مرات؛ وفي أي من الوضعين قد يعمل الخطاف مجددًا أثناء الترقيات عند ضبط `shouldRunOnVersionUpgrade: true`. -* متغيرات البيئة `APPLICATION_ID` و`APP_ACCESS_TOKEN` و`API_URL` متاحة داخل المعالج (كما في أي دالة منطق أخرى)، لذا يمكنك استدعاء واجهة Twenty API باستخدام رمز وصول للتطبيق مقيّد بنطاق تطبيقك. -* يُسمح بدالة ما بعد التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة. -* تُرفَق خصائص الدالة `universalIdentifier` و`shouldRunOnVersionUpgrade` و`shouldRunSynchronously` تلقائيًا ببيان التطبيق ضمن الحقل `postInstallLogicFunction` أثناء عملية البناء — ولا تحتاج إلى الإشارة إليها في `defineApplication()`. -* تم تعيين مهلة افتراضية إلى 300 ثانية (5 دقائق) للسماح بمهام الإعداد الأطول مثل تهيئة البيانات. -* **لا يُنفَّذ في وضع التطوير**: عند تسجيل تطبيق محليًا (عبر `yarn twenty dev`)، يتجاوز الخادم تدفّق التثبيت بالكامل ويُزامن الملفات مباشرةً عبر مراقِب CLI — لذا لن يعمل ما بعد التثبيت في وضع التطوير مطلقًا، بغضّ النظر عن `shouldRunSynchronously`. استخدم `yarn twenty exec --postInstall` لتشغيله يدويًا على مساحة عمل قيد التشغيل. - - - - -دالة ما قبل التثبيت هي دالة منطقية تعمل تلقائيًا أثناء التثبيت، **قبل تطبيق ترحيل البيانات الوصفية لمساحة العمل**. تتشارك نفس بنية الحمولة مع ما بعد التثبيت (`InstallPayload`)، لكنها موضوعة أبكر في تدفّق التثبيت كي تجهّز حالة يعتمد عليها الترحيل القادم — ومن الاستخدامات الشائعة: نسخ البيانات احتياطيًا، التحقق من التوافق مع المخطط الجديد، أو أرشفة السجلات التي ستُعاد هيكلتها أو ستُحذف. - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -يمكنك أيضًا تنفيذ دالة ما قبل التثبيت يدويًا في أي وقت باستخدام CLI: - -```bash filename="Terminal" -yarn twenty exec --preInstall -``` - -النقاط الرئيسية: -* تستخدم دوال ما قبل التثبيت `definePreInstallLogicFunction()` — نفس الإعدادات المتخصصة كما في ما بعد التثبيت، لكنها مرتبطة بموضع مختلف ضمن دورة الحياة. -* يتلقّى كلٌّ من معالجي ما قبل التثبيت وما بعد التثبيت النوع نفسه `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. استورده مرة واحدة وأعد استخدامه لكلا الخطافين. -* **موعد تشغيل الخطاف**: موضوع مباشرةً قبل ترحيل البيانات الوصفية لمساحة العمل (`synchronizeFromManifest`). قبل التنفيذ، يُشغِّل الخادم مزامنة "pared-down sync" ذات طابع إضافي فقط تقوم بتسجيل دالة ما قبل التثبيت للإصدار **الجديد** في البيانات الوصفية لمساحة العمل — دون لمس أي شيء آخر — ثم يُنفّذها. لأن هذه المزامنة «إضافية فقط»، تبقى كائنات وحقول وبيانات الإصدار السابق سليمة عند تشغيل معالجك: يمكنك قراءة حالة ما قبل الترحيل ونسخها احتياطيًا بأمان. -* **نموذج التنفيذ**: يُنفَّذ ما قبل التثبيت **بشكل متزامن** و**يحجب عملية التثبيت**. إذا رمى المعالج استثناءً، تُلغى عملية التثبيت قبل تطبيق أي تغييرات على المخطط — وتبقى مساحة العمل على الإصدار السابق بحالة متّسقة. هذا مقصود: ما قبل التثبيت هو فرصتك الأخيرة لرفض ترقية تنطوي على مخاطر. -* كما هو الحال مع ما بعد التثبيت، يُسمح بدالة ما قبل التثبيت واحدة فقط لكل تطبيق. تُربَط تلقائيًا ببيان التطبيق تحت `preInstallLogicFunction` أثناء عملية البناء. -* **لا يُنفَّذ في وضع التطوير**: كما في ما بعد التثبيت — يتم تجاوز تدفّق التثبيت بالكامل للتطبيقات المسجّلة محليًا، لذا لن يعمل ما قبل التثبيت مطلقًا عند `yarn twenty dev`. استخدم `yarn twenty exec --preInstall` لتشغيله يدويًا. - - - - -كلا الخطافين جزء من تدفّق التثبيت نفسه ويتلقّيان نفس `InstallPayload`. الاختلاف يكمن في **موعد** تشغيلهما نسبةً إلى ترحيل البيانات الوصفية لمساحة العمل، وهذا يغيّر البيانات التي يمكنهما التعامل معها بأمان. +## Entity types + +| كيان | الغرض | وثائق | +| ------------------------ | ----------------------------------------- | ------------------------------------------------------------ | +| **Application** | App identity, permissions, variables | [Data Model](/l/ar/developers/extend/apps/data-model) | +| **Role** | Permission sets for objects and fields | [Data Model](/l/ar/developers/extend/apps/data-model) | +| **الكائن** | Custom data tables with fields | [Data Model](/l/ar/developers/extend/apps/data-model) | +| **الحقل** | Extend existing objects, define relations | [Data Model](/l/ar/developers/extend/apps/data-model) | +| **Logic Function** | Server-side TypeScript with triggers | [الوظائف المنطقية](/l/ar/developers/extend/apps/logic-functions) | +| **Front Component** | Sandboxed React UI in Twenty's page | [Front Components](/l/ar/developers/extend/apps/front-components) | +| **Skill** | Reusable AI agent instructions | [Skills & Agents](/l/ar/developers/extend/apps/skills-and-agents) | +| **Agent** | AI assistants with custom prompts | [Skills & Agents](/l/ar/developers/extend/apps/skills-and-agents) | +| **View** | Pre-configured record list views | [Layout](/l/ar/developers/extend/apps/layout) | +| **Navigation Menu Item** | Custom sidebar entries | [Layout](/l/ar/developers/extend/apps/layout) | +| **Page Layout** | Custom record page tabs and widgets | [Layout](/l/ar/developers/extend/apps/layout) | + +## Sandboxing + +* **Logic functions** run in isolated Node.js processes on the server. They only access data through the typed API client, scoped to the app's role permissions. +* **Front components** run in Web Workers using Remote DOM — sandboxed from the main page but rendering native DOM elements (not iframes). They communicate with Twenty via a message-passing host API. +* **Permissions** are enforced at the API level. The runtime token (`TWENTY_APP_ACCESS_TOKEN`) is derived from the role defined in `defineApplication()`. + +## App lifecycle ``` -┌─────────────────────────────────────────────────────────────┐ -│ install flow │ -│ │ -│ upload package → [pre-install] → metadata migration → │ -│ generate SDK → [post-install] │ -│ │ -│ old schema visible new schema visible │ -└─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────┐ +│ Development │ +│ npx create-twenty-app → yarn twenty dev (live sync) │ +├─────────────────────────────────────────────────────────┤ +│ Build & Deploy │ +│ yarn twenty build → yarn twenty deploy │ +├─────────────────────────────────────────────────────────┤ +│ Install flow │ +│ upload → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +├─────────────────────────────────────────────────────────┤ +│ Publish │ +│ npm publish → appears in Twenty marketplace │ +└─────────────────────────────────────────────────────────┘ ``` -ما قبل التثبيت دائمًا **متزامن** (يحجب التثبيت ويمكنه إحباطه). ما بعد التثبيت **غير متزامن افتراضيًا** — يُدرج على عامل مع محاولات إعادة تلقائية — لكن يمكن التبديل إلى تنفيذ متزامن عبر `shouldRunSynchronously: true`. راجع الأكورديون `definePostInstallLogicFunction` أعلاه لمعرفة متى تستخدم كل وضع. - -**استخدم `post-install` لأي شيء يتطلّب وجود المخطط الجديد.** وهذا هو السيناريو الشائع: - -* بَذر بيانات افتراضية (إنشاء سجلات أولية وعروض افتراضية ومحتوى تجريبي) للكائنات والحقول المضافة حديثًا. -* تسجيل خطافات الويب مع خدمات أطراف ثالثة بعد أن حصل التطبيق على بيانات الاعتماد الخاصة به. -* استدعاء واجهة برمجة التطبيقات الخاصة بك لإكمال إعداد يعتمد على البيانات الوصفية المتزامنة. -* منطق idempotent لتحقيق "تأكّد من وجود هذا" والذي ينبغي مواءمة الحالة في كل ترقية — بالاقتران مع `shouldRunOnVersionUpgrade: true`. - -مثال — بَذر سجل `PostCard` افتراضي بعد التثبيت: - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion }: InstallPayload): Promise => { - if (previousVersion) return; // fresh installs only - - const client = createClient(); - await client.postCard.create({ - data: { title: 'Welcome to Postcard', content: 'Your first card!' }, - }); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Seeds a welcome post card after install.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - handler, -}); -``` - -**استخدم `pre-install` عندما قد يُتلف الترحيل أو يدمّر البيانات الحالية.** لأن ما قبل التثبيت يعمل مقابل المخطط *السابق* وفشله يُرجِع الترقية إلى الوراء، فهو المكان المناسب لأي شيء محفوف بالمخاطر: - -* **نسخ البيانات احتياطيًا قبل حذفها أو إعادة هيكلتها** — مثل إزالة حقل في v2 وتحتاج إلى نسخ قيمه إلى حقل آخر أو تصديرها إلى التخزين قبل تشغيل الترحيل. -* **أرشفة السجلات التي سيبطلها قيد جديد** — مثل أن يصبح حقل ما `NOT NULL` وتحتاج أولًا إلى حذف الصفوف ذات القيم الفارغة أو إصلاحها. -* **التحقق من التوافق ورفض الترقية إذا تعذّر ترحيل البيانات الحالية بسلاسة** — ارمِ من داخل المعالج وسيُلغى التثبيت دون تطبيق أي تغييرات. هذا أكثر أمانًا من اكتشاف عدم التوافق في منتصف الترحيل. -* **إعادة تسمية البيانات أو إعادة تعيين مفاتيحها** قبل تغيير في المخطط قد يؤدي إلى فقدان الارتباط. - -مثال — أرشف السجلات قبل ترحيل هدّام: - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { - // Only the 1.x → 2.x upgrade drops the legacy `notes` field. - if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { - return; - } - - const client = createClient(); - const legacyRecords = await client.postCard.findMany({ - where: { notes: { isNotNull: true } }, - }); - - if (legacyRecords.length === 0) return; - - // Copy legacy `notes` into the new `description` field before the migration - // drops the `notes` column. If this fails, the upgrade is aborted and the - // workspace stays on v1 with all data intact. - await Promise.all( - legacyRecords.map((record) => - client.postCard.update({ - where: { id: record.id }, - data: { description: record.notes }, - }), - ), - ); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Backs up legacy notes into description before the v2 migration.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -**قاعدة عامة:** - -| ترغب في… | استخدام | -| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | -| بذر بيانات افتراضية، تهيئة مساحة العمل، تسجيل موارد خارجية | `post-install` | -| تشغيل بذر طويل الأمد أو استدعاءات أطراف ثالثة لا ينبغي أن تحجب استجابة التثبيت | `post-install` (الإعداد الافتراضي — `shouldRunSynchronously: false`، مع محاولات إعادة من العامل) | -| تشغيل إعداد سريع سيعتمد عليه المستدعي مباشرةً بعد عودة نداء التثبيت | `post-install` مع `shouldRunSynchronously: true` | -| قراءة البيانات أو نسخها احتياطيًا والتي قد يفقدها الترحيل القادم | `pre-install` | -| رفض ترقية قد تُفسد البيانات الحالية | `pre-install` (ارمِ من المعالج) | -| تنفيذ مواءمة في كل ترقية | `post-install` مع `shouldRunOnVersionUpgrade: true` | -| تنفيذ إعداد لمرة واحدة في التثبيت الأول فقط | `post-install` مع `shouldRunOnVersionUpgrade: false` (الإعداد الافتراضي) | - - -إذا ساورك الشك، فاجعل الافتراضي هو **post-install**. الجأ إلى ما قبل التثبيت فقط عندما يكون الترحيل نفسه هدّامًا وتحتاج إلى التقاط الحالة السابقة قبل أن تزول. - - - - - -المكوّنات الأمامية هي مكوّنات React تُعرَض مباشرة داخل واجهة مستخدم Twenty. تعمل ضمن **Web Worker** معزول باستخدام Remote DOM — تكون شيفرتك في صندوق عزل لكنها تُعرَض أصيلًا داخل الصفحة، وليس ضمن iframe. - -#### أين يمكن استخدام مكوّنات الواجهة الأمامية - -يمكن عرض مكوّنات الواجهة الأمامية في موقعين داخل Twenty: - -* **اللوحة الجانبية** — المكوّنات غير عديمة الرأس تفتح في اللوحة الجانبية اليمنى. هذا هو السلوك الافتراضي عندما يتم تشغيل مكوّن واجهة أمامية من قائمة الأوامر. -* **الويدجت (لوحات المعلومات وصفحات السجلات)** — يمكن تضمين مكوّنات الواجهة الأمامية كويدجت داخل تخطيطات الصفحات. عند تكوين لوحة معلومات أو تخطيط صفحة سجل، يمكن للمستخدمين إضافة ويدجت لمكوّن واجهة أمامية. - -#### مثال أساسي - -أسرع طريقة لرؤية مكوّن أمامي قيد العمل هي تسجيله كأمر. إضافة حقل `command` مع `isPinned: true` يجعلُه يظهر كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة — دون الحاجة إلى تخطيط صفحة: - -```tsx src/front-components/hello-world.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; - -const HelloWorld = () => { - return ( -
-

Hello from my app!

-

This component renders inside Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', - name: 'hello-world', - description: 'A simple front component', - component: HelloWorld, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', - shortLabel: 'Hello', - label: 'Hello World', - icon: 'IconBolt', - isPinned: true, - availabilityType: 'GLOBAL', - }, -}); -``` - -بعد المزامنة باستخدام `yarn twenty dev` (أو تشغيل الأمر لمرة واحدة `yarn twenty dev --once`)، يظهر الإجراء السريع في الزاوية العلوية اليمنى من الصفحة: - -
- زر إجراء سريع في الزاوية العلوية اليمنى -
- -انقره لعرض المكوّن مضمنًا داخل الصفحة. - -{/* TODO: add screenshot of the rendered front component */} - -#### حقول التكوين - -| الحقل | مطلوب | الوصف | -| --------------------- | ----- | ----------------------------------------------------------------- | -| `universalIdentifier` | نعم | معرّف فريد ثابت لهذا المكوّن | -| `component` | نعم | دالة مكوّن React | -| `name` | لا | اسم العرض | -| `الوصف` | لا | وصف لما يفعله المكوّن | -| `isHeadless` | لا | عيِّنه إلى `true` إذا كان المكوّن بلا واجهة مرئية (انظر أدناه) | -| `أمر` | لا | سجّل المكوّن كأمر (انظر [خيارات الأوامر](#command-options) أدناه) | - -#### وضع مكوّن أمامي على صفحة - -إضافةً إلى الأوامر، يمكنك تضمين مكوّن أمامي مباشرةً في صفحة سجل عبر إضافته كودجت في **تخطيط صفحة**. راجع قسم [definePageLayout](#definepagelayout) للتفاصيل. - -#### عديم الرأس مقابل غير عديم الرأس - -تأتي مكوّنات الواجهة الأمامية بوضعَي عرض يتحكّم بهما الخيار `isHeadless`: - -**غير عديم الرأس (افتراضي)** — يعرض المكوّن واجهة مستخدم مرئية. عند تشغيله من قائمة الأوامر يفتح في اللوحة الجانبية. هذا هو السلوك الافتراضي عندما تكون `isHeadless` تساوي `false` أو يتم تجاهلها. - -**عديم الرأس (`isHeadless: true`)** — يتم تركيب المكوّن بشكل غير مرئي في الخلفية. لا يفتح اللوحة الجانبية. تم تصميم المكوّنات عديمة الرأس لإجراءات تنفّذ منطقًا ثم تُزيل تركيبها ذاتيًا — على سبيل المثال، تشغيل مهمة غير متزامنة، أو الانتقال إلى صفحة، أو إظهار نافذة تأكيد منبثقة. تتوافق بشكل طبيعي مع مكوّنات Command في SDK الموصوفة أدناه. - -```tsx src/front-components/sync-tracker.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; -import { useEffect } from 'react'; - -const SyncTracker = () => { - const recordId = useRecordId(); - - useEffect(() => { - enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); - }, [recordId]); - - return null; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'sync-tracker', - description: 'Tracks record views silently', - isHeadless: true, - component: SyncTracker, -}); -``` - -نظرًا لأن المكوّن يُرجع `null`، فإن Twenty يتخطّى عرض حاوية له — ولن تظهر مساحة فارغة في التخطيط. لا يزال لدى المكوّن إمكانية الوصول إلى جميع الخطافات وواجهة برمجة الاتصال مع المضيف. - -#### مكوّنات Command في SDK - -توفر حزمة `twenty-sdk` أربعة مكوّنات مساعدة من نوع Command مصممة للمكوّنات عديمة الرأس في الواجهة الأمامية. كل مكوّن ينفّذ إجراءً عند التركيب، ويتعامل مع الأخطاء بعرض إشعار Snackbar، ويزيل تركيب مكوّن الواجهة الأمامية تلقائيًا عند الانتهاء. - -استوردها من `twenty-sdk/command`: - -* **`Command`** — يشغّل رد نداء غير متزامن عبر الخاصية `execute`. -* **`CommandLink`** — ينتقل إلى مسار في التطبيق. الخصائص: `to`، `params`، `queryParams`، `options`. -* **`CommandModal`** — يفتح نافذة تأكيد منبثقة. إذا أكّد المستخدم، ينفّذ رد النداء `execute`. الخصائص: `title`، `subtitle`، `execute`، `confirmButtonText`، `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — يفتح صفحة محدّدة في اللوحة الجانبية. الخصائص: `page`، `pageTitle`، `pageIcon`. - -فيما يلي مثال كامل لمكوّن واجهة أمامية عديم الرأس يستخدم `Command` لتشغيل إجراء من قائمة الأوامر: - -```tsx src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -ومثال يستخدم `CommandModal` لطلب التأكيد قبل التنفيذ: - -```tsx src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### الوصول إلى سياق وقت التشغيل - -داخل مكوّنك، استخدم خطافات SDK للوصول إلى المستخدم الحالي، والسجل، ومثيل المكوّن: - -```tsx src/front-components/record-info.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - useUserId, - useRecordId, - useFrontComponentId, -} from 'twenty-sdk/front-component'; - -const RecordInfo = () => { - const userId = useUserId(); - const recordId = useRecordId(); - const componentId = useFrontComponentId(); - - return ( -
-

User: {userId}

-

Record: {recordId ?? 'No record context'}

-

Component: {componentId}

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', - name: 'record-info', - component: RecordInfo, -}); -``` - -الخطافات المتاحة: - -| الخطّاف | القيم المعادة | الوصف | -| --------------------------------------------- | ------------------ | ---------------------------------------------- | -| `useUserId()` | `string` أو `null` | معرّف المستخدم الحالي | -| `useRecordId()` | `string` أو `null` | معرّف السجل الحالي (عند وضعه على صفحة سجل) | -| `useFrontComponentId()` | `string` | معرّف مثيل هذا المكوّن | -| `useFrontComponentExecutionContext(selector)` | يختلف | الوصول إلى سياق التنفيذ الكامل عبر دالة محدِّد | - -#### واجهة الاتصال مع المضيف - -يمكن للمكوّنات الأمامية تشغيل التنقّل والنوافذ المنبثقة والإشعارات باستخدام دوال من `twenty-sdk`: - -| دالة | الوصف | -| ----------------------------------------------- | ------------------------------ | -| `navigate(to, params?, queryParams?, options?)` | الانتقال إلى صفحة داخل التطبيق | -| `openSidePanelPage(params)` | فتح لوحة جانبية | -| `closeSidePanel()` | إغلاق اللوحة الجانبية | -| `openCommandConfirmationModal(params)` | عرض مربع حوار تأكيد | -| `enqueueSnackbar(params)` | عرض إشعار توست | -| `unmountFrontComponent()` | إلغاء تركيب المكوّن | -| `updateProgress(progress)` | تحديث مؤشّر التقدّم | - -فيما يلي مثال يستخدم واجهة برمجة تطبيقات المضيف لعرض Snackbar وإغلاق اللوحة الجانبية بعد اكتمال الإجراء: - -```tsx src/front-components/archive-record.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId } from 'twenty-sdk/front-component'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -#### خيارات الأوامر - -إضافة حقل `command` إلى `defineFrontComponent` تُسجِّل المكوّن في قائمة الأوامر (Cmd+K). إذا كانت قيمة `isPinned` هي `true`، فسيظهر أيضًا كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة. - -| الحقل | مطلوب | الوصف | -| --------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `universalIdentifier` | نعم | معرّف فريد ثابت للأمر | -| `التسمية` | نعم | التسمية الكاملة المعروضة في قائمة الأوامر (Cmd+K) | -| `shortLabel` | لا | تسمية أقصر تُعرَض على زر الإجراء السريع المثبّت | -| `أيقونة` | لا | اسم الأيقونة المعروض بجانب التسمية (مثل `'IconBolt'` و`'IconSend'`) | -| `isPinned` | لا | عند كونها `true`، يعرض الأمر كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة | -| `availabilityType` | لا | تتحكّم في مكان ظهور الأمر: `'GLOBAL'` (متاح دائمًا)، و`'RECORD_SELECTION'` (فقط عند تحديد سجلات)، أو `'FALLBACK'` (يُعرَض عند عدم تطابق أي أوامر أخرى) | -| `availabilityObjectUniversalIdentifier` | لا | تقييد الأمر بصفحات نوع كائن معيّن (مثل سجلات Company فقط) | -| `conditionalAvailabilityExpression` | لا | تعبير منطقي للتحكم ديناميكيًا في ما إذا كان الأمر مرئيًا (انظر أدناه) | - -#### تعابير الإتاحة الشرطية - -يتيح لك الحقل `conditionalAvailabilityExpression` التحكّم في وقت ظهور الأمر بناءً على سياق الصفحة الحالي. استورد متغيّرات ومشغّلات مضبوطة الأنواع من `twenty-sdk` لبناء التعابير: - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - pageType, - numberOfSelectedRecords, - objectPermissions, - everyEquals, - isDefined, -} from 'twenty-sdk/front-component'; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'bulk-action', - component: BulkAction, - command: { - universalIdentifier: '...', - label: 'Bulk Update', - availabilityType: 'RECORD_SELECTION', - conditionalAvailabilityExpression: everyEquals( - objectPermissions, - 'canUpdateObjectRecords', - true, - ), - }, -}); -``` - -**متغيّرات السياق** — تُمثّل الحالة الحالية للصفحة: - -| المتغيّر | النوع | الوصف | -| ------------------------------ | ------------- | --------------------------------------------------------------- | -| `pageType` | `string` | نوع الصفحة الحالي (مثل `'RecordIndexPage'` و`'RecordShowPage'`) | -| `isInSidePanel` | `قيمة منطقية` | ما إذا كان المكوّن معروضًا في لوحة جانبية | -| `numberOfSelectedRecords` | `رقم` | عدد السجلات المحدّدة حاليًا | -| `isSelectAll` | `قيمة منطقية` | ما إذا كان "تحديد الكل" مفعّلًا | -| `selectedRecords` | `array` | كائنات السجلات المحدّدة | -| `favoriteRecordIds` | `array` | معرّفات السجلات المفضّلة | -| `objectPermissions` | `الكائن` | الأذونات الخاصة بنوع الكائن الحالي | -| `targetObjectReadPermissions` | `الكائن` | أذونات القراءة للكائن الهدف | -| `targetObjectWritePermissions` | `الكائن` | أذونات الكتابة للكائن الهدف | -| `featureFlags` | `الكائن` | أعلام الميزات المفعَّلة | -| `objectMetadataItem` | `الكائن` | بيانات التعريف لنوع الكائن الحالي | -| `hasAnySoftDeleteFilterOnView` | `قيمة منطقية` | ما إذا كان العرض الحالي يحتوي على مرشّح حذف منطقي | - -**المُشغِّلات** — جمّع المتغيّرات في تعابير منطقية: - -| المُشغِّل | الوصف | -| ----------------------------------- | -------------------------------------------------------------- | -| `isDefined(value)` | `true` إذا لم تكن القيمة null/undefined | -| `isNonEmptyString(value)` | `true` إذا كانت القيمة سلسلة غير فارغة | -| `includes(array, value)` | `true` إذا كانت المصفوفة تحتوي على القيمة | -| `includesEvery(array, prop, value)` | `true` إذا كانت خاصية كل عنصر تتضمن القيمة | -| `every(array, prop)` | `true` إذا كانت الخاصية تُقيَّم بصحّة في كل عنصر | -| `everyDefined(array, prop)` | `true` إذا كانت الخاصية معرّفة في كل عنصر | -| `everyEquals(array, prop, value)` | `true` إذا كانت الخاصية تساوي القيمة في كل عنصر | -| `some(array, prop)` | `true` إذا كانت الخاصية تُقيَّم بصحّة في عنصر واحد على الأقل | -| `someDefined(array, prop)` | `true` إذا كانت الخاصية معرّفة في عنصر واحد على الأقل | -| `someEquals(array, prop, value)` | `true` إذا كانت الخاصية تساوي القيمة في عنصر واحد على الأقل | -| `someNonEmptyString(array, prop)` | `true` إذا كانت الخاصية سلسلة غير فارغة في عنصر واحد على الأقل | -| `none(array, prop)` | `true` إذا كانت الخاصية تُقيَّم بخطأ في كل عنصر | -| `noneDefined(array, prop)` | `true` إذا كانت الخاصية غير معرّفة في كل عنصر | -| `noneEquals(array, prop, value)` | `true` إذا لم تكن الخاصية تساوي القيمة في أي عنصر | - -#### الأصول العامة - -يمكن للمكوّنات الأمامية الوصول إلى ملفات من دليل `public/` للتطبيق باستخدام `getPublicAssetUrl`: - -```tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -const Logo = () => Logo; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'logo', - component: Logo, -}); -``` - -راجع [قسم الأصول العامة](#accessing-public-assets-with-getpublicasseturl) للتفاصيل. - -#### التنسيق - -تدعم المكوّنات الأمامية عدة أساليب للتنسيق. يمكنك استخدام: - -* **أنماط مضمنة** — `style={{ color: 'red' }}` -* **مكوّنات Twenty لواجهة المستخدم** — استورد من `twenty-sdk/ui` (Button وTag وStatus وChip وAvatar وغيرها) -* **Emotion** — CSS-in-JS مع `@emotion/react` -* **Styled-components** — أنماط `styled.div` -* **Tailwind CSS** — أصناف مساعدة -* **أي مكتبة CSS-in-JS** متوافقة مع React - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Button, Tag, Status } from 'twenty-sdk/ui'; - -const StyledWidget = () => { - return ( -
-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', - name: 'styled-widget', - component: StyledWidget, -}); -``` - -
- - - -تُحدِّد المهارات تعليمات وإمكانات قابلة لإعادة الاستخدام يمكن لوكلاء الذكاء الاصطناعي استخدامها داخل مساحة العمل لديك. استخدم `defineSkill()` لتعريف مهارات مع تحقّق مدمج: - -```ts src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk/define'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -النقاط الرئيسية: -* `name` هي سلسلة معرّف فريدة للمهارة (يُنصَح باستخدام kebab-case). -* `label` هو اسم العرض المقروء للبشر الظاهر في واجهة المستخدم. -* `content` يحتوي على تعليمات المهارة — وهو النص الذي يستخدمه وكيل الذكاء الاصطناعي. -* `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم. -* `description` (اختياري) يوفّر سياقًا إضافيًا حول غرض المهارة. - - - - -الوكلاء هم مساعدون ذكاء اصطناعي يعيشون داخل مساحة العمل لديك. استخدم `defineAgent()` لإنشاء وكلاء بموجه نظام مخصّص: - -```ts src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk/define'; - -export default defineAgent({ - universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'Helps the sales team draft outreach emails and research prospects', - icon: 'IconRobot', - prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', -}); -``` - -النقاط الرئيسية: -* `name` هي سلسلة معرّف فريدة للوكيل (يُنصح باستخدام kebab-case). -* `label` هو اسم العرض الظاهر في واجهة المستخدم. -* `prompt` هو موجه النظام الذي يحدّد سلوك الوكيل. -* `description` (اختياري) يوفّر سياقًا حول ما يفعله الوكيل. -* `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم. -* `modelId` (اختياري) يتجاوز نموذج الذكاء الاصطناعي الافتراضي الذي يستخدمه الوكيل. - - - - -العروض هي تكوينات محفوظة لكيفية عرض سجلات كائن ما — بما في ذلك الحقول المرئية وترتيبها وأي مرشّحات أو مجموعات مُطبَّقة. استخدم `defineView()` لتضمين عروض مُهيّأة مسبقًا مع تطبيقك: - -```ts src/views/example-view.ts -import { defineView, ViewKey } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; - -export default defineView({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'All example items', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - icon: 'IconList', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', - fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 200, - }, - ], -}); -``` - -النقاط الرئيسية: -* `objectUniversalIdentifier` يحدّد الكائن الذي ينطبق عليه هذا العرض. -* `key` يحدّد نوع العرض (مثل `ViewKey.INDEX` لعرض القائمة الرئيسي). -* `fields` يتحكّم في الأعمدة الظاهرة وترتيبها. يشير كل حقل إلى `fieldMetadataUniversalIdentifier`. -* يمكنك أيضًا تعريف `filters` و`filterGroups` و`groups` و`fieldGroups` لمزيد من التكوينات المتقدمة. -* `position` يتحكّم في الترتيب عند وجود عدة عروض لنفس الكائن. - - - - -تضيف عناصر قائمة التنقل إدخالات مخصّصة إلى الشريط الجانبي لمساحة العمل. استخدم `defineNavigationMenuItem()` للارتباط بالعروض أو عناوين URL خارجية أو الكائنات: - -```ts src/navigation-menu-items/example-navigation-menu-item.ts -import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; -import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; - -export default defineNavigationMenuItem({ - universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', - name: 'example-navigation-menu-item', - icon: 'IconList', - color: 'blue', - position: 0, - type: NavigationMenuItemType.VIEW, - viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, -}); -``` - -النقاط الرئيسية: -* `type` يحدّد إلى ماذا يرتبط عنصر القائمة: `NavigationMenuItemType.VIEW` لعرض محفوظ، أو `NavigationMenuItemType.LINK` لعنوان URL خارجي. -* لروابط العروض، عيِّن `viewUniversalIdentifier`. لروابط خارجية، عيِّن `link`. -* `position` يتحكّم في الترتيب ضمن الشريط الجانبي. -* `icon` و`color` (اختياريان) يخصّصان المظهر. - - - - -تتيح لك تخطيطات الصفحات تخصيص مظهر صفحة تفاصيل السجل — ما الألسنة التي تظهر، وما الويدجتات داخل كل لسان، وكيف يتم ترتيبها. استخدم `definePageLayout()` لتضمين تخطيطات مخصّصة مع تطبيقك: - -```ts src/page-layouts/example-record-page-layout.ts -import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; - -export default definePageLayout({ - universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', - name: 'Example Record Page', - type: 'RECORD_PAGE', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - tabs: [ - { - universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', - title: 'Hello World', - position: 50, - icon: 'IconWorld', - layoutMode: PageLayoutTabLayoutMode.CANVAS, - widgets: [ - { - universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', - title: 'Hello World', - type: 'FRONT_COMPONENT', - configuration: { - configurationType: 'FRONT_COMPONENT', - frontComponentUniversalIdentifier: - HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, - }, - }, - ], - }, - ], -}); -``` - -النقاط الرئيسية: -* `type` يكون عادة `'RECORD_PAGE'` لتخصيص عرض التفاصيل لكائن محدّد. -* `objectUniversalIdentifier` يحدّد الكائن الذي ينطبق عليه هذا التخطيط. -* يُعرّف كل `tab` قسمًا من الصفحة مع `title` و`position` و`layoutMode` (`CANVAS` لتخطيط حرّ). -* يمكن لكل `widget` داخل لسان أن يعرض مكوّنًا أماميًا أو قائمة علاقات أو أنواع ويدجت مدمجة أخرى. -* `position` على الألسنة يتحكّم في ترتيبها. استخدم قيمًا أعلى (مثل 50) لوضع الألسنة المخصّصة بعد الألسنة المدمجة. - - -
- -## الأصول العامة (مجلد `public/`) - -يحتوي مجلد `public/` في جذر تطبيقك على ملفات ثابتة — صور وأيقونات وخطوط وأي أصول أخرى يحتاجها تطبيقك وقت التشغيل. تُدرج هذه الملفات تلقائيًا في عمليات البناء، وتُزامَن أثناء وضع التطوير، وتُرفَع إلى الخادم. - -الملفات الموضوعة في `public/` هي: - -* **متاحة للعامة** — بمجرد مزامنتها إلى الخادم، تُقدَّم الأصول عبر عنوان URL عام. لا يلزم توثيق للوصول إليها. -* **متاحة في المكوّنات الأمامية** — استخدم عناوين الأصول لعرض الصور أو الأيقونات أو أي وسائط داخل مكوّنات React لديك. -* **متاحة في الدوال المنطقية** — أشِر إلى عناوين الأصول في رسائل البريد الإلكتروني أو استجابات واجهات البرمجة أو أي منطق على جهة الخادم. -* **مستخدمة لبيانات تعريف السوق** — يشير حقلا `logoUrl` و`screenshots` في `defineApplication()` إلى ملفات من هذا المجلد (مثل `public/logo.png`). تُعرَض هذه عند نشر تطبيقك في السوق. -* **تُزامَن تلقائيًا في وضع التطوير** — عند إضافة ملف في `public/` أو تحديثه أو حذفه، تتم مزامنته إلى الخادم تلقائيًا. لا حاجة لإعادة التشغيل. -* **مضمَّنة في عمليات البناء** — يقوم `yarn twenty build` بتجميع جميع الأصول العامة ضمن مخرجات التوزيع. - -### الوصول إلى الأصول العامة باستخدام `getPublicAssetUrl` - -استخدم المساعد `getPublicAssetUrl` من `twenty-sdk` للحصول على العنوان الكامل لملف في دليل `public/` لديك. يعمل ذلك في كلٍ من الدوال المنطقية والمكوّنات الأمامية. - -**في دالة منطقية:** - -```ts src/logic-functions/send-invoice.ts -import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; - -const handler = async (): Promise => { - const logoUrl = getPublicAssetUrl('logo.png'); - const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); - - // Fetch the file content (no auth required — public endpoint) - const response = await fetch(invoiceUrl); - const buffer = await response.arrayBuffer(); - - return { logoUrl, size: buffer.byteLength }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'a1b2c3d4-...', - name: 'send-invoice', - description: 'Sends an invoice with the app logo', - timeoutSeconds: 10, - handler, -}); -``` - -**في مكوّن أمامي:** - -```tsx src/front-components/company-card.tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -export default defineFrontComponent(() => { - const logoUrl = getPublicAssetUrl('logo.png'); - - return App logo; -}); -``` - -وسيطة `path` نسبية إلى مجلد `public/` الخاص بتطبيقك. كلٌّ من `getPublicAssetUrl('logo.png')` و`getPublicAssetUrl('public/logo.png')` يُحلاّن إلى العنوان نفسه — تتم إزالة بادئة `public/` تلقائيًا إن وُجدت. - -## استخدام حِزَم npm - -يمكنك تثبيت واستخدام أي حزمة npm في تطبيقك. يتم تجميع كلٍ من الدوال المنطقية والمكوّنات الأمامية باستخدام [esbuild](https://esbuild.github.io/)، والذي يُضمّن جميع التبعيات ضمن المخرجات — لا حاجة إلى `node_modules` وقت التشغيل. - -### تثبيت حزمة - -```bash filename="Terminal" -yarn add axios -``` - -ثم استوردها في شيفرتك: - -```ts src/logic-functions/fetch-data.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import axios from 'axios'; - -const handler = async (): Promise => { - const { data } = await axios.get('https://api.example.com/data'); - - return { data }; -}; - -export default defineLogicFunction({ - universalIdentifier: '...', - name: 'fetch-data', - description: 'Fetches data from an external API', - timeoutSeconds: 10, - handler, -}); -``` - -وينطبق الأمر نفسه على المكوّنات الأمامية: - -```tsx src/front-components/chart.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { format } from 'date-fns'; - -const DateWidget = () => { - return

Today is {format(new Date(), 'MMMM do, yyyy')}

; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### كيف يعمل التجميع - -تستخدم خطوة البناء أداة esbuild لإنتاج ملف واحد مستقل لكل دالة منطقية ولكل مكوّن أمامي. تُضمَّن جميع الحزم المستوردة داخل الحزمة. - -**الدوال المنطقية** تعمل في بيئة Node.js. الوحدات المدمجة في Node (`fs` و`path` و`crypto` و`http` وغيرها) متاحة ولا تحتاج إلى تثبيت. - -**المكوّنات الأمامية** تعمل ضمن Web Worker. وحدات Node المدمجة غير متاحة — المتاح فقط واجهات برمجة المتصفّح وحِزَم npm التي تعمل في بيئة المتصفّح. - -كلتا البيئتين تحتويان على `twenty-client-sdk/core` و`twenty-client-sdk/metadata` كوحدات متاحة مُسبقًا — لا تُضمَّن هذه ضمن الحزم بل تُحلّ وقت التشغيل بواسطة الخادم. - -## توليد قوالب الكيانات باستخدام `yarn twenty add` - -بدلًا من إنشاء ملفات الكيانات يدويًا، يمكنك استخدام أداة القوالب التفاعلية: - -```bash filename="Terminal" -yarn twenty add -``` - -ستطالبك باختيار نوع الكيان وتُرشدك خلال الحقول المطلوبة. تُولّد ملفًا جاهزًا للاستخدام مع `universalIdentifier` ثابت واستدعاء `defineEntity()` الصحيح. - -يمكنك أيضًا تمرير نوع الكيان مباشرة لتخطي المطالبة الأولى: - -```bash filename="Terminal" -yarn twenty add object -yarn twenty add logicFunction -yarn twenty add frontComponent -``` - -### أنواع الكيانات المتاحة - -| نوع الكيان | أمر | الملف المُولَّد | -| ------------------ | ------------------------------------ | ------------------------------------------------------- | -| كائن | `yarn twenty add object` | `src/objects/\.ts` | -| الحقل | `yarn twenty add field` | `src/fields/\.ts` | -| دالة منطقية | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | -| مكوّن أمامي | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | -| دور | `yarn twenty add role` | `src/roles/\.ts` | -| مهارة | `yarn twenty add skill` | `src/skills/\.ts` | -| وكيل | `yarn twenty add agent` | `src/agents/\.ts` | -| عرض | `yarn twenty add view` | `src/views/\.ts` | -| عنصر قائمة التنقّل | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | -| تخطيط الصفحة | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | - -### ما الذي تُنشئه أداة القوالب - -لكل نوع كيان قالب خاص به. على سبيل المثال، يسأل `yarn twenty add object` عن: - -1. **الاسم (مفرد)** — مثل `invoice` -2. **الاسم (جمع)** — مثل `invoices` -3. **التسمية (مفرد)** — تُستمد تلقائيًا من الاسم (مثل `Invoice`) -4. **التسمية (جمع)** — تُملأ تلقائيًا (مثل `Invoices`) -5. **إنشاء عرض وعنصر تنقّل؟** — إذا أجبت بنعم، فستُنشئ أداة القوالب أيضًا عرضًا مطابقًا ورابط شريط جانبي للكائن الجديد. - -أنواع الكيانات الأخرى لها مطالبات أبسط — فمعظمها يطلب اسمًا فقط. - -نوع الكيان `field` أكثر تفصيلاً: يطلب اسم الحقل وتسمية الحقل ونوعه (من قائمة بكل أنواع الحقول المتاحة مثل `TEXT` و`NUMBER` و`SELECT` و`RELATION` وغيرها)، ومعرّف `universalIdentifier` للكائن الهدف. - -### مسار خرج مخصّص - -استخدم العلم `--path` لوضع الملف المُولَّد في موقع مخصّص: - -```bash filename="Terminal" -yarn twenty add logicFunction --path src/custom-folder -``` - -## عملاء واجهة برمجة تطبيقات مضبوطة الأنواع (`twenty-client-sdk`) - -توفر حزمة `twenty-client-sdk` عميلين لـ GraphQL ذوي أنواع ثابتة للتفاعل مع واجهة Twenty البرمجية من وظائفك المنطقية ومكوّنات الواجهة الأمامية. - -| العميل | استيراد | نقطة النهاية | مُولَّد؟ | -| ------------------- | ---------------------------- | --------------------------------------------------- | -------------------------- | -| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — بيانات مساحة العمل (السجلات، الكائنات) | نعم، في وقت التطوير/البناء | -| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — تكوين مساحة العمل، رفع الملفات | لا، يأتي مُجهزًا مسبقًا | - - - - -`CoreApiClient` هو العميل الرئيسي للاستعلام وتعديل بيانات مساحة العمل. يُولَّد من مخطط مساحة العمل لديك أثناء `yarn twenty dev` أو `yarn twenty build`، لذا فهو مضبوط الأنواع بالكامل ليتوافق مع كائناتك وحقولك. - -```ts -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const client = new CoreApiClient(); - -// Query records -const { companies } = await client.query({ - companies: { - edges: { - node: { - id: true, - name: true, - domainName: { - primaryLinkLabel: true, - primaryLinkUrl: true, - }, - }, - }, - }, -}); - -// Create a record -const { createCompany } = await client.mutation({ - createCompany: { - __args: { - data: { - name: 'Acme Corp', - }, - }, - id: true, - name: true, - }, -}); -``` - -يستخدم العميل صياغة مجموعة اختيار: مرِّر `true` لتضمين حقل، واستخدم `__args` للوسيطات، وعشّش الكائنات للعلاقات. ستحصل على إكمال تلقائي كامل وفحص للأنواع يعتمد على مخطط مساحة العمل لديك. - - -**يتم توليد CoreApiClient في وقت التطوير/البناء.** إذا استخدمته دون تشغيل `yarn twenty dev` أو `yarn twenty build` أولًا، فسيؤدي ذلك إلى خطأ. تحدث عملية التوليد تلقائيًا — إذ يستطلع CLI مخطط GraphQL لمساحة عملك وينشئ عميلًا مضبوط الأنواع باستخدام `@genql/cli`. - - -#### استخدام CoreSchema للتعليقات التوضيحية للأنواع - -`CoreSchema` يوفّر أنواع TypeScript المطابقة لكائنات مساحة العمل لديك — مفيد لتعيين أنواع حالة المكوّن أو معاملات الدوال: - -```ts -import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; -import { useState } from 'react'; - -const [company, setCompany] = useState< - Pick | undefined ->(undefined); - -const client = new CoreApiClient(); -const result = await client.query({ - company: { - __args: { filter: { position: { eq: 1 } } }, - id: true, - name: true, - }, -}); -setCompany(result.company); -``` - - - - -يأتي `MetadataApiClient` مُجهّزًا مسبقًا مع SDK (لا حاجة للتوليد). يستعلم عن نقطة النهاية `/metadata` للحصول على تكوين مساحة العمل والتطبيقات ورفع الملفات. - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const metadataClient = new MetadataApiClient(); - -// List first 10 objects in the workspace -const { objects } = await metadataClient.query({ - objects: { - edges: { - node: { - id: true, - nameSingular: true, - namePlural: true, - labelSingular: true, - isCustom: true, - }, - }, - __args: { - filter: {}, - paging: { first: 10 }, - }, - }, -}); -``` - -#### رفع الملفات - -يتضمن `MetadataApiClient` طريقة `uploadFile` لإرفاق الملفات بالحقول من نوع الملف: - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -| المعلمة | النوع | الوصف | -| ---------------------------------- | -------- | ---------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | المحتوى الخام للملف | -| `filename` | `string` | اسم الملف (يُستخدم للتخزين والعرض) | -| `contentType` | `string` | نوع MIME (القيمة الافتراضية `application/octet-stream` إذا لم يُحدَّد) | -| `fieldMetadataUniversalIdentifier` | `string` | قيمة `universalIdentifier` لحقل نوع الملف في كائنك | - -النقاط الرئيسية: -* يستخدم `universalIdentifier` الخاص بالحقل (وليس معرّفه الخاص بمساحة العمل)، بحيث يعمل كود الرفع لديك عبر أي مساحة عمل مُثبَّت فيها تطبيقك. -* العنوان `url` المُعاد هو عنوان URL موقّع يمكنك استخدامه للوصول إلى الملف المرفوع. - - - - - - عند تشغيل كودك على Twenty (وظائف منطقية أو مكوّنات أمامية)، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئية: - - * `TWENTY_API_URL` — عنوان URL الأساسي لواجهة Twenty البرمجية - * `TWENTY_APP_ACCESS_TOKEN` — مفتاح قصير العمر ذو نطاق يقتصر على الدور الافتراضي لوظيفة تطبيقك - - لست **بحاجة** إلى تمرير هذه القيم إلى العملاء — فهي تُقرأ تلقائيًا من `process.env`. تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `defaultRoleUniversalIdentifier` ضمن `application-config.ts`. - - -## اختبار تطبيقك - -يوفّر SDK واجهات برمجة قابلة للتنفيذ برمجيًا تمكّنك من بناء تطبيقك ونشره وتثبيته وإلغاء تثبيته من شيفرة الاختبار. بالاقتران مع [Vitest](https://vitest.dev/) وعملاء واجهة البرمجة مضبوطي الأنواع، يمكنك كتابة اختبارات تكامل تتحقّق من أن تطبيقك يعمل من البداية إلى النهاية مقابل خادم Twenty حقيقي. - -### إعداد - -يتضمّن التطبيق المُولَّد بالقالب بالفعل Vitest. إذا أعددته يدويًا، فثبّت التبعيات: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -أنشئ `vitest.config.ts` في جذر تطبيقك: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -أنشئ ملف إعداد يتحقّق من إمكانية الوصول إلى الخادم قبل تشغيل الاختبارات: - -```ts src/__tests__/setup-test.ts -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { beforeAll } from 'vitest'; - -const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; -const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### واجهات SDK البرمجية - -يُصدِّر المسار الفرعي `twenty-sdk/cli` دوالًا يمكنك استدعاؤها مباشرةً من شيفرة الاختبار: - -| دالة | الوصف | -| -------------- | ----------------------------------------- | -| `appBuild` | بناء التطبيق واختياريًا حزم ملف tarball | -| `appDeploy` | رفع ملف tarball إلى الخادم | -| `appInstall` | تثبيت التطبيق على مساحة العمل النشطة | -| `appUninstall` | إلغاء تثبيت التطبيق من مساحة العمل النشطة | - -تُرجع كل دالة كائن نتيجة يحتوي على `success: boolean` وعلى إمّا `data` أو `error`. - -### كتابة اختبار تكامل - -إليك مثالًا كاملًا يبني التطبيق وينشره ويثبّته، ثم يتحقّق من ظهوره في مساحة العمل: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### تشغيل الاختبارات - -تأكّد من تشغيل خادم Twenty المحلي لديك، ثم: - -```bash filename="Terminal" -yarn test -``` - -أو في وضع المراقبة أثناء التطوير: - -```bash filename="Terminal" -yarn test:watch -``` - -### التحقق من الأنواع - -يمكنك أيضًا تشغيل التحقق من الأنواع على تطبيقك دون تشغيل الاختبارات: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -يشغِّل هذا الأمر `tsc --noEmit` ويبلغ عن أي أخطاء في الأنواع. - -## مرجع CLI - -بالإضافة إلى `dev` و`build` و`add` و`typecheck`، يوفّر CLI أوامر لتنفيذ الدوال وعرض السجلات وإدارة تثبيتات التطبيقات. - -### تنفيذ الدوال (`yarn twenty exec`) - -تشغيل دالة منطقية يدويًا دون تشغيلها عبر HTTP أو cron أو حدث قاعدة بيانات: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### عرض سجلات الدوال (`yarn twenty logs`) - -بثّ سجلات التنفيذ لدوال تطبيقك المنطقية: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - - -يختلف هذا عن `yarn twenty server logs`، الذي يعرض سجلات حاوية Docker. يعرض `yarn twenty logs` سجلات تنفيذ دوال تطبيقك من خادم Twenty. - - -### إلغاء تثبيت تطبيق (`yarn twenty uninstall`) - -أزل تطبيقك من مساحة العمل النشطة: - -```bash filename="Terminal" -yarn twenty uninstall - -# Skip the confirmation prompt -yarn twenty uninstall --yes -``` - -## إدارة الريموتات - -**الريموت** هو خادم Twenty يتصل به تطبيقك. أثناء الإعداد، تُنشئ أداة إنشاء الهيكل واحدًا لك تلقائيًا. يمكنك إضافة ريموتات أخرى أو التبديل بينها في أي وقت. - -```bash filename="Terminal" -# Add a new remote (opens a browser for OAuth login) -yarn twenty remote add - -# Connect to a local Twenty server (auto-detects port 2020 or 3000) -yarn twenty remote add --local - -# Add a remote non-interactively (useful for CI) -yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote - -# List all configured remotes -yarn twenty remote list - -# Switch the active remote -yarn twenty remote switch -``` - -تُخزَّن بيانات اعتمادك في `~/.twenty/config.json`. - -## التكامل المستمر (CI) باستخدام GitHub Actions - -تولّد أداة إنشاء الهيكل سير عمل GitHub Actions جاهزًا للاستخدام في `.github/workflows/ci.yml`. يشغّل اختبارات التكامل لديك تلقائيًا عند كل دفع إلى `main` وعلى طلبات السحب. - -سير العمل: - -1. يجلب الشيفرة الخاصة بك -2. يشغّل خادم Twenty مؤقتًا باستخدام الإجراء `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` -3. يثبّت التبعيات باستخدام `yarn install --immutable` -4. يشغّل `yarn test` مع حقن `TWENTY_API_URL` و`TWENTY_API_KEY` من مخرجات الإجراء - -```yaml .github/workflows/ci.yml -name: CI - -on: - push: - branches: - - main - pull_request: {} - -env: - TWENTY_VERSION: latest - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Spawn Twenty instance - id: twenty - uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main - with: - twenty-version: ${{ env.TWENTY_VERSION }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Enable Corepack - run: corepack enable - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - - name: Install dependencies - run: yarn install --immutable - - - name: Run integration tests - run: yarn test - env: - TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} - TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} -``` - -لا تحتاج إلى تهيئة أي أسرار — إذ يبدأ إجراء `spawn-twenty-docker-image` خادم Twenty عابرًا مباشرة في المشغّل ويُخرِج تفاصيل الاتصال. يتم توفير السر `GITHUB_TOKEN` تلقائيًا من قِبل GitHub. - -لتثبيت إصدار محدّد من Twenty بدلًا من `latest`، غيّر متغير البيئة `TWENTY_VERSION` في أعلى سير العمل. +* **`yarn twenty dev`** — watches your source files and live-syncs changes to a connected Twenty server. The typed API client is regenerated automatically when the schema changes. +* **`yarn twenty build`** — compiles TypeScript, bundles logic functions and front components with esbuild, and produces a manifest. +* **Pre/post-install hooks** — optional logic functions that run during installation. See [Logic Functions](/l/ar/developers/extend/apps/logic-functions) for details. + +## الخطوات التالية + + + + Define objects, fields, roles, and relations. + + + Server-side functions with HTTP, cron, and event triggers. + + + Sandboxed React components inside Twenty's UI. + + + Views, navigation items, and record page layouts. + + + AI skills and agents with custom prompts. + + + CLI commands, testing, assets, remotes, and CI. + + + Deploy to a server or publish to the marketplace. + + diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/cli-and-testing.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/cli-and-testing.mdx new file mode 100644 index 00000000000..44590c2e23a --- /dev/null +++ b/packages/twenty-docs/l/ar/developers/extend/apps/cli-and-testing.mdx @@ -0,0 +1,434 @@ +--- +title: CLI & Testing +description: CLI commands, testing setup, public assets, npm packages, remotes, and CI configuration. +icon: terminal +--- + +## الأصول العامة (مجلد `public/`) + +يحتوي مجلد `public/` في جذر تطبيقك على ملفات ثابتة — صور وأيقونات وخطوط وأي أصول أخرى يحتاجها تطبيقك وقت التشغيل. تُدرج هذه الملفات تلقائيًا في عمليات البناء، وتُزامَن أثناء وضع التطوير، وتُرفَع إلى الخادم. + +الملفات الموضوعة في `public/` هي: + +* **متاحة للعامة** — بمجرد مزامنتها إلى الخادم، تُقدَّم الأصول عبر عنوان URL عام. لا يلزم توثيق للوصول إليها. +* **متاحة في المكوّنات الأمامية** — استخدم عناوين الأصول لعرض الصور أو الأيقونات أو أي وسائط داخل مكوّنات React لديك. +* **متاحة في الدوال المنطقية** — أشِر إلى عناوين الأصول في رسائل البريد الإلكتروني أو استجابات واجهات البرمجة أو أي منطق على جهة الخادم. +* **مستخدمة لبيانات تعريف السوق** — يشير حقلا `logoUrl` و`screenshots` في `defineApplication()` إلى ملفات من هذا المجلد (مثل `public/logo.png`). تُعرَض هذه عند نشر تطبيقك في السوق. +* **تُزامَن تلقائيًا في وضع التطوير** — عند إضافة ملف في `public/` أو تحديثه أو حذفه، تتم مزامنته إلى الخادم تلقائيًا. لا حاجة لإعادة التشغيل. +* **مضمَّنة في عمليات البناء** — يقوم `yarn twenty build` بتجميع جميع الأصول العامة ضمن مخرجات التوزيع. + +### الوصول إلى الأصول العامة باستخدام `getPublicAssetUrl` + +استخدم المساعد `getPublicAssetUrl` من `twenty-sdk` للحصول على العنوان الكامل لملف في دليل `public/` لديك. يعمل ذلك في كلٍ من الدوال المنطقية والمكوّنات الأمامية. + +**في دالة منطقية:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; + +const handler = async (): Promise => { + const logoUrl = getPublicAssetUrl('logo.png'); + const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); + + // Fetch the file content (no auth required — public endpoint) + const response = await fetch(invoiceUrl); + const buffer = await response.arrayBuffer(); + + return { logoUrl, size: buffer.byteLength }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'a1b2c3d4-...', + name: 'send-invoice', + description: 'Sends an invoice with the app logo', + timeoutSeconds: 10, + handler, +}); +``` + +**في مكوّن أمامي:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +وسيطة `path` نسبية إلى مجلد `public/` الخاص بتطبيقك. كلٌّ من `getPublicAssetUrl('logo.png')` و`getPublicAssetUrl('public/logo.png')` يُحلاّن إلى العنوان نفسه — تتم إزالة بادئة `public/` تلقائيًا إن وُجدت. + +## استخدام حِزَم npm + +يمكنك تثبيت واستخدام أي حزمة npm في تطبيقك. يتم تجميع كلٍ من الدوال المنطقية والمكوّنات الأمامية باستخدام [esbuild](https://esbuild.github.io/)، والذي يُضمّن جميع التبعيات ضمن المخرجات — لا حاجة إلى `node_modules` وقت التشغيل. + +### تثبيت حزمة + +```bash filename="Terminal" +yarn add axios +``` + +ثم استوردها في شيفرتك: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +وينطبق الأمر نفسه على المكوّنات الأمامية: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### كيف يعمل التجميع + +تستخدم خطوة البناء أداة esbuild لإنتاج ملف واحد مستقل لكل دالة منطقية ولكل مكوّن أمامي. تُضمَّن جميع الحزم المستوردة داخل الحزمة. + +**الدوال المنطقية** تعمل في بيئة Node.js. الوحدات المدمجة في Node (`fs` و`path` و`crypto` و`http` وغيرها) متاحة ولا تحتاج إلى تثبيت. + +**المكوّنات الأمامية** تعمل ضمن Web Worker. وحدات Node المدمجة غير متاحة — المتاح فقط واجهات برمجة المتصفّح وحِزَم npm التي تعمل في بيئة المتصفّح. + +كلتا البيئتين تحتويان على `twenty-client-sdk/core` و`twenty-client-sdk/metadata` كوحدات متاحة مُسبقًا — لا تُضمَّن هذه ضمن الحزم بل تُحلّ وقت التشغيل بواسطة الخادم. + +## اختبار تطبيقك + +يوفّر SDK واجهات برمجة قابلة للتنفيذ برمجيًا تمكّنك من بناء تطبيقك ونشره وتثبيته وإلغاء تثبيته من شيفرة الاختبار. بالاقتران مع [Vitest](https://vitest.dev/) وعملاء واجهة البرمجة مضبوطي الأنواع، يمكنك كتابة اختبارات تكامل تتحقّق من أن تطبيقك يعمل من البداية إلى النهاية مقابل خادم Twenty حقيقي. + +### إعداد + +يتضمّن التطبيق المُولَّد بالقالب بالفعل Vitest. إذا أعددته يدويًا، فثبّت التبعيات: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +أنشئ `vitest.config.ts` في جذر تطبيقك: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +أنشئ ملف إعداد يتحقّق من إمكانية الوصول إلى الخادم قبل تشغيل الاختبارات: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### واجهات SDK البرمجية + +يُصدِّر المسار الفرعي `twenty-sdk/cli` دوالًا يمكنك استدعاؤها مباشرةً من شيفرة الاختبار: + +| دالة | الوصف | +| -------------- | ----------------------------------------- | +| `appBuild` | بناء التطبيق واختياريًا حزم ملف tarball | +| `appDeploy` | رفع ملف tarball إلى الخادم | +| `appInstall` | تثبيت التطبيق على مساحة العمل النشطة | +| `appUninstall` | إلغاء تثبيت التطبيق من مساحة العمل النشطة | + +تُرجع كل دالة كائن نتيجة يحتوي على `success: boolean` وعلى إمّا `data` أو `error`. + +### كتابة اختبار تكامل + +إليك مثالًا كاملًا يبني التطبيق وينشره ويثبّته، ثم يتحقّق من ظهوره في مساحة العمل: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### تشغيل الاختبارات + +تأكّد من تشغيل خادم Twenty المحلي لديك، ثم: + +```bash filename="Terminal" +yarn test +``` + +أو في وضع المراقبة أثناء التطوير: + +```bash filename="Terminal" +yarn test:watch +``` + +### التحقق من الأنواع + +يمكنك أيضًا تشغيل التحقق من الأنواع على تطبيقك دون تشغيل الاختبارات: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +يشغِّل هذا الأمر `tsc --noEmit` ويبلغ عن أي أخطاء في الأنواع. + +## مرجع CLI + +بالإضافة إلى `dev` و`build` و`add` و`typecheck`، يوفّر CLI أوامر لتنفيذ الدوال وعرض السجلات وإدارة تثبيتات التطبيقات. + +### تنفيذ الدوال (`yarn twenty exec`) + +تشغيل دالة منطقية يدويًا دون تشغيلها عبر HTTP أو cron أو حدث قاعدة بيانات: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute the post-install function +yarn twenty exec --postInstall +``` + +### عرض سجلات الدوال (`yarn twenty logs`) + +بثّ سجلات التنفيذ لدوال تطبيقك المنطقية: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +يختلف هذا عن `yarn twenty server logs`، الذي يعرض سجلات حاوية Docker. يعرض `yarn twenty logs` سجلات تنفيذ دوال تطبيقك من خادم Twenty. + + +### إلغاء تثبيت تطبيق (`yarn twenty uninstall`) + +أزل تطبيقك من مساحة العمل النشطة: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` + +## إدارة الريموتات + +**الريموت** هو خادم Twenty يتصل به تطبيقك. أثناء الإعداد، تُنشئ أداة إنشاء الهيكل واحدًا لك تلقائيًا. يمكنك إضافة ريموتات أخرى أو التبديل بينها في أي وقت. + +```bash filename="Terminal" +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add + +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local + +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote + +# List all configured remotes +yarn twenty remote list + +# Switch the active remote +yarn twenty remote switch +``` + +تُخزَّن بيانات اعتمادك في `~/.twenty/config.json`. + +## التكامل المستمر (CI) باستخدام GitHub Actions + +تولّد أداة إنشاء الهيكل سير عمل GitHub Actions جاهزًا للاستخدام في `.github/workflows/ci.yml`. يشغّل اختبارات التكامل لديك تلقائيًا عند كل دفع إلى `main` وعلى طلبات السحب. + +سير العمل: + +1. يجلب الشيفرة الخاصة بك +2. يشغّل خادم Twenty مؤقتًا باستخدام الإجراء `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` +3. يثبّت التبعيات باستخدام `yarn install --immutable` +4. يشغّل `yarn test` مع حقن `TWENTY_API_URL` و`TWENTY_API_KEY` من مخرجات الإجراء + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +لا تحتاج إلى تهيئة أي أسرار — إذ يبدأ إجراء `spawn-twenty-docker-image` خادم Twenty عابرًا مباشرة في المشغّل ويُخرِج تفاصيل الاتصال. يتم توفير السر `GITHUB_TOKEN` تلقائيًا من قِبل GitHub. + +لتثبيت إصدار محدّد من Twenty بدلًا من `latest`، غيّر متغير البيئة `TWENTY_VERSION` في أعلى سير العمل. diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/data-model.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/data-model.mdx new file mode 100644 index 00000000000..a402406cfd2 --- /dev/null +++ b/packages/twenty-docs/l/ar/developers/extend/apps/data-model.mdx @@ -0,0 +1,494 @@ +--- +title: نموذج البيانات +description: Define objects, fields, roles, and application metadata with the Twenty SDK. +icon: database +--- + +The `twenty-sdk` package provides `defineEntity` functions to declare your app's data model. يجب عليك استخدام `export default defineEntity({...})` لكي يكتشف SDK الكيانات الخاصة بك. تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع. + + + **تنظيم الملفات يعود إليك.** + يعتمد اكتشاف الكيانات على AST — حيث يعثر SDK على استدعاءات `export default defineEntity(...)` بغض النظر عن مكان وجود الملف. تجميع الملفات حسب النوع (مثلًا، `logic-functions/` و`roles/`) هو مجرّد عرف، وليس متطلبًا. + + + + + +تُغلّف الأدوار الصلاحيات على كائنات وإجراءات مساحة العمل لديك. + +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export default defineRole({ + universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', + label: 'My new role', + description: 'A role that can be used in your workspace', + canReadAllObjectRecords: false, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + canReadObjectRecords: true, + canUpdateObjectRecords: true, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + }, + ], + fieldPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + fieldUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, + canReadFieldValue: false, + canUpdateFieldValue: false, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +يجب أن يحتوي كل تطبيق على استدعاء واحد فقط لـ `defineApplication` يصف: + +* **الهوية**: المعرّفات، اسم العرض، والوصف. +* **الأذونات**: أيُّ دورٍ تستخدمه وظائفه ومكوّناته الأمامية. +* **(اختياري) المتغيرات**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة. +* **(اختياري) دوال ما قبل التثبيت/ما بعد التثبيت**: دوال منطقية تعمل قبل التثبيت أو بعده. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk/define'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +الملاحظات: +* حقول `universalIdentifier` هي معرّفات حتمية تملكها أنت. أنشِئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة. +* `applicationVariables` تصبح متغيرات بيئة لوظائفك ومكوّناتك الأمامية (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` يجب أن يُشير إلى دور مُعرَّف باستخدام `defineRole()` (انظر أعلاه). +* يتم اكتشاف دوال ما قبل التثبيت وما بعده تلقائيًا أثناء بناء البيان — لا حاجة للإشارة إليها في `defineApplication()`. + +#### بيانات التعريف لسوق التطبيقات + +إذا كنت تخطط لـ [نشر تطبيقك](/l/ar/developers/extend/apps/publishing)، فإن هذه الحقول الاختيارية تتحكّم في كيفية ظهوره في السوق: + +| الحقل | الوصف | +| ------------------ | ------------------------------------------------------------------------------------------------------------ | +| `author` | اسم المؤلف أو الشركة | +| `category` | فئة التطبيق لتصفية سوق التطبيقات | +| `logoUrl` | مسار شعار تطبيقك (مثلًا، `public/logo.png`) | +| `screenshots` | مصفوفة لمسارات لقطات الشاشة (مثلًا، `public/screenshot-1.png`) | +| `aboutDescription` | وصف ماركداون أطول لعلامة التبويب "حول". إذا لم يتم تضمينه، يستخدم السوق ملف `README.md` الخاص بالحزمة من npm | +| `websiteUrl` | رابط إلى موقعك الإلكتروني | +| `termsUrl` | رابط إلى شروط الخدمة | +| `emailSupport` | عنوان البريد الإلكتروني للدعم | +| `issueReportUrl` | رابط إلى متتبّع المشاكل | + +#### الأدوار والصلاحيات + +يُحدّد الحقل `defaultRoleUniversalIdentifier` في `application-config.ts` الدور الافتراضي الذي تستخدمه وظائف المنطق والمكوّنات الأمامية في تطبيقك. راجع `defineRole` أعلاه للحصول على التفاصيل. + +* رمز وقت التشغيل المحقون باسم `TWENTY_APP_ACCESS_TOKEN` مستمد من هذا الدور. +* العميل مضبوط الأنواع مقيَّد بالأذونات الممنوحة لذلك الدور. +* اتبع مبدأ أقل الامتياز: أنشئ دورًا مخصصًا يضم فقط الأذونات التي تحتاجها وظائفك. + +##### الدور الافتراضي للوظيفة + +عند توليد تطبيق جديد بالقالب، ينشئ CLI ملفّ دور افتراضي: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk/define'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + label: 'Default function role', + description: 'Default role for function Twenty client', + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [], + fieldPermissions: [], + permissionFlags: [], +}); +``` + +يُشار إلى `universalIdentifier` لهذا الدور في `application-config.ts` باسم `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** يحدد ما يمكن أن يفعله الدور. +* **application-config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك أذوناته. + +الملاحظات: +* ابدأ من الدور المُنشأ بالقالب، ثم قيّده تدريجيًا باتباع مبدأ أقل الامتياز. +* استبدل `objectPermissions` و`fieldPermissions` بالكائنات والحقول التي تحتاجها وظائفك فعليًا. +* `permissionFlags` تتحكم في الوصول إلى القدرات على مستوى المنصة. اجعلها في حدّها الأدنى. +* اطّلع على مثال عملي: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + + +تصف الكائنات المخصصة كلًا من المخطط والسلوك للسجلات في مساحة عملك. استخدم `defineObject()` لتعريف كائنات مع تحقق مدمج: + +```ts postCard.object.ts +import { defineObject, FieldType } from 'twenty-sdk/define'; + +enum PostCardStatus { + DRAFT = 'DRAFT', + SENT = 'SENT', + DELIVERED = 'DELIVERED', + RETURNED = 'RETURNED', +} + +export default defineObject({ + universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', + nameSingular: 'postCard', + namePlural: 'postCards', + labelSingular: 'Post Card', + labelPlural: 'Post Cards', + description: 'A post card object', + icon: 'IconMail', + fields: [ + { + universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', + name: 'content', + type: FieldType.TEXT, + label: 'Content', + description: "Postcard's content", + icon: 'IconAbc', + }, + { + universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', + name: 'recipientName', + type: FieldType.FULL_NAME, + label: 'Recipient name', + icon: 'IconUser', + }, + { + universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', + name: 'recipientAddress', + type: FieldType.ADDRESS, + label: 'Recipient address', + icon: 'IconHome', + }, + { + universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', + name: 'status', + type: FieldType.SELECT, + label: 'Status', + icon: 'IconSend', + defaultValue: `'${PostCardStatus.DRAFT}'`, + options: [ + { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, + { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, + { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, + { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, + ], + }, + { + universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', + name: 'deliveredAt', + type: FieldType.DATE_TIME, + label: 'Delivered at', + icon: 'IconCheck', + isNullable: true, + defaultValue: null, + }, + ], +}); +``` + +النقاط الرئيسية: + +* استخدم `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, etc.) أو كائنات من تطبيقات أخرى. على خلاف الحقول المضمّنة في `defineObject()`، تتطلّب الحقول المستقلة `objectUniversalIdentifier` لتحديد الكائن الذي تقوم بتوسيعه: + +```ts src/fields/company-loyalty-tier.field.ts +import { defineField, FieldType } from 'twenty-sdk/define'; + +export default defineField({ + universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', + objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object + name: 'loyaltyTier', + type: FieldType.SELECT, + label: 'Loyalty Tier', + icon: 'IconStar', + options: [ + { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, + { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, + { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, + ], +}); +``` + +النقاط الرئيسية: +* `objectUniversalIdentifier` يحدّد الكائن الهدف. بالنسبة للكائنات القياسية، استخدم `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` المُصدَّر من `twenty-sdk`. +* عند تعريف الحقول بشكل مضمّن في `defineObject()`، **لا** تحتاج إلى `objectUniversalIdentifier` — إذ يُورَّث من الكائن الأب. +* `defineField()` هي الطريقة الوحيدة لإضافة حقول إلى كائنات لم تُنشئها باستخدام `defineObject()`. + + + + +تربط العلاقات الكائنات معًا. في Twenty، تكون العلاقات دائمًا **ثنائية الاتجاه** — حيث تعرّف الجانبين، ويشير كل جانب إلى الآخر. + +هناك نوعان من العلاقات: + +| نوع العلاقة | الوصف | هل لديه مفتاح خارجي؟ | +| ------------- | ------------------------------------------------------ | ---------------------- | +| `MANY_TO_ONE` | تشير العديد من سجلات هذا الكائن إلى سجل واحد من الهدف | نعم (`joinColumnName`) | +| `ONE_TO_MANY` | يحتوي سجل واحد من هذا الكائن على العديد من سجلات الهدف | لا (الجانب العكسي) | + +#### كيف تعمل العلاقات + +تتطلّب كل علاقة **حقلين** يشيران إلى بعضهما البعض: + +1. جانب **MANY_TO_ONE** — يوجد على الكائن الذي يحمل المفتاح الخارجي +2. جانب **ONE_TO_MANY** — يوجد على الكائن الذي يملك المجموعة + +يستخدم كلا الحقلين `FieldType.RELATION` ويُحيل كلٌ منهما إلى الآخر عبر `relationTargetFieldMetadataUniversalIdentifier`. + +#### مثال: البطاقة البريدية لديها العديد من المستلمين + +افترض أن `PostCard` يمكن إرسالها إلى العديد من سجلات `PostCardRecipient`. ينتمي كل مستلم إلى بطاقة بريدية واحدة بالضبط. + +**الخطوة 1: عرّف جانب ONE_TO_MANY على PostCard** (جانب "الواحد"): + +```ts src/fields/post-card-recipients-on-post-card.field.ts +import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; +// Import from the other side +import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; + +export default defineField({ + universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCardRecipients', + label: 'Post Card Recipients', + icon: 'IconUsers', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); +``` + +**الخطوة 2: عرّف جانب MANY_TO_ONE على PostCardRecipient** (جانب "العديد" — يحمل المفتاح الخارجي): + +```ts src/fields/post-card-on-post-card-recipient.field.ts +import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; +// Import from the other side +import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; + +export default defineField({ + universalIdentifier: POST_CARD_FIELD_ID, + objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + icon: 'IconMail', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, +}); +``` + + +**الاستيرادات الدائرية:** كلا حقلي العلاقة يُحيل كلٌ منهما إلى `universalIdentifier` الخاص بالآخر. لتجنّب مشكلات الاستيراد الدائري، صدّر معرّفات الحقول كثوابت مسمّاة من كل ملف، واستوردها في الملف الآخر. يقوم نظام البناء بحلّها في وقت التجميع. + + +#### الربط مع الكائنات القياسية + +لإنشاء علاقة مع كائن Twenty مضمّن (Person, Company, etc.)، استخدم `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: + +```ts src/fields/person-on-self-hosting-user.field.ts +import { + defineField, + FieldType, + RelationType, + OnDeleteAction, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; +import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; + +export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; +export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; + +export default defineField({ + universalIdentifier: PERSON_FIELD_ID, + objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'person', + label: 'Person', + description: 'Person matching with the self hosting user', + isNullable: true, + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'personId', + }, +}); +``` + +#### خصائص حقل العلاقة + +| الخاصية | مطلوب | الوصف | +| ------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------- | +| `type` | نعم | يجب أن يكون `FieldType.RELATION` | +| `relationTargetObjectMetadataUniversalIdentifier` | نعم | قيمة `universalIdentifier` للكائن الهدف | +| `relationTargetFieldMetadataUniversalIdentifier` | نعم | قيمة `universalIdentifier` للحقل المطابق على الكائن الهدف | +| `universalSettings.relationType` | نعم | `RelationType.MANY_TO_ONE` أو `RelationType.ONE_TO_MANY` | +| `universalSettings.onDelete` | MANY_TO_ONE فقط | ماذا يحدث عند حذف السجل المشار إليه: `CASCADE`، `SET_NULL`، `RESTRICT`، أو `NO_ACTION` | +| `universalSettings.joinColumnName` | MANY_TO_ONE فقط | اسم عمود قاعدة البيانات للمفتاح الخارجي (مثل `postCardId`) | + +#### حقول العلاقات المضمّنة في defineObject + +يمكنك أيضًا تعريف حقول العلاقات مباشرةً داخل `defineObject()`. في هذه الحالة، احذف `objectUniversalIdentifier` — إذ يُورَّث من الكائن الأب: + +```ts +export default defineObject({ + universalIdentifier: '...', + nameSingular: 'postCardRecipient', + // ... + fields: [ + { + universalIdentifier: POST_CARD_FIELD_ID, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, + }, + // ... other fields + ], +}); +``` + + + +## توليد قوالب الكيانات باستخدام `yarn twenty add` + +بدلًا من إنشاء ملفات الكيانات يدويًا، يمكنك استخدام أداة القوالب التفاعلية: + +```bash filename="Terminal" +yarn twenty add +``` + +ستطالبك باختيار نوع الكيان وتُرشدك خلال الحقول المطلوبة. تُولّد ملفًا جاهزًا للاستخدام مع `universalIdentifier` ثابت واستدعاء `defineEntity()` الصحيح. + +يمكنك أيضًا تمرير نوع الكيان مباشرة لتخطي المطالبة الأولى: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### أنواع الكيانات المتاحة + +| نوع الكيان | أمر | الملف المُولَّد | +| ------------------ | ------------------------------------ | ------------------------------------------------------- | +| كائن | `yarn twenty add object` | `src/objects/\.ts` | +| الحقل | `yarn twenty add field` | `src/fields/\.ts` | +| دالة منطقية | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | +| مكوّن أمامي | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | +| دور | `yarn twenty add role` | `src/roles/\.ts` | +| مهارة | `yarn twenty add skill` | `src/skills/\.ts` | +| وكيل | `yarn twenty add agent` | `src/agents/\.ts` | +| عرض | `yarn twenty add view` | `src/views/\.ts` | +| عنصر قائمة التنقّل | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | +| تخطيط الصفحة | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | + +### ما الذي تُنشئه أداة القوالب + +لكل نوع كيان قالب خاص به. على سبيل المثال، يسأل `yarn twenty add object` عن: + +1. **الاسم (مفرد)** — مثل `invoice` +2. **الاسم (جمع)** — مثل `invoices` +3. **التسمية (مفرد)** — تُستمد تلقائيًا من الاسم (مثل `Invoice`) +4. **التسمية (جمع)** — تُملأ تلقائيًا (مثل `Invoices`) +5. **إنشاء عرض وعنصر تنقّل؟** — إذا أجبت بنعم، فستُنشئ أداة القوالب أيضًا عرضًا مطابقًا ورابط شريط جانبي للكائن الجديد. + +أنواع الكيانات الأخرى لها مطالبات أبسط — فمعظمها يطلب اسمًا فقط. + +نوع الكيان `field` أكثر تفصيلاً: يطلب اسم الحقل وتسمية الحقل ونوعه (من قائمة بكل أنواع الحقول المتاحة مثل `TEXT` و`NUMBER` و`SELECT` و`RELATION` وغيرها)، ومعرّف `universalIdentifier` للكائن الهدف. + +### مسار خرج مخصّص + +استخدم العلم `--path` لوضع الملف المُولَّد في موقع مخصّص: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/front-components.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/front-components.mdx new file mode 100644 index 00000000000..363064ba95e --- /dev/null +++ b/packages/twenty-docs/l/ar/developers/extend/apps/front-components.mdx @@ -0,0 +1,419 @@ +--- +title: المكوّنات الأمامية +description: Build React components that render inside Twenty's UI with sandboxed isolation. +icon: window-maximize +--- + +المكوّنات الأمامية هي مكوّنات React تُعرَض مباشرة داخل واجهة مستخدم Twenty. تعمل ضمن **Web Worker** معزول باستخدام Remote DOM — تكون شيفرتك في صندوق عزل لكنها تُعرَض أصيلًا داخل الصفحة، وليس ضمن iframe. + +## أين يمكن استخدام مكوّنات الواجهة الأمامية + +يمكن عرض مكوّنات الواجهة الأمامية في موقعين داخل Twenty: + +* **اللوحة الجانبية** — المكوّنات غير عديمة الرأس تفتح في اللوحة الجانبية اليمنى. هذا هو السلوك الافتراضي عندما يتم تشغيل مكوّن واجهة أمامية من قائمة الأوامر. +* **الويدجت (لوحات المعلومات وصفحات السجلات)** — يمكن تضمين مكوّنات الواجهة الأمامية كويدجت داخل تخطيطات الصفحات. عند تكوين لوحة معلومات أو تخطيط صفحة سجل، يمكن للمستخدمين إضافة ويدجت لمكوّن واجهة أمامية. + +## مثال أساسي + +أسرع طريقة لرؤية مكوّن أمامي قيد العمل هي تسجيله كأمر. إضافة حقل `command` مع `isPinned: true` يجعلُه يظهر كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة — دون الحاجة إلى تخطيط صفحة: + +```tsx src/front-components/hello-world.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; + +const HelloWorld = () => { + return ( +
+

Hello from my app!

+

This component renders inside Twenty.

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, +}); +``` + +بعد المزامنة باستخدام `yarn twenty dev` (أو تشغيل الأمر لمرة واحدة `yarn twenty dev --once`)، يظهر الإجراء السريع في الزاوية العلوية اليمنى من الصفحة: + +
+ زر إجراء سريع في الزاوية العلوية اليمنى +
+ +انقره لعرض المكوّن مضمنًا داخل الصفحة. + +## حقول التكوين + +| الحقل | مطلوب | الوصف | +| --------------------- | ----- | ----------------------------------------------------------------- | +| `universalIdentifier` | نعم | معرّف فريد ثابت لهذا المكوّن | +| `component` | نعم | دالة مكوّن React | +| `name` | لا | اسم العرض | +| `الوصف` | لا | وصف لما يفعله المكوّن | +| `isHeadless` | لا | عيِّنه إلى `true` إذا كان المكوّن بلا واجهة مرئية (انظر أدناه) | +| `أمر` | لا | سجّل المكوّن كأمر (انظر [خيارات الأوامر](#command-options) أدناه) | + +## وضع مكوّن أمامي على صفحة + +إضافةً إلى الأوامر، يمكنك تضمين مكوّن أمامي مباشرةً في صفحة سجل عبر إضافته كودجت في **تخطيط صفحة**. راجع قسم [definePageLayout](/l/ar/developers/extend/apps/skills-and-agents#definepagelayout) للتفاصيل. + +## عديم الرأس مقابل غير عديم الرأس + +تأتي مكوّنات الواجهة الأمامية بوضعَي عرض يتحكّم بهما الخيار `isHeadless`: + +**غير عديم الرأس (افتراضي)** — يعرض المكوّن واجهة مستخدم مرئية. عند تشغيله من قائمة الأوامر يفتح في اللوحة الجانبية. هذا هو السلوك الافتراضي عندما تكون `isHeadless` تساوي `false` أو يتم تجاهلها. + +**عديم الرأس (`isHeadless: true`)** — يتم تركيب المكوّن بشكل غير مرئي في الخلفية. لا يفتح اللوحة الجانبية. تم تصميم المكوّنات عديمة الرأس لإجراءات تنفّذ منطقًا ثم تُزيل تركيبها ذاتيًا — على سبيل المثال، تشغيل مهمة غير متزامنة، أو الانتقال إلى صفحة، أو إظهار نافذة تأكيد منبثقة. تتوافق بشكل طبيعي مع مكوّنات Command في SDK الموصوفة أدناه. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +نظرًا لأن المكوّن يُرجع `null`، فإن Twenty يتخطّى عرض حاوية له — ولن تظهر مساحة فارغة في التخطيط. لا يزال لدى المكوّن إمكانية الوصول إلى جميع الخطافات وواجهة برمجة الاتصال مع المضيف. + +## مكوّنات Command في SDK + +توفر حزمة `twenty-sdk` أربعة مكوّنات مساعدة من نوع Command مصممة للمكوّنات عديمة الرأس في الواجهة الأمامية. كل مكوّن ينفّذ إجراءً عند التركيب، ويتعامل مع الأخطاء بعرض إشعار Snackbar، ويزيل تركيب مكوّن الواجهة الأمامية تلقائيًا عند الانتهاء. + +استوردها من `twenty-sdk/command`: + +* **`Command`** — يشغّل رد نداء غير متزامن عبر الخاصية `execute`. +* **`CommandLink`** — ينتقل إلى مسار في التطبيق. الخصائص: `to`، `params`، `queryParams`، `options`. +* **`CommandModal`** — يفتح نافذة تأكيد منبثقة. إذا أكّد المستخدم، ينفّذ رد النداء `execute`. الخصائص: `title`، `subtitle`، `execute`، `confirmButtonText`، `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — يفتح صفحة محدّدة في اللوحة الجانبية. الخصائص: `page`، `pageTitle`، `pageIcon`. + +فيما يلي مثال كامل لمكوّن واجهة أمامية عديم الرأس يستخدم `Command` لتشغيل إجراء من قائمة الأوامر: + +```tsx src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +ومثال يستخدم `CommandModal` لطلب التأكيد قبل التنفيذ: + +```tsx src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +## الوصول إلى سياق وقت التشغيل + +داخل مكوّنك، استخدم خطافات SDK للوصول إلى المستخدم الحالي، والسجل، ومثيل المكوّن: + +```tsx src/front-components/record-info.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk/front-component'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +الخطافات المتاحة: + +| الخطّاف | القيم المعادة | الوصف | +| --------------------------------------------- | ------------------ | ---------------------------------------------- | +| `useUserId()` | `string` أو `null` | معرّف المستخدم الحالي | +| `useRecordId()` | `string` أو `null` | معرّف السجل الحالي (عند وضعه على صفحة سجل) | +| `useFrontComponentId()` | `string` | معرّف مثيل هذا المكوّن | +| `useFrontComponentExecutionContext(selector)` | يختلف | الوصول إلى سياق التنفيذ الكامل عبر دالة محدِّد | + +## واجهة الاتصال مع المضيف + +يمكن للمكوّنات الأمامية تشغيل التنقّل والنوافذ المنبثقة والإشعارات باستخدام دوال من `twenty-sdk`: + +| دالة | الوصف | +| ----------------------------------------------- | ------------------------------ | +| `navigate(to, params?, queryParams?, options?)` | الانتقال إلى صفحة داخل التطبيق | +| `openSidePanelPage(params)` | فتح لوحة جانبية | +| `closeSidePanel()` | إغلاق اللوحة الجانبية | +| `openCommandConfirmationModal(params)` | عرض مربع حوار تأكيد | +| `enqueueSnackbar(params)` | عرض إشعار توست | +| `unmountFrontComponent()` | إلغاء تركيب المكوّن | +| `updateProgress(progress)` | تحديث مؤشّر التقدّم | + +فيما يلي مثال يستخدم واجهة برمجة تطبيقات المضيف لعرض Snackbar وإغلاق اللوحة الجانبية بعد اكتمال الإجراء: + +```tsx src/front-components/archive-record.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId } from 'twenty-sdk/front-component'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + +## خيارات الأوامر + +إضافة حقل `command` إلى `defineFrontComponent` تُسجِّل المكوّن في قائمة الأوامر (Cmd+K). إذا كانت قيمة `isPinned` هي `true`، فسيظهر أيضًا كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة. + +| الحقل | مطلوب | الوصف | +| --------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `universalIdentifier` | نعم | معرّف فريد ثابت للأمر | +| `التسمية` | نعم | التسمية الكاملة المعروضة في قائمة الأوامر (Cmd+K) | +| `shortLabel` | لا | تسمية أقصر تُعرَض على زر الإجراء السريع المثبّت | +| `أيقونة` | لا | اسم الأيقونة المعروض بجانب التسمية (مثل `'IconBolt'` و`'IconSend'`) | +| `isPinned` | لا | عند كونها `true`، يعرض الأمر كزر إجراء سريع في الزاوية العلوية اليمنى من الصفحة | +| `availabilityType` | لا | تتحكّم في مكان ظهور الأمر: `'GLOBAL'` (متاح دائمًا)، و`'RECORD_SELECTION'` (فقط عند تحديد سجلات)، أو `'FALLBACK'` (يُعرَض عند عدم تطابق أي أوامر أخرى) | +| `availabilityObjectUniversalIdentifier` | لا | تقييد الأمر بصفحات نوع كائن معيّن (مثل سجلات Company فقط) | +| `conditionalAvailabilityExpression` | لا | تعبير منطقي للتحكم ديناميكيًا في ما إذا كان الأمر مرئيًا (انظر أدناه) | + +## تعابير الإتاحة الشرطية + +يتيح لك الحقل `conditionalAvailabilityExpression` التحكّم في وقت ظهور الأمر بناءً على سياق الصفحة الحالي. استورد متغيّرات ومشغّلات مضبوطة الأنواع من `twenty-sdk` لبناء التعابير: + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk/front-component'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**متغيّرات السياق** — تُمثّل الحالة الحالية للصفحة: + +| المتغيّر | النوع | الوصف | +| ------------------------------ | ------------- | --------------------------------------------------------------- | +| `pageType` | `string` | نوع الصفحة الحالي (مثل `'RecordIndexPage'` و`'RecordShowPage'`) | +| `isInSidePanel` | `قيمة منطقية` | ما إذا كان المكوّن معروضًا في لوحة جانبية | +| `numberOfSelectedRecords` | `رقم` | عدد السجلات المحدّدة حاليًا | +| `isSelectAll` | `قيمة منطقية` | ما إذا كان "تحديد الكل" مفعّلًا | +| `selectedRecords` | `array` | كائنات السجلات المحدّدة | +| `favoriteRecordIds` | `array` | معرّفات السجلات المفضّلة | +| `objectPermissions` | `الكائن` | الأذونات الخاصة بنوع الكائن الحالي | +| `targetObjectReadPermissions` | `الكائن` | أذونات القراءة للكائن الهدف | +| `targetObjectWritePermissions` | `الكائن` | أذونات الكتابة للكائن الهدف | +| `featureFlags` | `الكائن` | أعلام الميزات المفعَّلة | +| `objectMetadataItem` | `الكائن` | بيانات التعريف لنوع الكائن الحالي | +| `hasAnySoftDeleteFilterOnView` | `قيمة منطقية` | ما إذا كان العرض الحالي يحتوي على مرشّح حذف منطقي | + +**المُشغِّلات** — جمّع المتغيّرات في تعابير منطقية: + +| المُشغِّل | الوصف | +| ----------------------------------- | -------------------------------------------------------------- | +| `isDefined(value)` | `true` إذا لم تكن القيمة null/undefined | +| `isNonEmptyString(value)` | `true` إذا كانت القيمة سلسلة غير فارغة | +| `includes(array, value)` | `true` إذا كانت المصفوفة تحتوي على القيمة | +| `includesEvery(array, prop, value)` | `true` إذا كانت خاصية كل عنصر تتضمن القيمة | +| `every(array, prop)` | `true` إذا كانت الخاصية تُقيَّم بصحّة في كل عنصر | +| `everyDefined(array, prop)` | `true` إذا كانت الخاصية معرّفة في كل عنصر | +| `everyEquals(array, prop, value)` | `true` إذا كانت الخاصية تساوي القيمة في كل عنصر | +| `some(array, prop)` | `true` إذا كانت الخاصية تُقيَّم بصحّة في عنصر واحد على الأقل | +| `someDefined(array, prop)` | `true` إذا كانت الخاصية معرّفة في عنصر واحد على الأقل | +| `someEquals(array, prop, value)` | `true` إذا كانت الخاصية تساوي القيمة في عنصر واحد على الأقل | +| `someNonEmptyString(array, prop)` | `true` إذا كانت الخاصية سلسلة غير فارغة في عنصر واحد على الأقل | +| `none(array, prop)` | `true` إذا كانت الخاصية تُقيَّم بخطأ في كل عنصر | +| `noneDefined(array, prop)` | `true` إذا كانت الخاصية غير معرّفة في كل عنصر | +| `noneEquals(array, prop, value)` | `true` إذا لم تكن الخاصية تساوي القيمة في أي عنصر | + +## الأصول العامة + +يمكن للمكوّنات الأمامية الوصول إلى ملفات من دليل `public/` للتطبيق باستخدام `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +راجع [قسم الأصول العامة](/l/ar/developers/extend/apps/cli-and-testing#public-assets-public-folder) للتفاصيل. + +## التنسيق + +تدعم المكوّنات الأمامية عدة أساليب للتنسيق. يمكنك استخدام: + +* **أنماط مضمنة** — `style={{ color: 'red' }}` +* **مكوّنات Twenty لواجهة المستخدم** — استورد من `twenty-sdk/ui` (Button وTag وStatus وChip وAvatar وغيرها) +* **Emotion** — CSS-in-JS مع `@emotion/react` +* **Styled-components** — أنماط `styled.div` +* **Tailwind CSS** — أصناف مساعدة +* **أي مكتبة CSS-in-JS** متوافقة مع React + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/getting-started.mdx index be97963b51e..dbd5c152969 100644 --- a/packages/twenty-docs/l/ar/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/ar/developers/extend/apps/getting-started.mdx @@ -1,12 +1,9 @@ --- title: البدء +icon: rocket description: أنشئ أول تطبيق Twenty خلال دقائق. --- - -التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور. - - ## ما هي التطبيقات؟ تتيح لك التطبيقات توسيع Twenty باستخدام كائنات وحقول مخصّصة ووظائف منطقية ومكوّنات الواجهة الأمامية ومهارات الذكاء الاصطناعي وغير ذلك — جميعها تُدار ككود. بدلًا من تكوين كل شيء عبر واجهة المستخدم، تعرّف نموذج بياناتك ومنطقك في TypeScript وتقوم بنشره إلى مساحة عمل واحدة أو أكثر. diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/layout.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/layout.mdx new file mode 100644 index 00000000000..1f9a64efae8 --- /dev/null +++ b/packages/twenty-docs/l/ar/developers/extend/apps/layout.mdx @@ -0,0 +1,131 @@ +--- +title: التخطيط +description: Define views, navigation menu items, and page layouts to shape how your app appears in Twenty. +icon: table-columns +--- + +Layout entities control how your app surfaces inside Twenty's UI — what lives in the sidebar, which saved views ship with the app, and how a record detail page is arranged. + +## Layout concepts + +| Concept | What it controls | كيان | +| ------------------------ | --------------------------------------------------------------------------------- | -------------------------- | +| **View** | A saved list configuration for an object — visible fields, order, filters, groups | `defineView` | +| **Navigation Menu Item** | An entry in the left sidebar that links to a view or an external URL | `defineNavigationMenuItem` | +| **Page Layout** | The tabs and widgets that make up a record's detail page | `definePageLayout` | + +Views, navigation items, and page layouts reference each other by `universalIdentifier`: + +* A **navigation menu item** of type `VIEW` points at a `defineView` identifier, so the sidebar link opens that saved view. +* A **page layout** of type `RECORD_PAGE` targets an object and can embed [front components](/l/ar/developers/extend/apps/front-components) inside its tabs as widgets. + + + + +العروض هي تكوينات محفوظة لكيفية عرض سجلات كائن ما — بما في ذلك الحقول المرئية وترتيبها وأي مرشّحات أو مجموعات مُطبَّقة. استخدم `defineView()` لتضمين عروض مُهيّأة مسبقًا مع تطبيقك: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +النقاط الرئيسية: +* `objectUniversalIdentifier` يحدّد الكائن الذي ينطبق عليه هذا العرض. +* `key` يحدّد نوع العرض (مثل `ViewKey.INDEX` لعرض القائمة الرئيسي). +* `fields` يتحكّم في الأعمدة الظاهرة وترتيبها. يشير كل حقل إلى `fieldMetadataUniversalIdentifier`. +* يمكنك أيضًا تعريف `filters` و`filterGroups` و`groups` و`fieldGroups` لمزيد من التكوينات المتقدمة. +* `position` يتحكّم في الترتيب عند وجود عدة عروض لنفس الكائن. + + + + +تضيف عناصر قائمة التنقل إدخالات مخصّصة إلى الشريط الجانبي لمساحة العمل. استخدم `defineNavigationMenuItem()` للارتباط بالعروض أو عناوين URL خارجية أو الكائنات: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +النقاط الرئيسية: +* `type` يحدّد إلى ماذا يرتبط عنصر القائمة: `NavigationMenuItemType.VIEW` لعرض محفوظ، أو `NavigationMenuItemType.LINK` لعنوان URL خارجي. +* لروابط العروض، عيِّن `viewUniversalIdentifier`. لروابط خارجية، عيِّن `link`. +* `position` يتحكّم في الترتيب ضمن الشريط الجانبي. +* `icon` و`color` (اختياريان) يخصّصان المظهر. + + + + +تتيح لك تخطيطات الصفحات تخصيص مظهر صفحة تفاصيل السجل — ما الألسنة التي تظهر، وما الويدجتات داخل كل لسان، وكيف يتم ترتيبها. استخدم `definePageLayout()` لتضمين تخطيطات مخصّصة مع تطبيقك: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +النقاط الرئيسية: +* `type` يكون عادة `'RECORD_PAGE'` لتخصيص عرض التفاصيل لكائن محدّد. +* `objectUniversalIdentifier` يحدّد الكائن الذي ينطبق عليه هذا التخطيط. +* يُعرّف كل `tab` قسمًا من الصفحة مع `title` و`position` و`layoutMode` (`CANVAS` لتخطيط حرّ). +* يمكن لكل `widget` داخل لسان أن يعرض مكوّنًا أماميًا أو قائمة علاقات أو أنواع ويدجت مدمجة أخرى. +* `position` على الألسنة يتحكّم في ترتيبها. استخدم قيمًا أعلى (مثل 50) لوضع الألسنة المخصّصة بعد الألسنة المدمجة. + + + diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/logic-functions.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/logic-functions.mdx new file mode 100644 index 00000000000..45d9130d406 --- /dev/null +++ b/packages/twenty-docs/l/ar/developers/extend/apps/logic-functions.mdx @@ -0,0 +1,559 @@ +--- +title: الوظائف المنطقية +description: Define server-side TypeScript functions with HTTP, cron, and database event triggers. +icon: bolt +--- + +Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents. + + + + +كل ملف وظيفة يستخدم `defineLogicFunction()` لتصدير تكوين مع معالج ومشغّلات اختيارية. + +```ts src/logic-functions/createPostCard.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; + +const handler = async (params: RoutePayload) => { + const client = new CoreApiClient(); + const name = 'name' in params.queryStringParameters + ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' + : 'Hello world'; + + const result = await client.mutation({ + createPostCard: { + __args: { data: { name } }, + id: true, + name: true, + }, + }); + return result; +}; + +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'create-new-post-card', + timeoutSeconds: 2, + handler, + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: true, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ +}); +``` + +أنواع المشغّلات المتاحة: +* **httpRoute**: يعرِض وظيفتك على مسار وطريقة HTTP **تحت نقطة النهاية `/s/`**: +> مثال: `path: '/post-card/create'` يمكن استدعاؤه عبر `https://your-twenty-server.com/s/post-card/create` +* **cron**: يشغّل وظيفتك على جدول باستخدام تعبير CRON. +* **databaseEvent**: يعمل على أحداث دورة حياة كائنات مساحة العمل. عندما تكون عملية الحدث هي `updated`، يمكن تحديد الحقول المحددة المراد الاستماع إليها في مصفوفة `updatedFields`. إذا تُركت غير معرّفة أو فارغة، فسيؤدي أي تحديث إلى تشغيل الدالة. +> مثال: `person.updated`، `*.created`، `company.*` + + +يمكنك أيضًا تنفيذ دالة يدويًا باستخدام CLI: + +```bash filename="Terminal" +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' +``` + +```bash filename="Terminal" +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + +يمكنك متابعة السجلات باستخدام: + +```bash filename="Terminal" +yarn twenty logs +``` + + +#### حمولة مشغل المسار + +عندما يستدعي مُشغِّل المسار وظيفتك المنطقية، فإنها تتلقّى كائن `RoutePayload` الذي يتبع [صيغة AWS HTTP API v2](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +استورد نوع `RoutePayload` من `twenty-sdk`: + +```ts +import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; + +const handler = async (event: RoutePayload) => { + const { headers, queryStringParameters, pathParameters, body } = event; + const { method, path } = event.requestContext.http; + + return { message: 'Success' }; +}; +``` + +يحتوي نوع `RoutePayload` على البنية التالية: + + | الخاصية | النوع | الوصف | مثال | + | ---------------------------- | ------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- | + | `headers` | `Record\` | رؤوس HTTP (فقط تلك المدرجة في `forwardedRequestHeaders`) | انظر القسم أدناه | + | `queryStringParameters` | `Record\` | معلمات سلسلة الاستعلام (تُضمّ القيم المتعددة باستخدام فواصل) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record\` | معلمات المسار المستخرجة من نمط المسار | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `المحتوى` | `object \| null` | جسم الطلب المُحلَّل (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `قيمة منطقية` | ما إذا كان جسم الطلب مُرمَّزًا بترميز base64 | | + | `requestContext.http.method` | `string` | طريقة HTTP (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | المسار الخام للطلب | | + + +#### forwardedRequestHeaders + +افتراضيًا، **لا** تُمرَّر رؤوس HTTP من الطلبات الواردة إلى دالتك المنطقية لأسباب أمنية. +للوصول إلى رؤوس محددة، أدرِجها في مصفوفة `forwardedRequestHeaders`: + +```ts +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'webhook-handler', + handler, + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, +}); +``` + +في معالجك، يمكنك الوصول إلى الرؤوس المُمرَّرة بهذه الطريقة: + +```ts +const handler = async (event: RoutePayload) => { + const signature = event.headers['x-webhook-signature']; + const contentType = event.headers['content-type']; + + // Validate webhook signature... + return { received: true }; +}; +``` + + +تُحوَّل أسماء الرؤوس إلى أحرف صغيرة. يمكنك الوصول إليها باستخدام مفاتيح بأحرف صغيرة (على سبيل المثال، `event.headers['content-type']`). + + +#### إتاحة دالة كأداة + +يمكن إتاحة الدوال المنطقية بوصفها **أدوات** لوكلاء الذكاء الاصطناعي وسير العمل. عند تمييز دالة كأداة، تصبح قابلة للاكتشاف بواسطة ميزات الذكاء الاصطناعي في Twenty ويمكن استخدامها في أتمتة سير العمل. + +لتمييز دالة منطقية كأداة، عيِّن `isTool: true`: + +```ts src/logic-functions/enrich-company.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const handler = async (params: { companyName: string; domain?: string }) => { + const client = new CoreApiClient(); + + const result = await client.mutation({ + createTask: { + __args: { + data: { + title: `Enrich data for ${params.companyName}`, + body: `Domain: ${params.domain ?? 'unknown'}`, + }, + }, + id: true, + }, + }); + + return { taskId: result.createTask.id }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + name: 'enrich-company', + description: 'Enrich a company record with external data', + timeoutSeconds: 10, + handler, + isTool: true, +}); +``` + +النقاط الرئيسية: + +* يمكنك دمج `isTool` مع المشغِّلات — إذ يمكن للدالة أن تكون أداة (قابلة للاستدعاء من قِبل وكلاء الذكاء الاصطناعي) وأن تُشغَّل بواسطة الأحداث في الوقت نفسه. +* **`toolInputSchema`** (اختياري): كائن JSON Schema يصف المعلمات التي تقبلها دالتك. يُحسَب المخطط تلقائيًا من خلال تحليل ساكن للشيفرة المصدرية، ولكن يمكنك تعيينه صراحةً: + +```ts +export default defineLogicFunction({ + ..., + toolInputSchema: { + type: 'object', + properties: { + companyName: { + type: 'string', + description: 'The name of the company to enrich', + }, + domain: { + type: 'string', + description: 'The company website domain (optional)', + }, + }, + required: ['companyName'], + }, +}); +``` + + +**اكتب `description` جيدًا.** يعتمد وكلاء الذكاء الاصطناعي على حقل `description` الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها. + + + + + +دالة ما بعد التثبيت هي دالة منطقية تعمل تلقائيًا بعد تثبيت تطبيقك على مساحة عمل. ينفّذه الخادم **بعد** مزامنة البيانات الوصفية للتطبيق وإنشاء عميل SDK، بحيث تكون مساحة العمل جاهزة تمامًا للاستخدام ويكون المخطط الجديد مطبَّقًا. تشمل حالات الاستخدام النموذجية تهيئة البيانات الافتراضية، وإنشاء السجلات الأولية، وتكوين إعدادات مساحة العمل، أو توفير الموارد على خدمات جهات خارجية. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + shouldRunSynchronously: false, + handler, +}); +``` + +يمكنك أيضًا تنفيذ دالة ما بعد التثبيت يدويًا في أي وقت باستخدام CLI: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +النقاط الرئيسية: +* تستخدم دوال ما بعد التثبيت `definePostInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`). +* يتلقى المعالج `InstallPayload` يحتوي على `{ previousVersion?: string; newVersion: string }` — حيث إن `newVersion` هو الإصدار الجاري تثبيته، و`previousVersion` هو الإصدار الذي كان مُثبّتًا سابقًا (أو `undefined` عند التثبيت الأولي). استخدم هذه القيم للتمييز بين عمليات التثبيت الجديدة والترقيات ولتشغيل منطق الترحيل الخاص بالإصدار. +* **موعد تشغيل الخطاف**: في عمليات التثبيت الجديدة فقط، افتراضيًا. مرّر `shouldRunOnVersionUpgrade: true` إذا كنت تريد تشغيله أيضًا عند ترقية التطبيق من إصدار سابق. عند إغفاله، تكون القيمة الافتراضية للعلم `false`، وتتجاوز الترقيات هذا الخطاف. +* **نموذج التنفيذ — غير متزامن افتراضيًا، والتزامني اختياري**: يتحكّم العلم `shouldRunSynchronously` في كيفية تنفيذ ما بعد التثبيت. + * `shouldRunSynchronously: false` *(الإعداد الافتراضي)* — يتم **إدراج الخطاف في قائمة الرسائل** مع `retryLimit: 3` ويعمل بشكل غير متزامن داخل عامل عمل. يعود ردّ التثبيت بمجرد وضع المهمة في الطابور، لذا فإن معالجًا بطيئًا أو متعطلًا لا يحجب المستدعي. سيُجرِّب العامل إعادة المحاولة حتى ثلاث مرات. **استخدم هذا للمهام طويلة التشغيل** — بَذر مجموعات بيانات كبيرة، استدعاء واجهات برمجة تطبيقات خارجية بطيئة، تهيئة موارد خارجية، أو أي شيء قد يتجاوز نافذة استجابة HTTP المعقولة. + * `shouldRunSynchronously: true` — يُنفّذ الخطاف **ضمن تدفّق التثبيت مباشرةً** (نفس المنفِّذ كما قبل التثبيت). يَحجُب طلب التثبيت حتى ينتهي المعالج، وإذا رمى استثناءً، سيتلقى مستدعي التثبيت `POST_INSTALL_ERROR`. لا توجد محاولات إعادة تلقائية. **استخدم هذا للمهام السريعة التي يجب إكمالها قبل الاستجابة** — مثل إظهار خطأ تحقق للمستخدم، أو إعداد سريع سيعتمد عليه العميل مباشرةً بعد عودة نداء التثبيت. ضع في اعتبارك أن ترحيل البيانات الوصفية يكون قد طُبِّق بالفعل عند تشغيل ما بعد التثبيت، لذلك فإن فشل الوضع المتزامن **لا** يعيد التغييرات على المخطط إلى الوراء — بل يكتفي بإبراز الخطأ. +* تأكّد من أن معالجك قابل للتنفيذ المتكرر دون آثار جانبية. في الوضع غير المتزامن قد تُعيد قائمة الانتظار المحاولة حتى ثلاث مرات؛ وفي أي من الوضعين قد يعمل الخطاف مجددًا أثناء الترقيات عند ضبط `shouldRunOnVersionUpgrade: true`. +* متغيرات البيئة `APPLICATION_ID` و`APP_ACCESS_TOKEN` و`API_URL` متاحة داخل المعالج (كما في أي دالة منطق أخرى)، لذا يمكنك استدعاء واجهة Twenty API باستخدام رمز وصول للتطبيق مقيّد بنطاق تطبيقك. +* يُسمح بدالة ما بعد التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة. +* تُرفَق خصائص الدالة `universalIdentifier` و`shouldRunOnVersionUpgrade` و`shouldRunSynchronously` تلقائيًا ببيان التطبيق ضمن الحقل `postInstallLogicFunction` أثناء عملية البناء — ولا تحتاج إلى الإشارة إليها في `defineApplication()`. +* تم تعيين مهلة افتراضية إلى 300 ثانية (5 دقائق) للسماح بمهام الإعداد الأطول مثل تهيئة البيانات. +* **لا يُنفَّذ في وضع التطوير**: عند تسجيل تطبيق محليًا (عبر `yarn twenty dev`)، يتجاوز الخادم تدفّق التثبيت بالكامل ويُزامن الملفات مباشرةً عبر مراقِب CLI — لذا لن يعمل ما بعد التثبيت في وضع التطوير مطلقًا، بغضّ النظر عن `shouldRunSynchronously`. استخدم `yarn twenty exec --postInstall` لتشغيله يدويًا على مساحة عمل قيد التشغيل. + + + + +دالة ما قبل التثبيت هي دالة منطقية تعمل تلقائيًا أثناء التثبيت، **قبل تطبيق ترحيل البيانات الوصفية لمساحة العمل**. تتشارك نفس بنية الحمولة مع ما بعد التثبيت (`InstallPayload`)، لكنها موضوعة أبكر في تدفّق التثبيت كي تجهّز حالة يعتمد عليها الترحيل القادم — ومن الاستخدامات الشائعة: نسخ البيانات احتياطيًا، التحقق من التوافق مع المخطط الجديد، أو أرشفة السجلات التي ستُعاد هيكلتها أو ستُحذف. + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +يمكنك أيضًا تنفيذ دالة ما قبل التثبيت يدويًا في أي وقت باستخدام CLI: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +النقاط الرئيسية: +* تستخدم دوال ما قبل التثبيت `definePreInstallLogicFunction()` — نفس الإعدادات المتخصصة كما في ما بعد التثبيت، لكنها مرتبطة بموضع مختلف ضمن دورة الحياة. +* يتلقّى كلٌّ من معالجي ما قبل التثبيت وما بعد التثبيت النوع نفسه `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. استورده مرة واحدة وأعد استخدامه لكلا الخطافين. +* **موعد تشغيل الخطاف**: موضوع مباشرةً قبل ترحيل البيانات الوصفية لمساحة العمل (`synchronizeFromManifest`). قبل التنفيذ، يُشغِّل الخادم مزامنة "pared-down sync" ذات طابع إضافي فقط تقوم بتسجيل دالة ما قبل التثبيت للإصدار **الجديد** في البيانات الوصفية لمساحة العمل — دون لمس أي شيء آخر — ثم يُنفّذها. لأن هذه المزامنة «إضافية فقط»، تبقى كائنات وحقول وبيانات الإصدار السابق سليمة عند تشغيل معالجك: يمكنك قراءة حالة ما قبل الترحيل ونسخها احتياطيًا بأمان. +* **نموذج التنفيذ**: يُنفَّذ ما قبل التثبيت **بشكل متزامن** و**يحجب عملية التثبيت**. إذا رمى المعالج استثناءً، تُلغى عملية التثبيت قبل تطبيق أي تغييرات على المخطط — وتبقى مساحة العمل على الإصدار السابق بحالة متّسقة. هذا مقصود: ما قبل التثبيت هو فرصتك الأخيرة لرفض ترقية تنطوي على مخاطر. +* كما هو الحال مع ما بعد التثبيت، يُسمح بدالة ما قبل التثبيت واحدة فقط لكل تطبيق. تُربَط تلقائيًا ببيان التطبيق تحت `preInstallLogicFunction` أثناء عملية البناء. +* **لا يُنفَّذ في وضع التطوير**: كما في ما بعد التثبيت — يتم تجاوز تدفّق التثبيت بالكامل للتطبيقات المسجّلة محليًا، لذا لن يعمل ما قبل التثبيت مطلقًا عند `yarn twenty dev`. استخدم `yarn twenty exec --preInstall` لتشغيله يدويًا. + + + + +كلا الخطافين جزء من تدفّق التثبيت نفسه ويتلقّيان نفس `InstallPayload`. الاختلاف يكمن في **موعد** تشغيلهما نسبةً إلى ترحيل البيانات الوصفية لمساحة العمل، وهذا يغيّر البيانات التي يمكنهما التعامل معها بأمان. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ install flow │ +│ │ +│ upload package → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +│ │ +│ old schema visible new schema visible │ +└─────────────────────────────────────────────────────────────┘ +``` + +ما قبل التثبيت دائمًا **متزامن** (يحجب التثبيت ويمكنه إحباطه). ما بعد التثبيت **غير متزامن افتراضيًا** — يُدرج على عامل مع محاولات إعادة تلقائية — لكن يمكن التبديل إلى تنفيذ متزامن عبر `shouldRunSynchronously: true`. راجع الأكورديون `definePostInstallLogicFunction` أعلاه لمعرفة متى تستخدم كل وضع. + +**استخدم `post-install` لأي شيء يتطلّب وجود المخطط الجديد.** وهذا هو السيناريو الشائع: + +* بَذر بيانات افتراضية (إنشاء سجلات أولية وعروض افتراضية ومحتوى تجريبي) للكائنات والحقول المضافة حديثًا. +* تسجيل خطافات الويب مع خدمات أطراف ثالثة بعد أن حصل التطبيق على بيانات الاعتماد الخاصة به. +* استدعاء واجهة برمجة التطبيقات الخاصة بك لإكمال إعداد يعتمد على البيانات الوصفية المتزامنة. +* منطق idempotent لتحقيق "تأكّد من وجود هذا" والذي ينبغي مواءمة الحالة في كل ترقية — بالاقتران مع `shouldRunOnVersionUpgrade: true`. + +مثال — بَذر سجل `PostCard` افتراضي بعد التثبيت: + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion }: InstallPayload): Promise => { + if (previousVersion) return; // fresh installs only + + const client = createClient(); + await client.postCard.create({ + data: { title: 'Welcome to Postcard', content: 'Your first card!' }, + }); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Seeds a welcome post card after install.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + handler, +}); +``` + +**استخدم `pre-install` عندما قد يُتلف الترحيل أو يدمّر البيانات الحالية.** لأن ما قبل التثبيت يعمل مقابل المخطط *السابق* وفشله يُرجِع الترقية إلى الوراء، فهو المكان المناسب لأي شيء محفوف بالمخاطر: + +* **نسخ البيانات احتياطيًا قبل حذفها أو إعادة هيكلتها** — مثل إزالة حقل في v2 وتحتاج إلى نسخ قيمه إلى حقل آخر أو تصديرها إلى التخزين قبل تشغيل الترحيل. +* **أرشفة السجلات التي سيبطلها قيد جديد** — مثل أن يصبح حقل ما `NOT NULL` وتحتاج أولًا إلى حذف الصفوف ذات القيم الفارغة أو إصلاحها. +* **التحقق من التوافق ورفض الترقية إذا تعذّر ترحيل البيانات الحالية بسلاسة** — ارمِ من داخل المعالج وسيُلغى التثبيت دون تطبيق أي تغييرات. هذا أكثر أمانًا من اكتشاف عدم التوافق في منتصف الترحيل. +* **إعادة تسمية البيانات أو إعادة تعيين مفاتيحها** قبل تغيير في المخطط قد يؤدي إلى فقدان الارتباط. + +مثال — أرشف السجلات قبل ترحيل هدّام: + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { + // Only the 1.x → 2.x upgrade drops the legacy `notes` field. + if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { + return; + } + + const client = createClient(); + const legacyRecords = await client.postCard.findMany({ + where: { notes: { isNotNull: true } }, + }); + + if (legacyRecords.length === 0) return; + + // Copy legacy `notes` into the new `description` field before the migration + // drops the `notes` column. If this fails, the upgrade is aborted and the + // workspace stays on v1 with all data intact. + await Promise.all( + legacyRecords.map((record) => + client.postCard.update({ + where: { id: record.id }, + data: { description: record.notes }, + }), + ), + ); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Backs up legacy notes into description before the v2 migration.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +**قاعدة عامة:** + +| You want to... | استخدام | +| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | +| بذر بيانات افتراضية، تهيئة مساحة العمل، تسجيل موارد خارجية | `post-install` | +| تشغيل بذر طويل الأمد أو استدعاءات أطراف ثالثة لا ينبغي أن تحجب استجابة التثبيت | `post-install` (الإعداد الافتراضي — `shouldRunSynchronously: false`، مع محاولات إعادة من العامل) | +| تشغيل إعداد سريع سيعتمد عليه المستدعي مباشرةً بعد عودة نداء التثبيت | `post-install` مع `shouldRunSynchronously: true` | +| قراءة البيانات أو نسخها احتياطيًا والتي قد يفقدها الترحيل القادم | `pre-install` | +| رفض ترقية قد تُفسد البيانات الحالية | `pre-install` (ارمِ من المعالج) | +| تنفيذ مواءمة في كل ترقية | `post-install` مع `shouldRunOnVersionUpgrade: true` | +| تنفيذ إعداد لمرة واحدة في التثبيت الأول فقط | `post-install` مع `shouldRunOnVersionUpgrade: false` (الإعداد الافتراضي) | + + +إذا ساورك الشك، فاجعل الافتراضي هو **post-install**. الجأ إلى ما قبل التثبيت فقط عندما يكون الترحيل نفسه هدّامًا وتحتاج إلى التقاط الحالة السابقة قبل أن تزول. + + + + + +## عملاء واجهة برمجة تطبيقات مضبوطة الأنواع (`twenty-client-sdk`) + +توفر حزمة `twenty-client-sdk` عميلين لـ GraphQL ذوي أنواع ثابتة للتفاعل مع واجهة Twenty البرمجية من وظائفك المنطقية ومكوّنات الواجهة الأمامية. + +| العميل | استيراد | نقطة النهاية | مُولَّد؟ | +| ------------------- | ---------------------------- | --------------------------------------------------- | -------------------------- | +| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — بيانات مساحة العمل (السجلات، الكائنات) | نعم، في وقت التطوير/البناء | +| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — تكوين مساحة العمل، رفع الملفات | لا، يأتي مُجهزًا مسبقًا | + + + + +`CoreApiClient` هو العميل الرئيسي للاستعلام وتعديل بيانات مساحة العمل. يُولَّد من مخطط مساحة العمل لديك أثناء `yarn twenty dev` أو `yarn twenty build`، لذا فهو مضبوط الأنواع بالكامل ليتوافق مع كائناتك وحقولك. + +```ts +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const client = new CoreApiClient(); + +// Query records +const { companies } = await client.query({ + companies: { + edges: { + node: { + id: true, + name: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, + }, + }, + }, +}); + +// Create a record +const { createCompany } = await client.mutation({ + createCompany: { + __args: { + data: { + name: 'Acme Corp', + }, + }, + id: true, + name: true, + }, +}); +``` + +يستخدم العميل صياغة مجموعة اختيار: مرِّر `true` لتضمين حقل، واستخدم `__args` للوسيطات، وعشّش الكائنات للعلاقات. ستحصل على إكمال تلقائي كامل وفحص للأنواع يعتمد على مخطط مساحة العمل لديك. + + +**يتم توليد CoreApiClient في وقت التطوير/البناء.** إذا استخدمته دون تشغيل `yarn twenty dev` أو `yarn twenty build` أولًا، فسيؤدي ذلك إلى خطأ. تحدث عملية التوليد تلقائيًا — إذ يستطلع CLI مخطط GraphQL لمساحة عملك وينشئ عميلًا مضبوط الأنواع باستخدام `@genql/cli`. + + +#### استخدام CoreSchema للتعليقات التوضيحية للأنواع + +`CoreSchema` يوفّر أنواع TypeScript المطابقة لكائنات مساحة العمل لديك — مفيد لتعيين أنواع حالة المكوّن أو معاملات الدوال: + +```ts +import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; +import { useState } from 'react'; + +const [company, setCompany] = useState< + Pick | undefined +>(undefined); + +const client = new CoreApiClient(); +const result = await client.query({ + company: { + __args: { filter: { position: { eq: 1 } } }, + id: true, + name: true, + }, +}); +setCompany(result.company); +``` + + + + +يأتي `MetadataApiClient` مُجهّزًا مسبقًا مع SDK (لا حاجة للتوليد). يستعلم عن نقطة النهاية `/metadata` للحصول على تكوين مساحة العمل والتطبيقات ورفع الملفات. + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; + +const metadataClient = new MetadataApiClient(); + +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, + }, +}); +``` + +#### رفع الملفات + +يتضمن `MetadataApiClient` طريقة `uploadFile` لإرفاق الملفات بالحقول من نوع الملف: + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import * as fs from 'fs'; + +const metadataClient = new MetadataApiClient(); + +const fileBuffer = fs.readFileSync('./invoice.pdf'); + +const uploadedFile = await metadataClient.uploadFile( + fileBuffer, // file contents as a Buffer + 'invoice.pdf', // filename + 'application/pdf', // MIME type + '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier +); + +console.log(uploadedFile); +// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } +``` + +| المعلمة | النوع | الوصف | +| ---------------------------------- | -------- | ---------------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | المحتوى الخام للملف | +| `filename` | `string` | اسم الملف (يُستخدم للتخزين والعرض) | +| `contentType` | `string` | نوع MIME (القيمة الافتراضية `application/octet-stream` إذا لم يُحدَّد) | +| `fieldMetadataUniversalIdentifier` | `string` | قيمة `universalIdentifier` لحقل نوع الملف في كائنك | + +النقاط الرئيسية: +* يستخدم `universalIdentifier` الخاص بالحقل (وليس معرّفه الخاص بمساحة العمل)، بحيث يعمل كود الرفع لديك عبر أي مساحة عمل مُثبَّت فيها تطبيقك. +* العنوان `url` المُعاد هو عنوان URL موقّع يمكنك استخدامه للوصول إلى الملف المرفوع. + + + + + + عند تشغيل كودك على Twenty (وظائف منطقية أو مكوّنات أمامية)، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئية: + + * `TWENTY_API_URL` — عنوان URL الأساسي لواجهة Twenty البرمجية + * `TWENTY_APP_ACCESS_TOKEN` — مفتاح قصير العمر ذو نطاق يقتصر على الدور الافتراضي لوظيفة تطبيقك + + لست **بحاجة** إلى تمرير هذه القيم إلى العملاء — فهي تُقرأ تلقائيًا من `process.env`. تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `defaultRoleUniversalIdentifier` ضمن `application-config.ts`. + diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/publishing.mdx index 15a9bc8a23d..8b384c0a93b 100644 --- a/packages/twenty-docs/l/ar/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/ar/developers/extend/apps/publishing.mdx @@ -1,12 +1,9 @@ --- title: النشر +icon: رفع description: وزّع تطبيق Twenty الخاص بك على سوق Twenty أو انشره داخليًا. --- - - التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور. - - ## نظرة عامة بمجرد أن يكون تطبيقك [مبنيًا ومختبرًا محليًا](/l/ar/developers/extend/apps/building)، لديك مساران لتوزيعه: diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/skills-and-agents.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/skills-and-agents.mdx new file mode 100644 index 00000000000..5b6687608d7 --- /dev/null +++ b/packages/twenty-docs/l/ar/developers/extend/apps/skills-and-agents.mdx @@ -0,0 +1,69 @@ +--- +title: المهارات والوكلاء +description: Define AI skills and agents for your app. +icon: robot +--- + + + Skills and agents are currently in alpha. الميزة تعمل لكنها لا تزال قيد التطور. + + +Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts. + + + + +تُحدِّد المهارات تعليمات وإمكانات قابلة لإعادة الاستخدام يمكن لوكلاء الذكاء الاصطناعي استخدامها داخل مساحة العمل لديك. استخدم `defineSkill()` لتعريف مهارات مع تحقّق مدمج: + +```ts src/skills/example-skill.ts +import { defineSkill } from 'twenty-sdk/define'; + +export default defineSkill({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'sales-outreach', + label: 'Sales Outreach', + description: 'Guides the AI agent through a structured sales outreach process', + icon: 'IconBrain', + content: `You are a sales outreach assistant. When reaching out to a prospect: +1. Research the company and recent news +2. Identify the prospect's role and likely pain points +3. Draft a personalized message referencing specific details +4. Keep the tone professional but conversational`, +}); +``` + +النقاط الرئيسية: +* `name` هي سلسلة معرّف فريدة للمهارة (يُنصَح باستخدام kebab-case). +* `label` هو اسم العرض المقروء للبشر الظاهر في واجهة المستخدم. +* `content` يحتوي على تعليمات المهارة — وهو النص الذي يستخدمه وكيل الذكاء الاصطناعي. +* `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم. +* `description` (اختياري) يوفّر سياقًا إضافيًا حول غرض المهارة. + + + + +الوكلاء هم مساعدون ذكاء اصطناعي يعيشون داخل مساحة العمل لديك. استخدم `defineAgent()` لإنشاء وكلاء بموجه نظام مخصّص: + +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk/define'; + +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +النقاط الرئيسية: +* `name` هي سلسلة معرّف فريدة للوكيل (يُنصح باستخدام kebab-case). +* `label` هو اسم العرض الظاهر في واجهة المستخدم. +* `prompt` هو موجه النظام الذي يحدّد سلوك الوكيل. +* `description` (اختياري) يوفّر سياقًا حول ما يفعله الوكيل. +* `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم. +* `modelId` (اختياري) يتجاوز نموذج الذكاء الاصطناعي الافتراضي الذي يستخدمه الوكيل. + + + diff --git a/packages/twenty-docs/l/ar/developers/extend/oauth.mdx b/packages/twenty-docs/l/ar/developers/extend/oauth.mdx new file mode 100644 index 00000000000..cf0a1f062e3 --- /dev/null +++ b/packages/twenty-docs/l/ar/developers/extend/oauth.mdx @@ -0,0 +1,189 @@ +--- +title: OAuth +icon: المفتاح +description: Authorization code flow with PKCE and client credentials for server-to-server access. +--- + +Twenty implements OAuth 2.0 with authorization code + PKCE for user-facing apps and client credentials for server-to-server access. Clients are registered dynamically via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) — no manual setup in a dashboard. + +## When to Use OAuth + +| السيناريو | Auth Method | +| --------------------------------------- | -------------------------------------------------------------------------------- | +| Internal scripts, automation | [API Key](/l/ar/developers/extend/api#authentication) | +| External app acting on behalf of a user | **OAuth — Authorization Code** | +| Server-to-server, no user context | **OAuth — Client Credentials** | +| Twenty App with UI extensions | [Apps](/l/ar/developers/extend/apps/getting-started) (OAuth is handled automatically) | + +## Register a Client + +Twenty supports **dynamic client registration** per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). No manual setup needed — register programmatically: + +```bash +POST /oauth/register +Content-Type: application/json + +{ + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"], + "grant_types": ["authorization_code"], + "token_endpoint_auth_method": "client_secret_post" +} +``` + +**Response:** + +```json +{ + "client_id": "abc123", + "client_secret": "secret456", + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"] +} +``` + + +Store the `client_secret` securely — it cannot be retrieved later. + + +## النطاقات + +| Scope | الوصول | +| --------- | ---------------------------------------------------- | +| `api` | Full read/write access to the Core and Metadata APIs | +| `profile` | Read the authenticated user's profile information | + +Request scopes as a space-separated string: `scope=api profile` + +## Authorization Code Flow + +Use this flow when your app acts on behalf of a Twenty user. + +### 1. Redirect the user to authorize + +``` +GET /oauth/authorize? + client_id=YOUR_CLIENT_ID& + response_type=code& + redirect_uri=https://myapp.com/callback& + scope=api& + state=random_state_value& + code_challenge=CHALLENGE& + code_challenge_method=S256 +``` + +| المعلمة | مطلوب | الوصف | +| ----------------------- | -------- | ------------------------------------------------------------ | +| `client_id` | نعم | Your registered client ID | +| `response_type` | نعم | Must be `code` | +| `redirect_uri` | نعم | Must match a registered redirect URI | +| `scope` | لا | Space-separated scopes (defaults to `api`) | +| `الحالة` | مُوصى به | Random string to prevent CSRF attacks | +| `code_challenge` | مُوصى به | PKCE challenge (SHA-256 hash of verifier, base64url-encoded) | +| `code_challenge_method` | مُوصى به | Must be `S256` when using PKCE | + +The user sees a consent screen and approves or denies access. + +### ٢. Handle the callback + +After authorization, Twenty redirects back to your `redirect_uri`: + +``` +https://myapp.com/callback?code=AUTH_CODE&state=random_state_value +``` + +Verify that `state` matches what you sent. + +### ٣. Exchange the code for tokens + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=authorization_code& +code=AUTH_CODE& +redirect_uri=https://myapp.com/callback& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +code_verifier=YOUR_PKCE_VERIFIER +``` + +**Response:** + +```json +{ + "access_token": "eyJhbG...", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "dGhpcyBpcyBh..." +} +``` + +### 4. Use the access token + +```bash +GET /rest/companies +Authorization: Bearer ACCESS_TOKEN +``` + +### 5. Refresh when expired + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=refresh_token& +refresh_token=YOUR_REFRESH_TOKEN& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET +``` + +## Client Credentials Flow + +For server-to-server integrations with no user interaction: + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=client_credentials& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +scope=api +``` + +The returned token has workspace-level access, not tied to any specific user. + +## Server Discovery + +Twenty publishes its OAuth configuration at a standard discovery endpoint: + +``` +GET /.well-known/oauth-authorization-server +``` + +This returns all endpoints, supported grant types, scopes, and capabilities — useful for building generic OAuth clients. + +## API Endpoints Summary + +| نقطة النهاية | الغرض | +| ----------------------------------------- | --------------------------- | +| `/.well-known/oauth-authorization-server` | Server metadata discovery | +| `/oauth/register` | Dynamic client registration | +| `/oauth/authorize` | User authorization | +| `/oauth/token` | Token exchange and refresh | + +| البيئة | عنوان URL الأساسي | +| --------------------- | ------------------------ | +| **السحابة** | `https://api.twenty.com` | +| **الاستضافة الذاتية** | `https://{your-domain}` | + +## OAuth vs API Keys + +| | مفاتيح واجهة برمجة التطبيقات | OAuth | +| ------------------ | ---------------------------- | -------------------------------------- | +| **الإعداد** | Generate in Settings | Register a client, implement flow | +| **User context** | None (workspace-level) | Specific user's permissions | +| **الأفضل لـ** | Scripts, internal tools | External apps, multi-user integrations | +| **Token rotation** | يدوي | Automatic via refresh tokens | +| **Scoped access** | Full API access | Granular via scopes | diff --git a/packages/twenty-docs/l/ar/developers/extend/webhooks.mdx b/packages/twenty-docs/l/ar/developers/extend/webhooks.mdx index a9d4c8e71e5..26c51606c51 100644 --- a/packages/twenty-docs/l/ar/developers/extend/webhooks.mdx +++ b/packages/twenty-docs/l/ar/developers/extend/webhooks.mdx @@ -1,11 +1,12 @@ --- title: خطافات الويب -description: استقبل إشعارات في الوقت الفعلي عند وقوع أحداث في نظام إدارة علاقات العملاء (CRM) الخاص بك. +icon: satellite-dish +description: Get notified when records change — HTTP POST to your endpoint on every create, update, or delete. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -تدفع خطافات الويب البيانات إلى أنظمتك في الوقت الفعلي عند وقوع أحداث في Twenty — دون الحاجة إلى الاستطلاع الدوري. استخدمها للحفاظ على تزامن الأنظمة الخارجية، وتشغيل الأتمتة، أو إرسال التنبيهات. +Twenty sends an HTTP POST to your URL whenever a record is created, updated, or deleted. All object types are covered, including custom objects. ## إنشاء خطاف ويب diff --git a/packages/twenty-docs/l/ar/developers/introduction.mdx b/packages/twenty-docs/l/ar/developers/introduction.mdx index b3404d481e4..46d91041198 100644 --- a/packages/twenty-docs/l/ar/developers/introduction.mdx +++ b/packages/twenty-docs/l/ar/developers/introduction.mdx @@ -1,23 +1,28 @@ --- -title: البدء -description: مرحبًا بك في وثائق المطوّرين الخاصة بـ Twenty، مرجعك للتوسيع والاستضافة الذاتية والمساهمة في Twenty. +title: المطورون +description: Build apps, use the API, self-host, or contribute to the codebase. --- import { CardTitle } from "/snippets/card-title.mdx" - - التوسيع - أنشئ عمليات تكامل مع واجهات برمجة التطبيقات وخطافات الويب والتطبيقات المخصصة. + + Apps + Extend Twenty with custom objects, server-side logic, UI components, and AI agents — all as TypeScript packages. - - الاستضافة الذاتية - قم بنشر Twenty وإدارته على البنية التحتية الخاصة بك. + + API + REST and GraphQL APIs, webhooks, and OAuth. - - المساهمة - انضم إلى مجتمعنا مفتوح المصدر وساهم في Twenty. + + Self-Host + Run Twenty on your own infrastructure. + + + + Contribute + Set up the monorepo locally and submit PRs. diff --git a/packages/twenty-docs/l/ar/developers/self-host/capabilities/cloud-providers.mdx b/packages/twenty-docs/l/ar/developers/self-host/capabilities/cloud-providers.mdx index 959827375fe..923ba2e6fe8 100644 --- a/packages/twenty-docs/l/ar/developers/self-host/capabilities/cloud-providers.mdx +++ b/packages/twenty-docs/l/ar/developers/self-host/capabilities/cloud-providers.mdx @@ -1,5 +1,6 @@ --- title: طرق أخرى +icon: cloud --- diff --git a/packages/twenty-docs/l/ar/developers/self-host/capabilities/docker-compose.mdx b/packages/twenty-docs/l/ar/developers/self-host/capabilities/docker-compose.mdx index ff2bef53f8b..3d8c0eb0ad6 100644 --- a/packages/twenty-docs/l/ar/developers/self-host/capabilities/docker-compose.mdx +++ b/packages/twenty-docs/l/ar/developers/self-host/capabilities/docker-compose.mdx @@ -1,5 +1,6 @@ --- -title: بنقرة واحدة مع Docker Compose +title: Docker Compose +icon: docker --- diff --git a/packages/twenty-docs/l/ar/developers/self-host/capabilities/setup.mdx b/packages/twenty-docs/l/ar/developers/self-host/capabilities/setup.mdx index 81ca5f83cea..567b9013beb 100644 --- a/packages/twenty-docs/l/ar/developers/self-host/capabilities/setup.mdx +++ b/packages/twenty-docs/l/ar/developers/self-host/capabilities/setup.mdx @@ -1,5 +1,6 @@ --- title: إعداد +icon: gear --- # إدارة الإعدادات diff --git a/packages/twenty-docs/l/ar/developers/self-host/capabilities/troubleshooting.mdx b/packages/twenty-docs/l/ar/developers/self-host/capabilities/troubleshooting.mdx index 146d5d21a73..ac5053d8406 100644 --- a/packages/twenty-docs/l/ar/developers/self-host/capabilities/troubleshooting.mdx +++ b/packages/twenty-docs/l/ar/developers/self-host/capabilities/troubleshooting.mdx @@ -1,5 +1,6 @@ --- title: استكشاف الأخطاء وإصلاحها +icon: wrench --- ## استكشاف الأخطاء وإصلاحها diff --git a/packages/twenty-docs/l/ar/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/ar/developers/self-host/capabilities/upgrade-guide.mdx index 80ae57d7b42..1c048932a8c 100644 --- a/packages/twenty-docs/l/ar/developers/self-host/capabilities/upgrade-guide.mdx +++ b/packages/twenty-docs/l/ar/developers/self-host/capabilities/upgrade-guide.mdx @@ -1,5 +1,6 @@ --- title: دليل الترقية +icon: arrow-up-right-dots --- ## إرشادات عامة @@ -16,366 +17,14 @@ title: دليل الترقية 3. قم بإعادة تشغيل Twenty باستخدام `docker compose up -d` -إذا كنت ترغب في ترقية مثيلك بزيادة بعض الإصدارات، مثل الانتقال من v0.33.0 إلى v0.35.0، يجب أن تقوم بترقية مثيلك بشكل تسلسلي، في هذا المثال من v0.33.0 إلى v0.34.0، ثم من v0.34.0 إلى v0.35.0. - **تأكد من أن لديك نسخة احتياطية غير تالفة بعد كل إصدار تمت ترقيته.** ## خطوات الترقية الخاصة بالإصدار -## v1.0 +## After v1.21 -مرحباً Twenty v1.0! 🎉 +We know support sequential upgrades. You don't need to go through each version one by one. -## v0.60 +## Before v1.21 -### تحسين الأداء - -تم تحسين جميع التفاعلات مع واجهة برمجة التطبيقات للبيانات الوصفية للحصول على أداء أفضل، خاصة فيما يتعلق بمعالجة بيانات الكائن وإنشاء المساحات. - -أعدنا تصميم استراتيجيتنا للتخزين المؤقت لإعطاء الأولوية للوصول عبر التخزين المؤقت على استعلامات قاعدة البيانات قدر الإمكان، مما أدى إلى تحسين كبير في أداء عمليات واجهة برمجة التطبيقات للبيانات الوصفية. - -إذا واجهت أي مشاكل في وقت التشغيل بعد الترقية، قد تحتاج إلى مسح التخزين المؤقت لضمان تزامنه مع أحدث التغييرات. قم بتشغيل هذا الأمر في حاوية خادم twenty الخاص بك: - -```bash -yarn command:prod cache:flush -``` - -### v0.55 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.55 - -لم تعد بحاجة إلى تشغيل أي أمر، الصورة الجديدة ستعتني بتشغيل جميع الترحيلات المطلوبة تلقائيًا. - -### خطأ: `User does not have permission` - -إذا واجهت أخطاء في الأذونات في معظم الطلبات بعد الترقية، فقد تحتاج إلى مسح التخزين المؤقت لإعادة حساب أحدث الأذونات. - -في حاوية خادم `twenty` الخاص بك، قم بتشغيل: - -```bash -yarn command:prod cache:flush -``` - -هذه المشكلة خاصة بهذا الإصدار من Twenty ولا يجب أن تكون ضرورية في الترقيات المستقبلية. - -### v0.54 - -منذ الإصدار `0.53`، لا حاجة لأي إجراءات يدوية. - -#### إيقاف تشغيل مخطط البيانات الوصفية - -قمنا بدمج مخطط `metadata` مع مخطط `core` لتبسيط استرجاع البيانات من `TypeORM`. -قمنا بدمج خطوة تنفيذ الأمر `migrate` مع الأمر `upgrade`. لا ننصح بتشغيل `migrate` يدويًا داخل أي من حاويات الخادم/العمل الخاصة بك. - -### منذ v0.53 - -بدءًا من الإصدار `0.53`، تتم الترقية بشكل برمجي داخل `DockerFile`، مما يعني أنه من الآن فصاعدًا، لن تحتاج إلى تشغيل أي أوامر يدويًا بعد الآن. - -تأكد من متابعة الترقية الخاصة بك تسلسليًا، دون تخطي أي إصدار رئيسي (على سبيل المثال `0.43.3` إلى `0.44.0` مسموح، ولكن `0.43.1` إلى `0.45.0` غير مسموح)، قد يؤدي بخلاف ذلك إلى عدم تزامن إصدار مساحة العمل مما قد يؤدي إلى خطأ في وقت التشغيل وفقدان الوظائف. - -للتحقق مما إذا كانت مساحة العمل قد تمت ترقيتها بشكل صحيح ، يمكنك مراجعة نسختها في قاعدة البيانات في جدول `core.workspace`. - -يجب أن تكون دائمًا في نطاق إصدار `major.minor` لحساب Twenty الحالي الخاص بك ، ويمكنك مشاهدة نسخة حسابك في لوحة المدير (في `/settings/admin-panel`، يمكن الوصول إليها إذا كانت خاصية `canAccessFullAdminPanel` الخاصة بالمستخدم مصفوفة إلى true في قاعدة البيانات) أو عن طريق تشغيل `echo $APP_VERSION` في حاوية `twenty-server` الخاصة بك. - -لإصلاح إصدار مساحة العمل غير المتزامن ، سيتعين عليك الترقية من الإصدار المعني لـ Twenty باتباع دليل الترقية الخاص ذو الصلة تسلسليًا وهكذا حتى يصل إلى الإصدار المطلوب. - -#### إزالة `auditLog` - -لقد قمنا بإزالة كائن المعيار auditLog، مما يعني أن حجم النسخة الاحتياطية الخاصة بك قد يقل بشكل كبير بعد هذه الترقية. - -### من v0.51 إلى v0.52 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.52 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### لدي مساحة عمل محظورة في الإصدار بين `0.52.0` و`0.52.6` - -لسوء الحظ، تم إزالة `0.52.0` و`0.52.6` بالكامل من dockerHub. -سيتعين عليك تحديث نسخة مساحة العمل يدويًا إلى `0.51.0` في قاعدة البيانات والترقية باستخدام إصدار twenty عند `0.52.11` باتباع دليل الترقية الخاص به أعلاه. - -### من v0.50 إلى v0.51 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.51 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### من v0.44.0 إلى v0.50.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.50.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### تغيير ملف docker-compose.yml - -يتضمن هذا الإصدار تغييرًا في `docker-compose.yml` لمنح خدمة `worker` إمكانية الوصول إلى وحدة التخزين `server-local-data`. -يرجى تحديث `docker-compose.yml` المحلي الخاص بك بـ [docker-compose.yml v0.50.0](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml) - -### من v0.43.0 إلى v0.44.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.44.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### من v0.42.0 إلى v0.43.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.43.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -في هذا الإصدار، قمنا أيضًا بالتحول إلى صورة postgres:16 في docker-compose.yml. - -#### (الخيار 1) ترحيل قاعدة البيانات - -احتفاظ بصورة postgres-spilo الحالية مقبول، ولكن سيتعين عليك تجميد الإصدار في docker-compose.yml ليكون 0.43.0. - -#### (الخيار 2) ترحيل قاعدة البيانات - -إذا كنت تريد ترحيل قاعدة بياناتك إلى الصورة الجديدة postgres:16، يرجى اتباع هذه الخطوات: - -1. نسخ قاعدة البيانات الخاصة بك من حاوية postgres-spilo القديمة - -``` -docker exec -it twenty-db-1 sh -pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql -exit -docker cp twenty-db-1:/home/postgres/databases_backup.sql . -``` - -تأكد من أن ملف النسخ الاحتياطي ليس فارغًا. - -2. قم بترقية docker-compose.yml الخاص بك لاستخدام صورة postgres:16 كما هو في الملف [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml). - -3. استعادة قاعدة البيانات إلى الحاوية الجديدة postgres:16 - -``` -docker cp databases_backup.sql twenty-db-1:/databases_backup.sql -docker exec -it twenty-db-1 sh -psql -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} -f databases_backup.sql -exit -``` - -### من v0.41.0 إلى v0.42.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.42.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.42 -``` - -**متغيرات البيئة** - -* تمت الإزالة: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT` -* تمت الإضافة: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED` - -### من v0.40.0 إلى v0.41.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.41.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.41 -``` - -**متغيرات البيئة** - -* تمت الإزالة: `AUTH_MICROSOFT_TENANT_ID` - -### من v0.35.0 إلى v0.40.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.40.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.40 -``` - -**متغيرات البيئة** - -* تمت الإضافة: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL` - -### من v0.34.0 إلى v0.35.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.35.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.35 -``` - -أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata) -أمر `yarn command:prod upgrade-0.35` يتولى ترقية البيانات إلى جميع المساحات. - -**متغيرات البيئة** - -* قمنا باستبدال `ENABLE_DB_MIGRATIONS` بـ `DISABLE_DB_MIGRATIONS` (القيمة الافتراضية الآن `false`, على الأرجح لن تحتاج إلى تعيين أي شيء) - -### من v0.33.0 إلى v0.34.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.34.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.34 -``` - -أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata) -أمر `yarn command:prod upgrade-0.34` يتولى ترقية البيانات إلى جميع المساحات. - -**متغيرات البيئة** - -* تمت الإزالة: `FRONT_BASE_URL` -* تمت الإضافة: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT` - -لقد قمنا بتحديث الطريقة التي نتعامل بها مع عنوان URL الخاص بالواجهة الأمامية. -يمكنك الآن تعيين عنوان URL الخاص بالواجهة الأمامية باستخدام متغيرات `FRONT_DOMAIN`, `FRONT_PROTOCOL` و`FRONT_PORT`. -إذا لم يتم تعيين FRONT_DOMAIN، فسوف يتراجع عنوان URL للواجهة الأمامية إلى `SERVER_URL`. - -### من v0.32.0 إلى v0.33.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.33.0 - -``` -yarn command:prod cache:flush -yarn database:migrate:prod -yarn command:prod upgrade-0.33 -``` - -أمر `yarn command:prod cache:flush` سيقوم بمسح ذاكرة تخزين Redis المؤقتة. -أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata) -أمر `yarn command:prod upgrade-0.33` يتولى ترقية البيانات إلى جميع المساحات. - -بدءًا من هذا الإصدار، أصبحت صورة twenty-postgres للقاعدة غير نشطة وتم استخدام twenty-postgres-spilo بدلاً منها. -إذا كنت ترغب في الاستمرار باستخدام صورة twenty-postgres، فما عليك سوى استبدال `twentycrm/twenty-postgres:${TAG}` بـ `twentycrm/twenty-postgres` في docker-compose.yml. - -### من v0.31.0 إلى v0.32.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.32.0 - -**ترقية المخطط والبيانات** - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.32 -``` - -أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata) -أمر `yarn command:prod upgrade-0.32` يتولى ترقية البيانات إلى جميع المساحات. - -**متغيرات البيئة** - -لقد قمنا بتحديث الطريقة التي نتعامل بها مع اتصال Redis. - -* تمت الإزالة: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD` -* تمت الإضافة: `REDIS_URL` - -قم بتحديث ملفك `.env` لاستخدام المتغير الجديد `REDIS_URL` بدلاً من معلمات اتصال Redis الفردية. - -قمنا أيضًا بتبسيط الطريقة التي نتعامل بها مع رموز JWT. - -* تمت الإزالة: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET` -* تمت الإضافة: `APP_SECRET` - -قم بتحديث ملفك `.env` لاستخدام المتغير الجديد `APP_SECRET` بدلاً من الأسرار الفردية للرموز (يمكنك استخدام نفس السر كما كان من قبل أو توليد سلسلة عشوائية جديدة) - -**الحساب المتصل** - -إذا كنت تستخدم حسابًا متصلًا لمزامنة رسائل بريدك الإلكتروني في جوجل والتقويمات، فستحتاج إلى تفعيل [People API](https://developers.google.com/people) في وحدة تحكم مشرف جوجل لديك. - -### من v0.30.0 إلى v0.31.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.31.0 - -**ترقية المخطط والبيانات**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.31 -``` - -أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata) -أمر `yarn command:prod upgrade-0.31` يتولى ترقية البيانات إلى جميع المساحات. - -### من v0.24.0 إلى v0.30.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.30.0 - -**تغيير كبير**: -لتحسين الأداء، يتطلب Twenty الآن تكوين Redis للتخزين المؤقت. قمنا بتحديث [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) لتعكس ذلك. -تأكد من تحديث إعدادات التكوين الخاصة بك وتحديث المتغيرات البيئية الخاصة بك وفقًا لذلك: - -``` -REDIS_HOST={your-redis-host} -REDIS_PORT={your-redis-port} -CACHE_STORAGE_TYPE=redis -``` - -**ترقية المخطط والبيانات**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.30 -``` - -أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata) -أمر `yarn command:prod upgrade-0.30` يتولى ترقية البيانات إلى جميع المساحات. - -### من v0.23.0 إلى v0.24.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.24.0 - -قم بتشغيل الأوامر التالية: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.24 -``` - -أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على هيكل قاعدة البيانات (مخططات core وmetadata) -أمر `yarn command:prod upgrade-0.24` يتولى ترقية البيانات إلى جميع المساحات. - -### من v0.22.0 إلى v0.23.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.23.0 - -قم بتشغيل الأوامر التالية: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.23 -``` - -أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على قاعدة البيانات. -أمر `yarn command:prod upgrade-0.23` يتولى ترقية البيانات، بما في ذلك نقل الأنشطة إلى المهام/الملاحظات. - -### من v0.21.0 إلى v0.22.0 - -قم بترقية مثيل Twenty الخاص بك لاستخدام صورة v0.22.0 - -قم بتشغيل الأوامر التالية: - -``` -yarn database:migrate:prod -yarn command:prod workspace:sync-metadata -f -yarn command:prod upgrade-0.22 -``` - -أمر `yarn database:migrate:prod` سيقوم بتطبيق الترقيات على قاعدة البيانات. -الأمر `yarn command:prod workspace:sync-metadata -f` سيزامن تعريف الكائنات القياسية مع جداول البيانات الوصفية ويطبق الترقيات المطلوبة على مساحات العمل الموجودة. -الأمر `yarn command:prod upgrade-0.22` سيقوم بتطبيق تحويلات بيانات محددة للتكيف مع الخيارات الافتراضية الجديدة لتوثيق الطلبات في الكائنات. +Make sure to go through every major tagged version when upgrading (upgrade v1.6.x to v.7.y, then v.7.y to v.8.z, etc.). diff --git a/packages/twenty-docs/l/ar/navigation.json b/packages/twenty-docs/l/ar/navigation.json index 85bb79fe928..2ffed74d3b5 100644 --- a/packages/twenty-docs/l/ar/navigation.json +++ b/packages/twenty-docs/l/ar/navigation.json @@ -1,24 +1,27 @@ { "tabs": { + "gettingStarted": { + "label": "البدء", + "groups": { + "welcome": { + "label": "Welcome" + }, + "coreConcepts": { + "label": "Core Concepts" + } + } + }, "userGuide": { "label": "دليل المستخدم", "groups": { - "discoverTwenty": { - "label": "اكتشف Twenty", - "groups": { - "gettingStartedCapabilities": { - "label": "القدرات" - }, - "gettingStartedHowTos": { - "label": "الإرشادات" - } - } + "userGuideOverview": { + "label": "نظرة عامة" }, "dataModel": { "label": "نموذج البيانات", "groups": { - "dataModelCapabilities": { - "label": "القدرات" + "dataModelReference": { + "label": "Reference" }, "dataModelHowTos": { "label": "الإرشادات" @@ -28,8 +31,8 @@ "dataMigration": { "label": "ترحيل البيانات", "groups": { - "dataMigrationCapabilities": { - "label": "القدرات" + "dataMigrationReference": { + "label": "Reference" }, "dataMigrationHowTos": { "label": "الإرشادات" @@ -39,8 +42,8 @@ "calendarEmails": { "label": "التقويم والبريد الإلكتروني", "groups": { - "calendarEmailsCapabilities": { - "label": "القدرات" + "calendarEmailsReference": { + "label": "Reference" }, "calendarEmailsHowTos": { "label": "الإرشادات" @@ -50,8 +53,8 @@ "workflows": { "label": "سير العمل", "groups": { - "workflowsCapabilities": { - "label": "القدرات" + "workflowsReference": { + "label": "Reference" }, "workflowsHowTos": { "label": "الإرشادات", @@ -75,21 +78,26 @@ "ai": { "label": "الذكاء الاصطناعي", "groups": { - "aiCapabilities": { - "label": "القدرات" + "aiReference": { + "label": "Reference" }, "aiHowTos": { "label": "الإرشادات" } } }, - "viewsPipelines": { - "label": "طرق العرض والمسارات", + "layout": { + "label": "التخطيط", "groups": { - "viewsPipelinesCapabilities": { - "label": "القدرات" + "layoutReference": { + "label": "Reference", + "groups": { + "layoutViews": { + "label": "العروض" + } + } }, - "viewsPipelinesHowTos": { + "layoutHowTos": { "label": "الإرشادات" } } @@ -97,8 +105,8 @@ "dashboards": { "label": "لوحات القيادة", "groups": { - "dashboardsCapabilities": { - "label": "القدرات" + "dashboardsReference": { + "label": "Reference" }, "dashboardsHowTos": { "label": "الإرشادات" @@ -108,8 +116,8 @@ "permissionsAccess": { "label": "الصلاحيات والوصول", "groups": { - "permissionsAccessCapabilities": { - "label": "القدرات" + "permissionsAccessReference": { + "label": "Reference" }, "permissionsAccessHowTos": { "label": "الإرشادات" @@ -119,8 +127,8 @@ "billing": { "label": "الفوترة", "groups": { - "billingCapabilities": { - "label": "القدرات" + "billingReference": { + "label": "Reference" }, "billingHowTos": { "label": "الإرشادات" @@ -130,8 +138,8 @@ "settings": { "label": "\\ا\\ل\\إ\\ع\\د\\ا\\د\\ا\\ت", "groups": { - "settingsCapabilities": { - "label": "القدرات" + "settingsReference": { + "label": "Reference" }, "settingsHowTos": { "label": "الإرشادات" @@ -143,59 +151,20 @@ "developers": { "label": "المطورون", "groups": { - "developersGroup": { - "label": "المطورون" + "developersOverview": { + "label": "نظرة عامة" }, - "extend": { - "label": "التوسيع", - "groups": { - "apps": { - "label": "التطبيقات" - } - } + "apps": { + "label": "التطبيقات" + }, + "api": { + "label": "واجهة برمجة التطبيقات" }, "selfHost": { - "label": "الاستضافة الذاتية", - "groups": { - "selfHostCapabilities": { - "label": "القدرات" - } - } + "label": "الاستضافة الذاتية" }, "contribute": { - "label": "المساهمة", - "groups": { - "contributeCapabilities": { - "label": "القدرات", - "groups": { - "frontendDevelopment": { - "label": "تطوير الواجهة الأمامية", - "groups": { - "twentyUi": { - "label": "Twenty UI", - "groups": { - "display": { - "label": "عرض" - }, - "feedback": { - "label": "التغذية الراجعة" - }, - "input": { - "label": "إدخال" - }, - "navigation": { - "label": "التنقل" - } - } - } - } - }, - "backendDevelopment": { - "label": "تطوير الواجهة الخلفية" - } - } - } - } + "label": "المساهمة" } } } diff --git a/packages/twenty-docs/l/ar/twenty-ui/display/app-tooltip.mdx b/packages/twenty-docs/l/ar/twenty-ui/display/app-tooltip.mdx index 14fccf10772..1a802a3c782 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/display/app-tooltip.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/display/app-tooltip.mdx @@ -1,5 +1,6 @@ --- title: تلميح التطبيق +icon: رسالة --- diff --git a/packages/twenty-docs/l/ar/twenty-ui/display/checkmark.mdx b/packages/twenty-docs/l/ar/twenty-ui/display/checkmark.mdx index f3d73a842a4..6d4542c19e8 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/display/checkmark.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/display/checkmark.mdx @@ -1,5 +1,6 @@ --- title: علامة صحيح +icon: circle-check --- diff --git a/packages/twenty-docs/l/ar/twenty-ui/display/icons.mdx b/packages/twenty-docs/l/ar/twenty-ui/display/icons.mdx index 5984cb9e109..17b5a6b6abd 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/display/icons.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/display/icons.mdx @@ -1,5 +1,6 @@ --- title: الأيقونات +icon: الأيقونات --- diff --git a/packages/twenty-docs/l/ar/twenty-ui/display/soon-pill.mdx b/packages/twenty-docs/l/ar/twenty-ui/display/soon-pill.mdx index e084937ddbc..bbacf4a2295 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/display/soon-pill.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/display/soon-pill.mdx @@ -2,7 +2,6 @@ title: شارة قريبًا --- - شارة صغيرة أو "كبسولة" للإشارة إلى أن شيئًا ما قادم قريبًا. ```jsx diff --git a/packages/twenty-docs/l/ar/twenty-ui/display/tag.mdx b/packages/twenty-docs/l/ar/twenty-ui/display/tag.mdx index c4ec923427c..51d26fdec38 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/display/tag.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/display/tag.mdx @@ -1,8 +1,8 @@ --- title: علامة +icon: علامة --- - مكوّن لتصنيف المحتوى أو وسمه بصريًا. diff --git a/packages/twenty-docs/l/ar/twenty-ui/input/buttons.mdx b/packages/twenty-docs/l/ar/twenty-ui/input/buttons.mdx index 6703059346a..2c55a677ab3 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/input/buttons.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/input/buttons.mdx @@ -1,5 +1,6 @@ --- title: الأزرار +icon: hand-pointer --- diff --git a/packages/twenty-docs/l/ar/twenty-ui/input/checkbox.mdx b/packages/twenty-docs/l/ar/twenty-ui/input/checkbox.mdx index 6bf67ebbbb8..db3e1e244a1 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/input/checkbox.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/input/checkbox.mdx @@ -1,5 +1,6 @@ --- title: مربع اختيار +icon: square-check --- diff --git a/packages/twenty-docs/l/ar/twenty-ui/input/color-scheme.mdx b/packages/twenty-docs/l/ar/twenty-ui/input/color-scheme.mdx index 58cb0083f3e..33b3fea6327 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/input/color-scheme.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/input/color-scheme.mdx @@ -1,5 +1,6 @@ --- title: طريقة عرض الألوان +icon: لوحة الألوان --- diff --git a/packages/twenty-docs/l/ar/twenty-ui/input/radio.mdx b/packages/twenty-docs/l/ar/twenty-ui/input/radio.mdx index 6a42406ccfb..45f79f2615d 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/input/radio.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/input/radio.mdx @@ -1,5 +1,6 @@ --- title: راديو +icon: circle-dot --- diff --git a/packages/twenty-docs/l/ar/twenty-ui/input/toggle.mdx b/packages/twenty-docs/l/ar/twenty-ui/input/toggle.mdx index ae2ff7212fa..7ad0495caab 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/input/toggle.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/input/toggle.mdx @@ -1,8 +1,8 @@ --- title: تبديل +icon: toggle-on --- - diff --git a/packages/twenty-docs/l/ar/twenty-ui/introduction.mdx b/packages/twenty-docs/l/ar/twenty-ui/introduction.mdx index 0f8444fef8f..ad43456eb3b 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/introduction.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/introduction.mdx @@ -1,5 +1,6 @@ --- title: نظرة عامة +icon: لوحة الألوان description: مكتبة المكونات لتطبيق Twenty CRM --- diff --git a/packages/twenty-docs/l/ar/twenty-ui/navigation.mdx b/packages/twenty-docs/l/ar/twenty-ui/navigation.mdx index 78d2a2213b8..d73f7041ccb 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/navigation.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/navigation.mdx @@ -1,5 +1,6 @@ --- title: التنقل +icon: compass --- diff --git a/packages/twenty-docs/l/ar/twenty-ui/navigation/links.mdx b/packages/twenty-docs/l/ar/twenty-ui/navigation/links.mdx index cd37762843a..332eb0783f7 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/navigation/links.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/navigation/links.mdx @@ -1,5 +1,6 @@ --- title: روابط +icon: رابط --- diff --git a/packages/twenty-docs/l/ar/twenty-ui/navigation/menu-item.mdx b/packages/twenty-docs/l/ar/twenty-ui/navigation/menu-item.mdx index 2e5170f3078..9850c16b4a8 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/navigation/menu-item.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/navigation/menu-item.mdx @@ -1,8 +1,8 @@ --- title: عنصر قائمة +icon: bars --- - عنصر قائمة متعدد الاستخدامات مصمم للاستخدام في قائمة أو قائمة تنقل. diff --git a/packages/twenty-docs/l/ar/twenty-ui/navigation/navigation-bar.mdx b/packages/twenty-docs/l/ar/twenty-ui/navigation/navigation-bar.mdx index ad1229d98b2..bd8c3459ca9 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/navigation/navigation-bar.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/navigation/navigation-bar.mdx @@ -1,8 +1,8 @@ --- title: شريط التنقل +icon: bars --- - يستعرض شريط التنقل الذي يحتوي على عدة مكونات `NavigationBarItem`. diff --git a/packages/twenty-docs/l/ar/twenty-ui/progress-bar.mdx b/packages/twenty-docs/l/ar/twenty-ui/progress-bar.mdx index d4e9b58a293..d9cc92338a6 100644 --- a/packages/twenty-docs/l/ar/twenty-ui/progress-bar.mdx +++ b/packages/twenty-docs/l/ar/twenty-ui/progress-bar.mdx @@ -2,7 +2,6 @@ title: التغذية الراجعة --- - يشير إلى تقدم أو عد تنازلي ويتحرك من اليمين إلى اليسار. diff --git a/packages/twenty-docs/l/ar/user-guide/billing/overview.mdx b/packages/twenty-docs/l/ar/user-guide/billing/overview.mdx index 9ebebb89235..edca68d3ac7 100644 --- a/packages/twenty-docs/l/ar/user-guide/billing/overview.mdx +++ b/packages/twenty-docs/l/ar/user-guide/billing/overview.mdx @@ -3,7 +3,6 @@ title: الفوترة description: تعرّف على تسعير Twenty وأدِر اشتراكك. --- - تقدّم Twenty خطط تسعير مرنة لتلبية احتياجات فريقك. أدِر اشتراكك، وتتبع أرصدة سير العمل، واطّلع على الفواتير — وكل ذلك من **Settings → Billing**. ## ما الذي يتضمنه هذا القسم diff --git a/packages/twenty-docs/l/ar/user-guide/calendar-emails/overview.mdx b/packages/twenty-docs/l/ar/user-guide/calendar-emails/overview.mdx index cc7c298d91b..cf2f7015c63 100644 --- a/packages/twenty-docs/l/ar/user-guide/calendar-emails/overview.mdx +++ b/packages/twenty-docs/l/ar/user-guide/calendar-emails/overview.mdx @@ -3,7 +3,6 @@ title: Calendar & Emails description: Connect your email and calendar accounts to Twenty. --- - ## Connection Options ### حساب Google (Gmail وتقويم Google) diff --git a/packages/twenty-docs/l/ar/user-guide/dashboards/overview.mdx b/packages/twenty-docs/l/ar/user-guide/dashboards/overview.mdx index b4d54950cb9..877b8253e27 100644 --- a/packages/twenty-docs/l/ar/user-guide/dashboards/overview.mdx +++ b/packages/twenty-docs/l/ar/user-guide/dashboards/overview.mdx @@ -3,7 +3,6 @@ title: لوحات القيادة description: تعلّم أساسيات إعداد التقارير ولوحات المعلومات في Twenty. --- - لوحات المعلومات حاليًا في الإصدار التجريبي. قم بتفعيلها ضمن **الإعدادات → التحديثات → الوصول المبكر**. diff --git a/packages/twenty-docs/l/ar/user-guide/data-migration/overview.mdx b/packages/twenty-docs/l/ar/user-guide/data-migration/overview.mdx index c6b2f560f0c..8da8eb15397 100644 --- a/packages/twenty-docs/l/ar/user-guide/data-migration/overview.mdx +++ b/packages/twenty-docs/l/ar/user-guide/data-migration/overview.mdx @@ -5,7 +5,6 @@ description: استيراد وتصدير بيانات CRM عبر ملفات CSV import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## طرق الاستيراد تدعم Twenty طريقتين رئيستين لاستيراد البيانات: diff --git a/packages/twenty-docs/l/ar/user-guide/data-model/overview.mdx b/packages/twenty-docs/l/ar/user-guide/data-model/overview.mdx index f6ed3051f03..98a8f3612ff 100644 --- a/packages/twenty-docs/l/ar/user-guide/data-model/overview.mdx +++ b/packages/twenty-docs/l/ar/user-guide/data-model/overview.mdx @@ -3,7 +3,6 @@ title: نموذج البيانات description: تعرّف إلى نموذج البيانات وكيفية تصميم نموذج يناسب نشاطك التجاري. --- - ## ما هو نموذج البيانات؟ نموذج البيانات هو الهيكل الذي يحدّد كيفية تنظيم المعلومات في نظام إدارة علاقات العملاء (CRM) لديك. فكّر فيه باعتباره **المخطط** لبيانات عملائك — تصمّمه مرة واحدة، ثم تملؤه ببياناتك الفعلية. diff --git a/packages/twenty-docs/l/ar/user-guide/introduction.mdx b/packages/twenty-docs/l/ar/user-guide/introduction.mdx index 8aa0f84493f..6bf7f407ab3 100644 --- a/packages/twenty-docs/l/ar/user-guide/introduction.mdx +++ b/packages/twenty-docs/l/ar/user-guide/introduction.mdx @@ -1,16 +1,11 @@ --- -title: اكتشف Twenty +title: دليل المستخدم description: مرحباً بك في دليل مستخدم Twenty، مصادرك للإعدادات المتقدمة وأفضل الممارسات. --- import { CardTitle } from "/snippets/card-title.mdx" - - اكتشف Twenty - تعرّف على ماهية Twenty وكيف يمكن أن تساعد عملك. - - نموذج البيانات خصّص نموذج بياناتك ليتوافق مع عمليات عملك. @@ -36,9 +31,9 @@ import { CardTitle } from "/snippets/card-title.mdx" عزّز فريقك بوكلاء الذكاء الاصطناعي. - - طرق العرض والمسارات - نظّم بياناتك من خلال طرق عرض عملية ومسارات. + + Layout + Navigation, views, and record page customization. diff --git a/packages/twenty-docs/l/ar/user-guide/layout/capabilities/navigation.mdx b/packages/twenty-docs/l/ar/user-guide/layout/capabilities/navigation.mdx new file mode 100644 index 00000000000..af5582c303b --- /dev/null +++ b/packages/twenty-docs/l/ar/user-guide/layout/capabilities/navigation.mdx @@ -0,0 +1,32 @@ +--- +title: التنقل +description: Customize the left sidebar to match how your team works. +--- + +The left sidebar is your primary way to move around Twenty. It's fully customizable — you can reorganize it to match your workflow without touching any settings page. + +## Reordering items + +Drag and drop any item in the sidebar to change its position. The order is saved per user, so each team member can arrange their own sidebar. + +## مجلدات + +Group related items into folders. For example, you might create a "Sales" folder containing your pipeline views, a "Support" folder for tickets, or an "Operations" folder for internal objects. + +To create a folder, right-click in the sidebar or use the `+` button. + +## Hiding objects + +Objects you don't use can be hidden from the sidebar. They're not deleted — they're just out of the way. You can show them again anytime from Settings > Data Model. + +## المفضلات + +Pin views, records, or searches to the Favorites section at the top of the sidebar for one-click access. Favorites are personal — each user manages their own. + +## Custom links + +Add links to external tools directly in the sidebar. Useful for linking to your wiki, dashboards in other tools, or any URL your team uses regularly. + +## Command menu + +Press `Cmd+K` (or `Ctrl+K`) to open the command menu — a quick-access search bar for jumping to any record, view, or action without navigating the sidebar. diff --git a/packages/twenty-docs/l/ar/user-guide/layout/capabilities/record-pages.mdx b/packages/twenty-docs/l/ar/user-guide/layout/capabilities/record-pages.mdx new file mode 100644 index 00000000000..3eb4c8f2558 --- /dev/null +++ b/packages/twenty-docs/l/ar/user-guide/layout/capabilities/record-pages.mdx @@ -0,0 +1,51 @@ +--- +title: صفحات السجل},{ +description: خصص تخطيط صفحات تفاصيل كل سجل باستخدام علامات تبويب وعناصر واجهة. +--- + +عند فتح سجل في Twenty، تتكون صفحة التفاصيل من **علامات تبويب** و**عناصر واجهة**. كلاهما قابل للتخصيص بالكامل حسب نوع الكائن. + +## علامات التبويب + +يمكن أن تحتوي كل صفحة سجل على عدة علامات تبويب — مشابهة لعلامات التبويب في المتصفح. استخدمها لتنظيم الجوانب المختلفة للسجل. على سبيل المثال، قد يحتوي سجل شركة على علامات تبويب مثل نظرة عامة والاتصالات والمهام والملفات. + +يمكنك: + +* إضافة علامات التبويب وإزالتها +* إعادة تسمية علامات التبويب +* أعد ترتيب علامات التبويب بالسحب +* حدد علامة التبويب التي تظهر افتراضيًا + +## الأدوات + +الأدوات هي اللبنات الأساسية داخل كل علامة تبويب. تشمل أنواع الأدوات المتاحة: + +| أداة | ما الذي تعرضه | +| ----------------------- | ----------------------------------------- | +| **حقول** | حقول السجل، مجمّعة أو بشكل فردي | +| **السجلات المرتبطة** | جدول سجلات مرتبطة عبر علاقة | +| **الرسائل الإلكترونية** | سجل البريد الإلكتروني من الحسابات المتصلة | +| **التقويم** | أحداث التقويم المرتبطة بالسجل | +| **الجدول الزمني** | سجل الأنشطة والأحداث | +| **المهام** | المهام المرتبطة | +| **الملاحظات** | ملاحظات بنص منسق | +| **الملفات** | مرفقات الملفات | +| **الرسوم البيانية** | بيانات مرئية من السجلات المرتبطة | +| **iFrame** | محتوى خارجي مُضمّن | +| **نص منسق** | محتوى ثابت أو أوصاف | + +## تخصيص صفحة السجل + +1. افتح أي سجل +2. اضغط على `Cmd+K` وابحث عن "تحرير تخطيط صفحة السجل" +3. أنت الآن في وضع التخصيص: + * **أضف أدوات** من منتقي الأدوات + * **اسحب الأدوات** لإعادة وضعها على الشبكة + * **غيّر حجم الأدوات** بسحب حوافها + * **اضبط الحقول** المعروضة داخل كل أداة + * **أدِر علامات التبويب** — أضف، أزل، أعد التسمية، وأعد الترتيب +4. احفظ تغييراتك — سيتم تطبيقها على جميع السجلات لذلك النوع من الكائنات + +## ظهور الحقول + +ضمن أداة الحقول، يمكنك التحكّم في أي الحقول مرئية وبأي ترتيب. يتيح لك ذلك إنشاء تخطيطات مركّزة — على سبيل المثال، إظهار الحقول الأهم فقط في علامة تبويب نظرة عامة ووضع الحقول التفصيلية في علامة تبويب منفصلة. diff --git a/packages/twenty-docs/l/ar/user-guide/layout/overview.mdx b/packages/twenty-docs/l/ar/user-guide/layout/overview.mdx new file mode 100644 index 00000000000..24dd09a7814 --- /dev/null +++ b/packages/twenty-docs/l/ar/user-guide/layout/overview.mdx @@ -0,0 +1,45 @@ +--- +title: التخطيط +description: Customize how you navigate, browse, and view records in Twenty. +--- + +Twenty's layout is customizable at three levels: how you navigate the app, how you browse lists of records, and what you see when you open an individual record. + +## التنقل + +The left sidebar is fully customizable. يمكنك: + +* **Reorder items** by dragging and dropping +* **Create folders** to group related objects and views +* **Hide objects** you don't use +* **Add custom links** to external tools +* **Pin favorites** for quick access to views, records, or searches + +[Navigation reference →](/l/ar/user-guide/layout/capabilities/navigation) + +## العروض + +Views control how lists of records are displayed. Twenty supports three view types: + +| عرض | Best for | +| ------------ | ---------------------------------------------------------------------- | +| **Table** | Working with many records at once — spreadsheet-style rows and columns | +| **Kanban** | Pipeline tracking — drag-and-drop cards organized by stage | +| **Calendar** | Time-based planning — records plotted by a date field | + +Each view saves its own filters, sorting, field visibility, and grouping configuration. Views can be shared with the workspace or kept private. + +[Table views →](/l/ar/user-guide/views-pipelines/capabilities/table-views) · [Kanban views →](/l/ar/user-guide/views-pipelines/capabilities/kanban-views) · [Calendar view →](/l/ar/user-guide/views-pipelines/capabilities/calendar-view) + +## Record pages + +When you open a record, the detail page is built from configurable tabs and widgets. يمكنك: + +* **Add, remove, and reorder tabs** on any record type +* **Configure widgets** — fields, related records, emails, timeline, calendar, tasks, notes, files, charts, iframes, and more +* **Drag and resize widgets** on a grid layout +* **Control field visibility** per widget + +Enter layout customization mode from the command menu (`Cmd+K` → "Edit record page layout"). + +[Record pages reference →](/l/ar/user-guide/layout/capabilities/record-pages) diff --git a/packages/twenty-docs/l/ar/user-guide/permissions-access/overview.mdx b/packages/twenty-docs/l/ar/user-guide/permissions-access/overview.mdx index 9aa43e17434..326e5d30f63 100644 --- a/packages/twenty-docs/l/ar/user-guide/permissions-access/overview.mdx +++ b/packages/twenty-docs/l/ar/user-guide/permissions-access/overview.mdx @@ -3,7 +3,6 @@ title: الأذونات والوصول description: إدارة الأدوار والأذونات والتحكم في الوصول ضمن مساحة العمل. --- - يتيح لك نظام الأذونات في Twenty التحكم بمن يمكنه الوصول إلى البيانات وتعديلها في مساحة العمل لديك. أنشئ أدوارًا، وامنح أذونات، وقم بتكوين تسجيل الدخول الأحادي (SSO) للوصول الآمن. ## ما الذي يتضمنه هذا القسم diff --git a/packages/twenty-docs/l/ar/user-guide/settings/overview.mdx b/packages/twenty-docs/l/ar/user-guide/settings/overview.mdx index 9f9c8a79059..463481b9d8c 100644 --- a/packages/twenty-docs/l/ar/user-guide/settings/overview.mdx +++ b/packages/twenty-docs/l/ar/user-guide/settings/overview.mdx @@ -3,7 +3,6 @@ title: \ا\ل\إ\ع\د\ا\د\ا\ت description: قم بإعداد مساحة عملك في Twenty باستخدام التكوينات الأساسية. --- - ## الإعداد الأولي عند إنشاء مساحة العمل لأول مرة، توجد عدة إعدادات أساسية لتكوينها. diff --git a/packages/twenty-docs/l/ar/user-guide/views-pipelines/overview.mdx b/packages/twenty-docs/l/ar/user-guide/views-pipelines/overview.mdx index 95746392264..65aa6d8c3b4 100644 --- a/packages/twenty-docs/l/ar/user-guide/views-pipelines/overview.mdx +++ b/packages/twenty-docs/l/ar/user-guide/views-pipelines/overview.mdx @@ -5,7 +5,6 @@ description: تعرّف على كيفية إنشاء العروض وإدارته import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## فهم العروض العروض هي إعدادات محفوظة تحدد كيفية عرض بياناتك. يمكن أن يتضمن كل عرض ما يلي: diff --git a/packages/twenty-docs/l/ar/user-guide/workflows/overview.mdx b/packages/twenty-docs/l/ar/user-guide/workflows/overview.mdx index 2a6aa61a625..ca920b81680 100644 --- a/packages/twenty-docs/l/ar/user-guide/workflows/overview.mdx +++ b/packages/twenty-docs/l/ar/user-guide/workflows/overview.mdx @@ -3,7 +3,6 @@ title: سير العمل description: تعرّف على كيفية إنشاء عمليات الأتمتة في Twenty. --- - ## أهمية تدفقات العمل تم تصميم Twenty لتوفير أقصى قدر من المرونة للمستخدمين. بدلاً من إجبارك على تكييف عمليات عملك مع ميزات ثابتة ومعدة مسبقًا، تتيح تدفقات العمل لك بناء الأتمتة التي تُنشئ نظام إدارة علاقات العملاء (CRM) الأنسب لحالات الاستخدام الفريدة الخاصة بك. diff --git a/packages/twenty-docs/l/cs/developers/contribute/capabilities/backend-development/server-commands.mdx b/packages/twenty-docs/l/cs/developers/contribute/capabilities/backend-development/server-commands.mdx index 02557f6c9d7..99a311f0b67 100644 --- a/packages/twenty-docs/l/cs/developers/contribute/capabilities/backend-development/server-commands.mdx +++ b/packages/twenty-docs/l/cs/developers/contribute/capabilities/backend-development/server-commands.mdx @@ -1,5 +1,6 @@ --- title: Příkazy backendu +icon: terminal --- ## Užitečné příkazy diff --git a/packages/twenty-docs/l/cs/developers/contribute/capabilities/bug-and-requests.mdx b/packages/twenty-docs/l/cs/developers/contribute/capabilities/bug-and-requests.mdx index ec840308c66..b80dc4f0aac 100644 --- a/packages/twenty-docs/l/cs/developers/contribute/capabilities/bug-and-requests.mdx +++ b/packages/twenty-docs/l/cs/developers/contribute/capabilities/bug-and-requests.mdx @@ -1,5 +1,6 @@ --- title: Hlášení chyb, požadavky a pull requesty +icon: bug info: Nahlašujte problémy, žádejte o nové funkce a přispívejte kódem --- diff --git a/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/best-practices-front.mdx b/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/best-practices-front.mdx index 3bcb11583f1..6c2affa2c97 100644 --- a/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/best-practices-front.mdx +++ b/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/best-practices-front.mdx @@ -1,5 +1,6 @@ --- title: Osvědčené postupy +icon: star --- Tento dokument popisuje osvědčené postupy, které byste měli dodržovat při práci na frontend. diff --git a/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx b/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx index 992cb3e82d0..f659ace2248 100644 --- a/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx +++ b/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx @@ -1,5 +1,6 @@ --- title: Architektura složek +icon: folder-tree info: Podrobný pohled na naši architekturu složek --- diff --git a/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/frontend-commands.mdx b/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/frontend-commands.mdx index a585ffa655a..294fc654819 100644 --- a/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/frontend-commands.mdx +++ b/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/frontend-commands.mdx @@ -1,5 +1,6 @@ --- title: Příkazy Frontend +icon: terminal --- ## Užitečné příkazy diff --git a/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/style-guide.mdx b/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/style-guide.mdx index fab7d693800..3f5e84d2e75 100644 --- a/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/style-guide.mdx +++ b/packages/twenty-docs/l/cs/developers/contribute/capabilities/frontend-development/style-guide.mdx @@ -1,5 +1,6 @@ --- title: Stylová příručka +icon: paintbrush --- Tento dokument obsahuje pravidla pro psaní kódu. diff --git a/packages/twenty-docs/l/cs/developers/contribute/capabilities/local-setup.mdx b/packages/twenty-docs/l/cs/developers/contribute/capabilities/local-setup.mdx index 22a49c3edbb..dcc0705c8b6 100644 --- a/packages/twenty-docs/l/cs/developers/contribute/capabilities/local-setup.mdx +++ b/packages/twenty-docs/l/cs/developers/contribute/capabilities/local-setup.mdx @@ -1,5 +1,6 @@ --- title: Místní nastavení +icon: laptop-code description: Průvodce pro přispěvatele (nebo zvídavé vývojáře), kteří chtějí spustit Twenty lokálně. --- diff --git a/packages/twenty-docs/l/cs/developers/contribute/commands.mdx b/packages/twenty-docs/l/cs/developers/contribute/commands.mdx new file mode 100644 index 00000000000..7c051be729e --- /dev/null +++ b/packages/twenty-docs/l/cs/developers/contribute/commands.mdx @@ -0,0 +1,77 @@ +--- +title: Commands +icon: terminal +description: Useful commands for developing Twenty. +--- + +Commands can be run from the repository root using `npx nx`. Use `npx nx run {project}:{command}` for explicit targeting. + +## Starting the App + +```bash +npx nx start twenty-front # Frontend dev server (http://localhost:3001) +npx nx start twenty-server # Backend server (http://localhost:3000) +npx nx run twenty-server:worker # Background worker +``` + +## Database + +```bash +npx nx database:reset twenty-server # Reset and seed database +npx nx run twenty-server:database:migrate:prod # Run migrations +npx nx run twenty-server:database:migrate:generate --name --type # Generate a migration +``` + +## Linting + +```bash +npx nx lint:diff-with-main twenty-front # Lint changed files (fastest) +npx nx lint:diff-with-main twenty-server +npx nx lint twenty-front --configuration=fix # Auto-fix +``` + +## Type Checking + +```bash +npx nx typecheck twenty-front +npx nx typecheck twenty-server +``` + +## Testování + +```bash +# Frontend +npx nx test twenty-front # Jest unit tests +npx nx storybook:build twenty-front # Build Storybook +npx nx storybook:test twenty-front # Storybook tests + +# Backend +npx nx run twenty-server:test:unit # Unit tests +npx nx run twenty-server:test:integration # Integration tests +npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset + +# Single file (fastest) +npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs +``` + +## GraphQL + +```bash +npx nx run twenty-front:graphql:generate # Regenerate types +npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema +``` + +## Překlady + +```bash +npx nx run twenty-front:lingui:extract # Extract strings +npx nx run twenty-front:lingui:compile # Compile translations +``` + +## Build + +```bash +npx nx build twenty-shared # Must be built first +npx nx build twenty-front +npx nx build twenty-server +``` diff --git a/packages/twenty-docs/l/cs/developers/contribute/style-guide.mdx b/packages/twenty-docs/l/cs/developers/contribute/style-guide.mdx new file mode 100644 index 00000000000..6c141489841 --- /dev/null +++ b/packages/twenty-docs/l/cs/developers/contribute/style-guide.mdx @@ -0,0 +1,176 @@ +--- +title: Stylová příručka +icon: paintbrush +description: Code conventions and best practices for contributing to Twenty. +--- + +## React + +### Functional components only + +Always use TSX functional components with named exports. + +```tsx +// ❌ Bad +const MyComponent = () => { + return
Hello World
; +}; +export default MyComponent; + +// ✅ Good +export function MyComponent() { + return
Hello World
; +}; +``` + +### Vlastnosti + +Create a type named `{ComponentName}Props`. Use destructuring. Don't use `React.FC`. + +```tsx +type MyComponentProps = { + name: string; +}; + +export const MyComponent = ({ name }: MyComponentProps) =>
Hello {name}
; +``` + +### No single-variable prop spreading + +```tsx +// ❌ Bad +const MyComponent = (props: MyComponentProps) => ; + +// ✅ Good +const MyComponent = ({ prop1, prop2 }: MyComponentProps) => ; +``` + +## Správa stavu + +### Jotai atoms for global state + +```tsx +import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; +import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; + +export const myAtomState = createAtomState({ + key: 'myAtomState', + defaultValue: 'default value', +}); +``` + +* Prefer atoms over prop drilling +* Don't use `useRef` for state — use `useState` or atoms +* Use atom families and selectors for lists + +### Avoid unnecessary re-renders + +* Extract `useEffect` and data fetching into sibling sidecar components +* Prefer event handlers (`handleClick`, `handleChange`) over `useEffect` +* Don't use `React.memo()` — fix the root cause instead +* Limit `useCallback` / `useMemo` usage + +```tsx +// ❌ Bad — useEffect in the same component causes re-renders +export const Page = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return
{data}
; +}; + +// ✅ Good — extract into sibling +export const PageData = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return <>; +}; +export const Page = () => { + const [data] = useAtomState(dataState); + return
{data}
; +}; +``` + +## TypeScript + +* **`type` over `interface`** — more flexible, easier to compose +* **String literals over enums** — except for GraphQL codegen enums and internal library APIs +* **No `any`** — strict TypeScript enforced +* **No type imports** — use regular imports (enforced by Oxlint `typescript/consistent-type-imports`) +* **Use [Zod](https://github.com/colinhacks/zod)** for runtime validation of untyped objects + +## JavaScript + +```tsx +// Use nullish-coalescing (??) instead of || +const value = process.env.MY_VALUE ?? 'default'; + +// Use optional chaining +onClick?.(); +``` + +## Pojmenovávání + +* **Variables**: camelCase, descriptive (`email` not `value`, `fieldMetadata` not `fm`) +* **Constants**: SCREAMING_SNAKE_CASE +* **Types/Classes**: PascalCase +* **Files/directories**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`) +* **Event handlers**: `handleClick` (not `onClick` for the handler function) +* **Component props**: prefix with component name (`ButtonProps`) +* **Styled components**: prefix with `Styled` (`StyledTitle`) + +## Styling + +Use [Linaria](https://github.com/callstack/linaria) styled components. Use theme values — avoid hardcoded `px`, `rem`, or colors. + +```tsx +// ❌ Bad +const StyledButton = styled.button` + color: #333333; + font-size: 1rem; + margin-left: 4px; +`; + +// ✅ Good +const StyledButton = styled.button` + color: ${({ theme }) => theme.font.color.primary}; + font-size: ${({ theme }) => theme.font.size.md}; + margin-left: ${({ theme }) => theme.spacing(1)}; +`; +``` + +## Importy + +Use aliases instead of relative paths: + +```tsx +// ❌ Bad +import { Foo } from '../../../../../testing/decorators/Foo'; + +// ✅ Good +import { Foo } from '~/testing/decorators/Foo'; +import { Bar } from '@/modules/bar/components/Bar'; +``` + +## Folder Structure + +``` +front +└── modules/ # Feature modules +│ └── module1/ +│ ├── components/ +│ ├── constants/ +│ ├── contexts/ +│ ├── graphql/ (fragments, queries, mutations) +│ ├── hooks/ +│ ├── states/ (atoms, selectors) +│ ├── types/ +│ └── utils/ +└── pages/ # Route-level components +└── ui/ # Reusable UI components (display, input, feedback, ...) +``` + +* Modules can import from other modules, but `ui/` should stay dependency-free +* Use `internal/` subfolders for module-private code +* Components under 300 lines, services under 500 lines diff --git a/packages/twenty-docs/l/cs/developers/extend/api.mdx b/packages/twenty-docs/l/cs/developers/extend/api.mdx index d5f9cf89744..1a38fee1d08 100644 --- a/packages/twenty-docs/l/cs/developers/extend/api.mdx +++ b/packages/twenty-docs/l/cs/developers/extend/api.mdx @@ -1,147 +1,55 @@ --- title: API -description: Programově dotazujte a upravujte svá CRM data pomocí REST nebo GraphQL. +icon: plug +description: REST and GraphQL APIs generated from your workspace schema. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -Twenty bylo vytvořeno s ohledem na vývojáře a nabízí výkonná API, která se přizpůsobí vašemu vlastnímu datovému modelu. Nabízíme čtyři typy API, které splňují různé potřeby integrace. +## Schema-per-tenant APIs -## Přístup orientovaný na vývojáře +There is no static API reference for Twenty. Each workspace has its own schema — when you add a custom object (say `Invoice`), it immediately gets REST and GraphQL endpoints identical to built-in objects like `Company` or `Person`. The API is generated from the schema, so endpoints use your object and field names directly — no opaque IDs. -Twenty generuje API specificky pro váš datový model: +Your workspace-specific API documentation is available under **Settings → API & Webhooks** after creating an API key. It includes an interactive playground where you can execute real calls against your data. -* **Nejsou vyžadována dlouhá ID**: Používejte v koncových bodech přímo názvy objektů a polí. -* **Standardní a vlastní objekty jsou rovnocenně zpracovány**: Vaše vlastní objekty mají stejnou podporu API jako vestavěné. -* **Vyhrazené koncové body**: Každý objekt a každé pole má svůj vlastní koncový bod API. -* **Vlastní dokumentace**: Generována specificky pro datový model vašeho pracovního prostoru. +## Two APIs - -Vaše personalizovaná dokumentace k API je dostupná v **Nastavení → API & Webhooks** po vytvoření API klíče. Protože Twenty generuje API odpovídající vašemu vlastnímu datovému modelu, dokumentace je jedinečná pro váš pracovní prostor. - +**Core API** — `/rest/` and `/graphql/` -## Dva typy API +CRUD on records: People, Companies, Opportunities, your custom objects. Query, filter, traverse relations. -### Core API +**Metadata API** — `/rest/metadata/` and `/metadata/` -Přístupné na `/rest/` nebo `/graphql/` +Schema management: create/modify/delete objects, fields, and relations. This is how you programmatically change your data model. -Pracujte se svými skutečnými **záznamy** (daty): +Both are available as REST and GraphQL. GraphQL adds batch upserts and the ability to traverse relations in a single query. Same underlying data either way. -* Vytvářejte, čtěte, aktualizujte a mazejte osoby, společnosti, příležitosti atd. -* Dotazujte a filtrujte data -* Spravujte vztahy mezi záznamy. +## Base URLs -### Metadata API - -Přístupné na `/rest/metadata/` nebo `/metadata/` - -Spravujte svůj **pracovní prostor a datový model**: - -* Vytvářejte, upravujte nebo mazejte objekty a pole. -* Konfigurujte nastavení pracovního prostoru. -* Definujte vztahy mezi objekty - -## REST vs GraphQL - -Jak Core, tak Metadata API jsou k dispozici ve formátech REST a GraphQL: - -| Formát | Dostupné operace | -| ----------- | ---------------------------------------------------------------------- | -| **REST** | CRUD, hromadné operace, operace upsert | -| **GraphQL** | Stejné + **hromadné operace upsert**, dotazy na vztahy v jednom volání | - -Zvolte podle svých potřeb — oba formáty přistupují ke stejným datům. - -## Koncové body API - -| Prostředí | Základní URL | -| ------------------- | ------------------------- | -| **Cloud** | `https://api.twenty.com/` | -| **Vlastní hosting** | `https://{your-domain}/` | +| Prostředí | Základní URL | +| ----------- | ------------------------- | +| Cloud | `https://api.twenty.com/` | +| Self-Hosted | `https://{your-domain}/` | ## Ověření -Každý požadavek na API vyžaduje klíč API v hlavičce: - ``` Authorization: Bearer YOUR_API_KEY ``` -### Vytvořit API klíč - -1. Přejděte na **Nastavení → APIs & Webhooks** -2. Klikněte na **+ Vytvořit klíč** -3. Nakonfigurujte: - * **Název**: Popisný název pro klíč - * **Datum vypršení platnosti**: Kdy klíč vyprší -4. Klikněte na **Uložit** -5. **Zkopírujte ihned** — klíč se zobrazí pouze jednou +Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access. - -Váš klíč API poskytuje přístup k citlivým datům. Nesdílejte ho s nedůvěryhodnými službami. Pokud je kompromitován, okamžitě ho deaktivujte a vygenerujte nový. - +For OAuth-based access (external apps acting on behalf of users), see [OAuth](/l/cs/developers/extend/oauth). -### Přiřaďte roli klíči API +## Batch operations -Pro vyšší bezpečnost přiřaďte konkrétní roli, abyste omezili přístup: +Both REST and GraphQL support batching up to 60 records per request — create, update, or delete. GraphQL also supports batch upsert (create-or-update in one call) using plural names like `CreateCompanies`. -1. Přejděte na **Nastavení → Role** -2. Klikněte na roli, kterou chcete přiřadit -3. Otevřete záložku **Přiřazení** -4. V části **API Keys** klikněte na **+ Přiřadit ke klíči API** -5. Vyberte klíč API +## Rate limits -Klíč zdědí oprávnění této role. Podrobnosti viz [Oprávnění](/l/cs/user-guide/permissions-access/capabilities/permissions). - -### Spravovat API klíče - -**Znovu vygenerovat**: Nastavení → APIs & Webhooks → Klikněte na klíč → **Znovu vygenerovat** - -**Smazat**: Nastavení → APIs & Webhooks → Klikněte na klíč → **Smazat** - -## API Playground - -Testujte svá API přímo v prohlížeči pomocí našeho vestavěného playgroundu — k dispozici pro **REST** i **GraphQL**. - -### Přístup do Playgroundu - -1. Přejděte na **Nastavení → APIs & Webhooks** -2. Vytvořte klíč API (povinné) -3. Klikněte na **REST API** nebo **GraphQL API** pro otevření playgroundu - -### Co získáte - -* **Interaktivní dokumentace**: Generována pro váš specifický datový model -* **Živé testování**: Spouštějte reálná volání API vůči vašemu pracovnímu prostoru -* **Průzkumník schématu**: Procházejte dostupné objekty, pole a vztahy -* **Tvůrce požadavků**: Sestavujte dotazy s automatickým doplňováním - -Playground odráží vaše vlastní objekty a pole, takže dokumentace je pro váš pracovní prostor vždy přesná. - -## Hromadné operace - -REST i GraphQL podporují hromadné operace: - -* **Velikost dávky**: Až 60 záznamů na požadavek. -* **Operace**: Vytváření, aktualizace a mazání více záznamů - -**Funkce pouze pro GraphQL:** - -* **Hromadný upsert**: Vytvoření nebo aktualizace v jednom volání -* Používejte množná čísla názvů objektů (např. `CreateCompanies` místo `CreateCompany`) - -## Limity rychlosti - -Požadavky na API jsou omezovány, aby byla zajištěna stabilita platformy: - -| Limit | Hodnota | -| ------------------ | -------------------- | -| **Požadavky** | 100 volání za minutu | -| **Velikost dávky** | 60 záznamů na volání | - - -Pro maximalizaci propustnosti používejte hromadné operace — zpracujte až 60 záznamů v jediném volání API místo odesílání jednotlivých požadavků. - +| Limit | Hodnota | +| ---------- | -------------------- | +| Requests | 100 per minute | +| Batch size | 60 záznamů na volání | diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/building.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/building.mdx index 3ae74cb1234..26d8c0262e0 100644 --- a/packages/twenty-docs/l/cs/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/cs/developers/extend/apps/building.mdx @@ -1,2063 +1,104 @@ --- -title: Vytváření aplikací -description: Definujte objekty, logické funkce, frontendové komponenty a další pomocí Twenty SDK. +title: Architektura +description: How Twenty apps work — sandboxing, lifecycle, and the building blocks. +icon: sitemap --- - - Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí. - +Twenty apps are TypeScript packages that extend your workspace with custom objects, logic, UI components, and AI capabilities. They run on the Twenty platform with full sandboxing and permission controls. -Balíček `twenty-sdk` poskytuje typované stavební bloky pro vytváření vaší aplikace. Tato stránka pokrývá všechny typy entit a klienty API dostupné v SDK. +## How apps work -## Funkce DefineEntity +An app is a collection of **entities** declared using `defineEntity()` functions from the `twenty-sdk` package. The SDK detects these declarations via AST analysis at build time and produces a **manifest** — a complete description of what your app adds to a workspace. -SDK poskytuje funkce pro definování entit vaší aplikace. Abyste umožnili SDK detekovat vaše entity, musíte použít `export default defineEntity({...})`. Tyto funkce validují vaši konfiguraci v době sestavení a poskytují automatické doplňování v IDE a typovou bezpečnost. - - - **Uspořádání souborů je na vás.** - Detekce entit je založená na AST — SDK najde volání `export default defineEntity(...)` bez ohledu na to, kde se soubor nachází. Seskupování souborů podle typu (např. `logic-functions/`, `roles/`) je pouze konvence, nikoli požadavek. - - - - - -Role zapouzdřují oprávnění k objektům a akcím ve vašem pracovním prostoru. - -```ts restricted-company-role.ts -import { - defineRole, - PermissionFlag, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -export default defineRole({ - universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', - label: 'My new role', - description: 'A role that can be used in your workspace', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - fieldUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); ``` - - - - -Každá aplikace musí mít právě jedno volání `defineApplication`, které popisuje: - -* **Identita**: identifikátory, zobrazovaný název a popis. -* **Oprávnění**: jakou roli používají její funkce a frontendové komponenty. -* **(Volitelné) proměnné**: dvojice klíč–hodnota zpřístupněné vašim funkcím jako proměnné prostředí. -* **(Volitelné) předinstalační / postinstalační funkce**: logické funkce, které se spouštějí před nebo po instalaci. - -```ts src/application-config.ts -import { defineApplication } from 'twenty-sdk/define'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Poznámky: -* Pole `universalIdentifier` jsou deterministické identifikátory, které vlastníte. Vygenerujte je jednou a zachovejte je stabilní napříč synchronizacemi. -* `applicationVariables` se stanou proměnnými prostředí pro vaše funkce a frontendové komponenty (například `DEFAULT_RECIPIENT_NAME` je dostupné jako `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` musí odkazovat na roli definovanou pomocí `defineRole()` (viz výše). -* Předinstalační a postinstalační funkce jsou při sestavení manifestu detekovány automaticky — není třeba na ně odkazovat v `defineApplication()`. - -#### Metadata Marketplace - -Pokud plánujete [zveřejnit svou aplikaci](/l/cs/developers/extend/apps/publishing), tato volitelná pole určují, jak se vaše aplikace zobrazuje na Marketplace: - -| Pole | Popis | -| ------------------ | ------------------------------------------------------------------------------------------------------------- | -| `author` | Jméno autora nebo název společnosti | -| `category` | Kategorie aplikace pro filtrování na Marketplace | -| `logoUrl` | Cesta k logu vaší aplikace (např. `public/logo.png`) | -| `screenshots` | Pole cest ke snímkům obrazovky (např. `public/screenshot-1.png`) | -| `aboutDescription` | Delší popis v Markdownu pro kartu "O aplikaci". Pokud je vynecháno, tržiště použije `README.md` balíčku z npm | -| `websiteUrl` | Odkaz na váš web | -| `termsUrl` | Odkaz na Podmínky služby | -| `emailSupport` | E-mailová adresa podpory | -| `issueReportUrl` | Odkaz na nástroj pro sledování problémů | - -#### Role a oprávnění - -Pole `defaultRoleUniversalIdentifier` v `application-config.ts` určuje výchozí roli používanou logickými funkcemi a frontendovými komponentami vaší aplikace. Podrobnosti viz výše u `defineRole`. - -* Běhový token vložený jako `TWENTY_APP_ACCESS_TOKEN` je odvozen z této role. -* Typovaný klient bude omezen oprávněními udělenými této roli. -* Dodržujte princip nejmenších oprávnění: vytvořte vyhrazenou roli pouze s oprávněními, která vaše funkce potřebují. - -##### Výchozí role funkce - -Když vygenerujete novou aplikaci, CLI vytvoří výchozí soubor role: - -```ts src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk/define'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: true, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [], - fieldPermissions: [], - permissionFlags: [], -}); -``` - -Na `universalIdentifier` této role se v `application-config.ts` odkazuje jako na `defaultRoleUniversalIdentifier`: - -* **\*.role.ts** definuje, co daná role může dělat. -* **application-config.ts** ukazuje na tuto roli, aby vaše funkce zdědily její oprávnění. - -Poznámky: -* Začněte vygenerovanou rolí a postupně ji omezujte podle principu nejmenších oprávnění. -* Nahraďte `objectPermissions` a `fieldPermissions` objekty a poli, které vaše funkce skutečně potřebují. -* `permissionFlags` řídí přístup k schopnostem na úrovni platformy. Udržujte je co nejmenší. -* Podívejte se na funkční příklad: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - - - - -Vlastní objekty popisují jak schéma, tak chování záznamů ve vašem pracovním prostoru. K definování objektů s vestavěnou validací použijte `defineObject()`: - -```ts postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk/define'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Hlavní body: - -* Použijte `defineObject()` pro vestavěnou validaci a lepší podporu v IDE. -* Hodnota `universalIdentifier` musí být jedinečná a stabilní napříč nasazeními. -* Každé pole vyžaduje `name`, `type`, `label` a svůj vlastní stabilní `universalIdentifier`. -* Pole `fields` je volitelné — objekty můžete definovat i bez vlastních polí. -* Nové objekty můžete vygenerovat pomocí `yarn twenty add`, který vás provede pojmenováním, poli a vztahy. - - -**Základní pole jsou vytvořena automaticky.** Když definujete vlastní objekt, Twenty automaticky přidá standardní pole -jako `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` a `deletedAt`. -Nemusíte je definovat v poli `fields` — přidejte pouze svá vlastní pole. -Výchozí pole můžete přepsat definováním pole se stejným názvem v poli `fields`, -ale to se nedoporučuje. - - - - - -Pomocí `defineField()` přidejte pole k objektům, které nevlastníte — například ke standardním objektům Twenty (Person, Company atd.). nebo k objektům z jiných aplikací. Na rozdíl od inline polí v `defineObject()` vyžadují samostatná pole `objectUniversalIdentifier` k určení, který objekt rozšiřují: - -```ts src/fields/company-loyalty-tier.field.ts -import { defineField, FieldType } from 'twenty-sdk/define'; - -export default defineField({ - universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', - objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object - name: 'loyaltyTier', - type: FieldType.SELECT, - label: 'Loyalty Tier', - icon: 'IconStar', - options: [ - { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, - { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, - { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, - ], -}); -``` - -Hlavní body: -* `objectUniversalIdentifier` identifikuje cílový objekt. Pro standardní objekty použijte `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` exportovaný z `twenty-sdk`. -* Při definování polí inline v `defineObject()` `objectUniversalIdentifier` nepotřebujete — dědí se z nadřazeného objektu. -* `defineField()` je jediný způsob, jak přidat pole k objektům, které jste nevytvořili pomocí `defineObject()`. - - - - -Relace propojují objekty. Ve Twenty jsou relace vždy obousměrné — definujete obě strany a každá strana odkazuje na tu druhou. - -Existují dva typy relací: - -| Typ vztahu | Popis | Má cizí klíč? | -| ------------- | --------------------------------------------------------------------- | ---------------------- | -| `MANY_TO_ONE` | Mnoho záznamů tohoto objektu ukazuje na jeden záznam cílového objektu | Ano (`joinColumnName`) | -| `ONE_TO_MANY` | Jeden záznam tohoto objektu má mnoho záznamů cílového objektu | Ne (inverzní strana) | - -#### Jak fungují relace - -Každá relace vyžaduje dvě pole, která na sebe vzájemně odkazují: - -1. Strana MANY_TO_ONE — je na objektu, který drží cizí klíč -2. Strana ONE_TO_MANY — je na objektu, který vlastní kolekci - -Obě pole používají `FieldType.RELATION` a vzájemně se odkazují prostřednictvím `relationTargetFieldMetadataUniversalIdentifier`. - -#### Příklad: Pohlednice má mnoho příjemců - -Předpokládejme, že `PostCard` lze odeslat mnoha záznamům `PostCardRecipient`. Každý příjemce náleží přesně jedné pohlednici. - -**Krok 1: Definujte stranu ONE_TO_MANY na PostCard** (strana "one"): - -```ts src/fields/post-card-recipients-on-post-card.field.ts -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; -// Import from the other side -import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; - -export default defineField({ - universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCardRecipients', - label: 'Post Card Recipients', - icon: 'IconUsers', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); -``` - -**Krok 2: Definujte stranu MANY_TO_ONE na PostCardRecipient** (strana "many" — drží cizí klíč): - -```ts src/fields/post-card-on-post-card-recipient.field.ts -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; -// Import from the other side -import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; - -export default defineField({ - universalIdentifier: POST_CARD_FIELD_ID, - objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - icon: 'IconMail', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, -}); +your-app/ +├── src/ +│ ├── application-config.ts ← defineApplication (required, one per app) +│ ├── roles/ ← defineRole +│ ├── objects/ ← defineObject +│ ├── fields/ ← defineField +│ ├── logic-functions/ ← defineLogicFunction +│ ├── front-components/ ← defineFrontComponent +│ ├── skills/ ← defineSkill +│ ├── agents/ ← defineAgent +│ ├── views/ ← defineView +│ ├── navigation-menu-items/ ← defineNavigationMenuItem +│ └── page-layouts/ ← definePageLayout +├── public/ ← Static assets (images, icons) +└── package.json ``` -**Cyklické importy:** Obě relační pole odkazují na `universalIdentifier` toho druhého. Abyste předešli problémům s cyklickými importy, exportujte ID polí jako pojmenované konstanty z každého souboru a v druhém souboru je importujte. Build systém je vyřeší v době kompilace. + **File organization is up to you.** Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. The folder structure above is a convention, not a requirement. -#### Vazby na standardní objekty - -Chcete-li vytvořit relaci s vestavěným objektem Twenty (Person, Company atd.), použijte `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: - -```ts src/fields/person-on-self-hosting-user.field.ts -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; -import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; - -export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; -export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; - -export default defineField({ - universalIdentifier: PERSON_FIELD_ID, - objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - description: 'Person matching with the self hosting user', - isNullable: true, - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'personId', - }, -}); -``` - -#### Vlastnosti relačních polí - -| Vlastnost | Povinné | Popis | -| ------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------- | -| `type` | Ano | Musí být `FieldType.RELATION` | -| `relationTargetObjectMetadataUniversalIdentifier` | Ano | `universalIdentifier` cílového objektu | -| `relationTargetFieldMetadataUniversalIdentifier` | Ano | `universalIdentifier` odpovídajícího pole na cílovém objektu | -| `universalSettings.relationType` | Ano | `RelationType.MANY_TO_ONE` nebo `RelationType.ONE_TO_MANY` | -| `universalSettings.onDelete` | Pouze MANY_TO_ONE | Co se stane, když je smazán odkazovaný záznam: `CASCADE`, `SET_NULL`, `RESTRICT` nebo `NO_ACTION` | -| `universalSettings.joinColumnName` | Pouze MANY_TO_ONE | Název databázového sloupce pro cizí klíč (např. `postCardId`) | - -#### Vložená relační pole v defineObject - -Relační pole můžete také definovat přímo uvnitř `defineObject()`. V takovém případě vynechejte `objectUniversalIdentifier` — dědí se z nadřazeného objektu: - -```ts -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCardRecipient', - // ... - fields: [ - { - universalIdentifier: POST_CARD_FIELD_ID, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, - }, - // ... other fields - ], -}); -``` - - - -Každý soubor funkce používá `defineLogicFunction()` k exportu konfigurace s obslužnou funkcí (handlerem) a volitelnými spouštěči. - -```ts src/logic-functions/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - httpRouteTriggerSettings: { - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: true, - }, - /*databaseEventTriggerSettings: { - eventName: 'people.created', - },*/ - /*cronTriggerSettings: { - pattern: '0 0 1 1 *', - },*/ -}); -``` - -Dostupné typy spouštěčů: -* **httpRoute**: Zpřístupní vaši funkci na HTTP cestě a metodě **pod koncovým bodem `/s/`**: -> např. `path: '/post-card/create'` je volatelné na `https://your-twenty-server.com/s/post-card/create` -* **cron**: Spouští vaši funkci podle plánu pomocí výrazu CRON. -* **databaseEvent**: Spouští se při událostech životního cyklu objektů v pracovním prostoru. Když je operace události `updated`, lze konkrétní sledovaná pole určit v poli `updatedFields`. Pokud zůstane nedefinované nebo prázdné, spustí funkci jakákoli aktualizace. -> např. `person.updated`, `*.created`, `company.*` - - -Funkci můžete také spustit ručně pomocí CLI: - -```bash filename="Terminal" -yarn twenty exec -n create-new-post-card -p '{"key": "value"}' -``` - -```bash filename="Terminal" -yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -Logy můžete sledovat pomocí: - -```bash filename="Terminal" -yarn twenty logs -``` - - -#### Payload spouštěče trasy - -Když spouštěč typu route vyvolá vaši logickou funkci, ta obdrží objekt `RoutePayload`, který odpovídá -[AWS HTTP API v2 formátu](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). -Importujte typ `RoutePayload` z `twenty-sdk`: - -```ts -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; - -const handler = async (event: RoutePayload) => { - const { headers, queryStringParameters, pathParameters, body } = event; - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -Typ `RoutePayload` má následující strukturu: - - | Vlastnost | Typ | Popis | Příklad | - | ---------------------------- | ------------------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------- | - | `headers` | `Record\` | Záhlaví HTTP (pouze ta uvedená v `forwardedRequestHeaders`) | viz sekce níže | - | `queryStringParameters` | `Record\` | Parametry query stringu (více hodnot spojených čárkami) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | - | `pathParameters` | `Record\` | Parametry cesty extrahované ze vzoru trasy | `/users/:id`, `/users/123` -> `{ id: '123' }` | - | `body` | `object \| null` | Parsované tělo požadavku (JSON) | `{ id: 1 }` -> `{ id: 1 }` | - | `isBase64Encoded` | `boolean` | Zda je tělo kódováno base64 | | - | `requestContext.http.method` | `string` | Metoda HTTP (GET, POST, PUT, PATCH, DELETE) | | - | `requestContext.http.path` | `string` | Nezpracovaná cesta požadavku | | - - -#### forwardedRequestHeaders - -Ve výchozím nastavení se záhlaví HTTP z příchozích požadavků z bezpečnostních důvodů do vaší logické funkce **ne** předávají. -Chcete-li zpřístupnit konkrétní záhlaví, výslovně je uveďte v poli `forwardedRequestHeaders`: - -```ts -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - httpRouteTriggerSettings: { - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, -}); -``` - -Ve vašem handleru k přeposlaným záhlavím přistupujte takto: - -```ts -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - -Názvy záhlaví jsou normalizovány na malá písmena. Přistupujte k nim pomocí klíčů s malými písmeny (například `event.headers['content-type']`). - - -#### Zpřístupnění funkce jako nástroje - -Logické funkce lze zpřístupnit jako **nástroje** pro agenty AI a pracovní postupy. Když je funkce označena jako nástroj, stane se dohledatelnou funkcemi AI produktu Twenty a lze ji použít v automatizacích pracovních postupů. - -Chcete-li označit logickou funkci jako nástroj, nastavte `isTool: true`: - -```ts src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, -}); -``` - -Hlavní body: - -* Můžete kombinovat `isTool` se spouštěči — funkce může být zároveň nástrojem (volatelným agenty AI) a současně se spouštět událostmi. -* **`toolInputSchema`** (volitelné): Objekt JSON Schema, který popisuje parametry, jež vaše funkce přijímá. Schéma se určuje automaticky ze statické analýzy zdrojového kódu, ale můžete ho nastavit i explicitně: - -```ts -export default defineLogicFunction({ - ..., - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - - -**Napište kvalitní `description`.** Agenti AI se spoléhají na pole funkce `description` při rozhodování, kdy nástroj použít. Buďte konkrétní ohledně toho, co nástroj dělá a kdy se má volat. - - - - - -Postinstalační funkce je logická funkce, která se spustí automaticky, jakmile je instalace vaší aplikace v pracovním prostoru dokončena. Server ji provede **poté**, co byla synchronizována metadata aplikace a vygenerován klient SDK, takže je pracovní prostor plně připraven k použití a nové schéma je zavedeno. Mezi typické případy použití patří naplnění výchozími daty, vytvoření počátečních záznamů, konfigurace nastavení pracovního prostoru nebo zřizování prostředků ve službách třetích stran. - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - shouldRunSynchronously: false, - handler, -}); -``` - -Postinstalační funkci můžete také kdykoli spustit ručně pomocí CLI: - -```bash filename="Terminal" -yarn twenty exec --postInstall -``` - -Hlavní body: -* Postinstalační funkce používají `definePostInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Obslužná funkce obdrží `InstallPayload` s `{ previousVersion?: string; newVersion: string }` — `newVersion` je verze, která se instaluje, a `previousVersion` je verze, která byla nainstalována dříve (nebo `undefined` při čisté instalaci). Tyto hodnoty použijte k rozlišení čistých instalací od aktualizací a ke spuštění migrační logiky specifické pro verzi. -* **Kdy se hook spouští**: ve výchozím nastavení pouze při čistých instalacích. Předejte `shouldRunOnVersionUpgrade: true`, pokud chcete, aby se spouštěl i při aktualizaci aplikace z předchozí verze. Pokud je vynechán, příznak má výchozí hodnotu `false` a při aktualizacích se hook přeskočí. -* **Model provádění — ve výchozím nastavení asynchronní, synchronní volitelně**: příznak `shouldRunSynchronously` určuje *jak* se spouští post-install. - * `shouldRunSynchronously: false` *(výchozí)* — hook je **zařazen do fronty zpráv** s `retryLimit: 3` a běží asynchronně ve workeru. Odezva instalace se vrátí hned po zařazení úlohy do fronty, takže pomalá nebo chybující obslužná funkce neblokuje volajícího. Worker se pokusí o opakování až třikrát. **Použijte pro dlouho běžící úlohy** — plnění velkých datových sad, volání pomalých externích API, zřizování externích prostředků, cokoli, co by mohlo přesáhnout rozumné časové okno HTTP odezvy. - * `shouldRunSynchronously: true` — hook se provádí **inline během instalačního procesu** (stejný vykonavatel jako pre-install). Instalační požadavek blokuje, dokud obslužná funkce nedokončí, a pokud vyvolá výjimku, volající instalace obdrží `POST_INSTALL_ERROR`. Žádné automatické opakování. **Použijte pro rychlé úlohy, které se musí dokončit před odpovědí** — například vrácení validační chyby uživateli nebo rychlé nastavení, na kterém bude klient záviset ihned po návratu volání instalace. Mějte na paměti, že v době, kdy se spustí post-install, už byla migrace metadat aplikována, takže selhání v synchronním režimu změny schématu **ne**vrací zpět — pouze odhalí chybu. -* Ujistěte se, že vaše obslužná funkce je idempotentní. V asynchronním režimu se může fronta pokusit až třikrát; v obou režimech se může hook znovu spustit při aktualizacích, pokud je `shouldRunOnVersionUpgrade: true`. -* Proměnné prostředí `APPLICATION_ID`, `APP_ACCESS_TOKEN` a `API_URL` jsou dostupné uvnitř obslužné funkce (stejně jako u jakékoli jiné logické funkce), takže můžete volat Twenty API s aplikačním přístupovým tokenem omezeným na vaši aplikaci. -* Na jednu aplikaci je povolena pouze jedna postinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna. -* Atributy funkce `universalIdentifier`, `shouldRunOnVersionUpgrade` a `shouldRunSynchronously` jsou během buildu automaticky připojeny k manifestu aplikace do pole `postInstallLogicFunction` — není potřeba je uvádět v `defineApplication()`. -* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší úlohy nastavení, jako je naplnění daty. -* **Nespouští se v režimu dev**: když je aplikace registrována lokálně (pomocí `yarn twenty dev`), server zcela přeskočí instalační tok a synchronizuje soubory přímo prostřednictvím sledovače CLI — takže se post-install v režimu dev nikdy nespustí bez ohledu na `shouldRunSynchronously`. Použijte `yarn twenty exec --postInstall` k ručnímu spuštění nad běžícím pracovním prostorem. - - - - -Funkce pre-install je logická funkce, která se během instalace spouští automaticky, **před aplikováním migrace metadat pracovního prostoru**. Má stejný tvar payloadu jako post-install (`InstallPayload`), ale je zařazena dříve v instalačním toku, aby mohla připravit stav, na němž nadcházející migrace závisí — typické použití zahrnuje zálohování dat, ověření kompatibility s novým schématem nebo archivaci záznamů, které se chystají přeuspořádat nebo odstranit. - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -Předinstalační funkci můžete také kdykoli spustit ručně pomocí CLI: - -```bash filename="Terminal" -yarn twenty exec --preInstall -``` - -Hlavní body: -* Funkce pre-install používají `definePreInstallLogicFunction()` — stejné specializované nastavení jako u post-install, pouze připojené k jiné fázi životního cyklu. -* Obě obslužné funkce pre- i post-install přijímají stejný typ `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Importujte jej jednou a znovu použijte pro oba hooky. -* **Kdy se hook spouští**: umístěn těsně před migrací metadat pracovního prostoru (`synchronizeFromManifest`). Před spuštěním server provede čistě aditivní "zjednodušenou synchronizaci", která v metadatech pracovního prostoru zaregistruje pre-install funkci **nové** verze — ničeho dalšího se nedotkne — a poté ji spustí. Protože tato synchronizace je pouze aditivní, objekty, pole a data předchozí verze zůstávají při spuštění vaší obslužné funkce zachována: můžete bezpečně číst a zálohovat stav před migrací. -* **Model provádění**: pre-install se provádí **synchronně** a **blokuje instalaci**. Pokud obslužná funkce vyvolá výjimku, instalace se přeruší ještě před aplikováním jakýchkoli změn schématu — pracovní prostor zůstane na předchozí verzi v konzistentním stavu. Je to záměrné: pre-install je vaše poslední šance odmítnout rizikovou aktualizaci. -* Stejně jako u post-install je na jednu aplikaci povolena pouze jedna funkce pre-install. Během buildu je automaticky připojena k manifestu aplikace pod `preInstallLogicFunction`. -* **Nespouští se v režimu dev**: stejně jako u post-install — u lokálně registrovaných aplikací je instalační tok zcela přeskočen, takže se pre-install pod `yarn twenty dev` nikdy nespustí. Použijte `yarn twenty exec --preInstall` k ručnímu spuštění. - - - - -Oba hooky jsou součástí téhož instalačního toku a přijímají stejný `InstallPayload`. Rozdíl je v tom, **kdy** se spouštějí vzhledem k migraci metadat pracovního prostoru, a to určuje, jakých dat se mohou bezpečně dotýkat. +## Entity types + +| Entita | Účel | Dokumentace | +| ------------------------ | ----------------------------------------- | ------------------------------------------------------------ | +| **Application** | App identity, permissions, variables | [Data Model](/l/cs/developers/extend/apps/data-model) | +| **Role** | Permission sets for objects and fields | [Data Model](/l/cs/developers/extend/apps/data-model) | +| **Objekt** | Custom data tables with fields | [Data Model](/l/cs/developers/extend/apps/data-model) | +| **Pole** | Extend existing objects, define relations | [Data Model](/l/cs/developers/extend/apps/data-model) | +| **Logic Function** | Server-side TypeScript with triggers | [Logické funkce](/l/cs/developers/extend/apps/logic-functions) | +| **Front Component** | Sandboxed React UI in Twenty's page | [Front Components](/l/cs/developers/extend/apps/front-components) | +| **Skill** | Reusable AI agent instructions | [Skills & Agents](/l/cs/developers/extend/apps/skills-and-agents) | +| **Agent** | AI assistants with custom prompts | [Skills & Agents](/l/cs/developers/extend/apps/skills-and-agents) | +| **View** | Pre-configured record list views | [Layout](/l/cs/developers/extend/apps/layout) | +| **Navigation Menu Item** | Custom sidebar entries | [Layout](/l/cs/developers/extend/apps/layout) | +| **Page Layout** | Custom record page tabs and widgets | [Layout](/l/cs/developers/extend/apps/layout) | + +## Sandboxing + +* **Logic functions** run in isolated Node.js processes on the server. They only access data through the typed API client, scoped to the app's role permissions. +* **Front components** run in Web Workers using Remote DOM — sandboxed from the main page but rendering native DOM elements (not iframes). They communicate with Twenty via a message-passing host API. +* **Permissions** are enforced at the API level. The runtime token (`TWENTY_APP_ACCESS_TOKEN`) is derived from the role defined in `defineApplication()`. + +## App lifecycle ``` -┌─────────────────────────────────────────────────────────────┐ -│ install flow │ -│ │ -│ upload package → [pre-install] → metadata migration → │ -│ generate SDK → [post-install] │ -│ │ -│ old schema visible new schema visible │ -└─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────┐ +│ Development │ +│ npx create-twenty-app → yarn twenty dev (live sync) │ +├─────────────────────────────────────────────────────────┤ +│ Build & Deploy │ +│ yarn twenty build → yarn twenty deploy │ +├─────────────────────────────────────────────────────────┤ +│ Install flow │ +│ upload → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +├─────────────────────────────────────────────────────────┤ +│ Publish │ +│ npm publish → appears in Twenty marketplace │ +└─────────────────────────────────────────────────────────┘ ``` -Pre-install je vždy **synchronní** (blokuje instalaci a může ji přerušit). Post-install je **ve výchozím nastavení asynchronní** — zařazen do workeru s automatickými pokusy o opakování — ale může přejít na synchronní provádění pomocí `shouldRunSynchronously: true`. Viz accordion `definePostInstallLogicFunction` výše, kdy použít jednotlivé režimy. - -**Použijte `post-install` pro cokoli, co vyžaduje existenci nového schématu.** To je běžný případ: - -* Plnění výchozími daty (vytváření počátečních záznamů, výchozích pohledů, demo obsahu) vůči nově přidaným objektům a polím. -* Registrace webhooků u služeb třetích stran poté, co má aplikace své přihlašovací údaje. -* Volání vlastního API k dokončení nastavení, které závisí na synchronizovaných metadatech. -* Idempotentní logika "zajisti, že to existuje", která má při každé aktualizaci uvést stav do souladu — kombinujte s `shouldRunOnVersionUpgrade: true`. - -Příklad — po instalaci naplňte výchozí záznam `PostCard`: - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion }: InstallPayload): Promise => { - if (previousVersion) return; // fresh installs only - - const client = createClient(); - await client.postCard.create({ - data: { title: 'Welcome to Postcard', content: 'Your first card!' }, - }); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Seeds a welcome post card after install.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - handler, -}); -``` - -**Použijte `pre-install`, pokud by migrace jinak zničila nebo poškodila existující data.** Protože pre-install běží proti *předchozímu* schématu a jeho selhání vrací aktualizaci zpět, je to správné místo pro cokoli rizikového: - -* **Zálohování dat, která se chystají odstranit nebo přeuspořádat** — např. odstraňujete pole ve verzi v2 a potřebujete jeho hodnoty zkopírovat do jiného pole nebo je před spuštěním migrace exportovat do úložiště. -* **Archivace záznamů, které by nové omezení zneplatnilo** — např. pole se stává `NOT NULL` a je třeba nejprve smazat nebo opravit řádky s hodnotami null. -* **Ověření kompatibility a odmítnutí aktualizace, pokud nelze aktuální data čistě migrovat** — vyhoďte výjimku z obslužné funkce a instalace se ukončí bez provedených změn. Je to bezpečnější, než zjistit nekompatibilitu uprostřed migrace. -* **Přejmenování nebo změna klíčů dat** před změnou schématu, která by ztratila vazby. - -Příklad — archivujte záznamy před destruktivní migrací: - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { - // Only the 1.x → 2.x upgrade drops the legacy `notes` field. - if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { - return; - } - - const client = createClient(); - const legacyRecords = await client.postCard.findMany({ - where: { notes: { isNotNull: true } }, - }); - - if (legacyRecords.length === 0) return; - - // Copy legacy `notes` into the new `description` field before the migration - // drops the `notes` column. If this fails, the upgrade is aborted and the - // workspace stays on v1 with all data intact. - await Promise.all( - legacyRecords.map((record) => - client.postCard.update({ - where: { id: record.id }, - data: { description: record.notes }, - }), - ), - ); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Backs up legacy notes into description before the v2 migration.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -**Zlaté pravidlo:** - -| Chcete… | Použít | -| ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | -| Naplňte výchozí data, nakonfigurujte pracovní prostor, zaregistrujte externí prostředky | `post-install` | -| Spusťte dlouho běžící plnění nebo volání třetích stran, která by neměla blokovat odezvu instalace | `post-install` (výchozí — `shouldRunSynchronously: false`, s opakovanými pokusy workeru) | -| Spusťte rychlé nastavení, na které bude volající spoléhat ihned po návratu volání instalace | `post-install` s `shouldRunSynchronously: true` | -| Čtěte nebo zálohujte data, která by nadcházející migrace ztratila | `pre-install` | -| Odmítněte aktualizaci, která by poškodila existující data | `pre-install` (vyhoďte výjimku z obslužné funkce) | -| Spouštějte srovnání stavu při každé aktualizaci | `post-install` s `shouldRunOnVersionUpgrade: true` | -| Proveďte jednorázové nastavení pouze při první instalaci | `post-install` s `shouldRunOnVersionUpgrade: false` (výchozí) | - - -Pokud si nejste jisti, výchozí volbou je **post-install**. Po pre-install sáhněte pouze tehdy, když je samotná migrace destruktivní a potřebujete zachytit předchozí stav, než zmizí. - - - - - -Frontendové komponenty jsou React komponenty, které se vykreslují přímo v uživatelském rozhraní Twenty. Běží v **izolovaném Web Workeru** s využitím Remote DOM — váš kód je sandboxovaný, ale vykresluje se nativně na stránce, nikoli v iframu. - -#### Kde lze použít front komponenty - -Front komponenty se mohou vykreslovat na dvou místech v rámci Twenty: - -* **Postranní panel** — Ne-headless front komponenty se otevírají v pravém postranním panelu. Toto je výchozí chování, když je front komponenta vyvolána z menu příkazů. -* **Widgety (nástěnky a stránky záznamů)** — Front komponenty lze vkládat jako widgety do rozložení stránek. Při konfiguraci nástěnky nebo rozložení stránky záznamu mohou uživatelé přidat widget front komponenty. - -#### Základní příklad - -Nejrychlejší způsob, jak vidět frontendovou komponentu v akci, je zaregistrovat ji jako **příkaz**. Přidáním pole `command` s `isPinned: true` se zobrazí jako tlačítko rychlé akce v pravém horním rohu stránky — není potřeba žádné rozvržení stránky: - -```tsx src/front-components/hello-world.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; - -const HelloWorld = () => { - return ( -
-

Hello from my app!

-

This component renders inside Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', - name: 'hello-world', - description: 'A simple front component', - component: HelloWorld, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', - shortLabel: 'Hello', - label: 'Hello World', - icon: 'IconBolt', - isPinned: true, - availabilityType: 'GLOBAL', - }, -}); -``` - -Po synchronizaci pomocí `yarn twenty dev` (nebo po jednorázovém spuštění `yarn twenty dev --once`) se rychlá akce zobrazí v pravém horním rohu stránky: - -
- Tlačítko rychlé akce v pravém horním rohu -
- -Kliknutím na něj vykreslíte komponentu přímo ve stránce. - -{/* TODO: add screenshot of the rendered front component */} - -#### Konfigurační pole - -| Pole | Povinné | Popis | -| --------------------- | ------- | ------------------------------------------------------------------------------------ | -| `universalIdentifier` | Ano | Stabilní jedinečné ID pro tuto komponentu | -| `component` | Ano | Funkce komponenty React | -| `name` | Ne | Zobrazovaný název | -| `description` | Ne | Popis toho, co komponenta dělá | -| `isHeadless` | Ne | Nastavte na `true`, pokud komponenta nemá viditelné UI (viz níže) | -| `command` | Ne | Zaregistrujte komponentu jako příkaz (viz [možnosti příkazu](#command-options) níže) | - -#### Umístění frontendové komponenty na stránku - -Mimo příkazy můžete frontendovou komponentu vložit přímo na stránku záznamu přidáním jako widget v **rozvržení stránky**. Podrobnosti viz sekce [definePageLayout](#definepagelayout). - -#### Headless vs. ne-headless - -Front komponenty existují ve dvou režimech vykreslování řízených volbou `isHeadless`: - -**Ne-headless (výchozí)** — Komponenta vykreslí viditelné uživatelské rozhraní. Po vyvolání z menu příkazů se otevře v postranním panelu. Toto je výchozí chování, když je `isHeadless` `false` nebo když tato volba není uvedena. - -**Headless (`isHeadless: true`)** — Komponenta se neviditelně inicializuje na pozadí. Neotevírá postranní panel. Headless komponenty jsou určené pro akce, které provedou logiku a poté se odpojí — například spuštění asynchronního úkolu, navigaci na stránku nebo zobrazení potvrzovacího modálního okna. Přirozeně se hodí ke komponentám SDK Command popsaným níže. - -```tsx src/front-components/sync-tracker.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; -import { useEffect } from 'react'; - -const SyncTracker = () => { - const recordId = useRecordId(); - - useEffect(() => { - enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); - }, [recordId]); - - return null; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'sync-tracker', - description: 'Tracks record views silently', - isHeadless: true, - component: SyncTracker, -}); -``` - -Protože komponenta vrací `null`, Twenty přeskočí vykreslení kontejneru — v rozvržení se neobjeví žádné prázdné místo. Komponenta má však stále přístup ke všem hookům a API komunikace s hostitelem. - -#### Komponenty SDK Command - -Balíček `twenty-sdk` poskytuje čtyři pomocné komponenty Command navržené pro headless front komponenty. Každá komponenta při připojení provede akci, chyby zpracuje zobrazením oznámení ve snackbaru a po dokončení automaticky odpojí front komponentu. - -Importujte je z `twenty-sdk/command`: - -* **`Command`** — Spustí asynchronní callback přes prop `execute`. -* **`CommandLink`** — Naviguje na cestu v aplikaci. Props: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — Otevře potvrzovací modální okno. Pokud uživatel potvrdí, provede callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — Otevře konkrétní stránku postranního panelu. Props: `page`, `pageTitle`, `pageIcon`. - -Zde je kompletní příklad headless front komponenty, která pomocí `Command` spouští akci z menu příkazů: - -```tsx src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -A příklad s použitím `CommandModal` k vyžádání potvrzení před provedením: - -```tsx src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Přístup k běhovému kontextu - -Uvnitř komponenty použijte hooky SDK pro přístup k aktuálnímu uživateli, záznamu a instanci komponenty: - -```tsx src/front-components/record-info.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - useUserId, - useRecordId, - useFrontComponentId, -} from 'twenty-sdk/front-component'; - -const RecordInfo = () => { - const userId = useUserId(); - const recordId = useRecordId(); - const componentId = useFrontComponentId(); - - return ( -
-

User: {userId}

-

Record: {recordId ?? 'No record context'}

-

Component: {componentId}

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', - name: 'record-info', - component: RecordInfo, -}); -``` - -Dostupné hooky: - -| Hook | Vrací | Popis | -| --------------------------------------------- | -------------------- | ------------------------------------------------------------ | -| `useUserId()` | `string` nebo `null` | ID aktuálního uživatele | -| `useRecordId()` | `string` nebo `null` | ID aktuálního záznamu (pokud je umístěna na stránce záznamu) | -| `useFrontComponentId()` | `string` | ID této instance komponenty | -| `useFrontComponentExecutionContext(selector)` | různé | Přístup k úplnému kontextu běhu pomocí selektorové funkce | - -#### API komunikace s hostitelem - -Frontendové komponenty mohou pomocí funkcí z `twenty-sdk` vyvolávat navigaci, modály a oznámení: - -| Funkce | Popis | -| ----------------------------------------------- | ------------------------------ | -| `navigate(to, params?, queryParams?, options?)` | Přejít na stránku v aplikaci | -| `openSidePanelPage(params)` | Otevřít postranní panel | -| `closeSidePanel()` | Zavře postranní panel | -| `openCommandConfirmationModal(params)` | Zobrazit potvrzovací dialog | -| `enqueueSnackbar(params)` | Zobrazit oznámení typu toast | -| `unmountFrontComponent()` | Odmontovat komponentu | -| `updateProgress(progress)` | Aktualizovat indikátor průběhu | - -Zde je příklad, který používá hostitelské API k zobrazení snackbaru a zavření postranního panelu po dokončení akce: - -```tsx src/front-components/archive-record.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId } from 'twenty-sdk/front-component'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -#### Možnosti příkazu - -Přidání pole `command` do `defineFrontComponent` zaregistruje komponentu v příkazovém menu (Cmd+K). Pokud je `isPinned` nastaveno na `true`, zobrazí se také jako tlačítko rychlé akce v pravém horním rohu stránky. - -| Pole | Povinné | Popis | -| --------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `universalIdentifier` | Ano | Stabilní jedinečné ID pro příkaz | -| `label` | Ano | Plný popisek zobrazený v příkazovém menu (Cmd+K) | -| `shortLabel` | Ne | Kratší popisek zobrazený na připnutém tlačítku rychlé akce | -| `icon` | Ne | Název ikony zobrazený vedle popisku (např. `'IconBolt'`, `'IconSend'`) | -| `isPinned` | Ne | Pokud je `true`, zobrazí příkaz jako tlačítko rychlé akce v pravém horním rohu stránky | -| `availabilityType` | Ne | Určuje, kde se příkaz zobrazuje: `'GLOBAL'` (vždy dostupné), `'RECORD_SELECTION'` (pouze když jsou vybrány záznamy) nebo `'FALLBACK'` (zobrazeno, když neodpovídají žádné jiné příkazy) | -| `availabilityObjectUniversalIdentifier` | Ne | Omezí příkaz na stránky konkrétního typu objektu (např. pouze u záznamů Company) | -| `conditionalAvailabilityExpression` | Ne | Logický výraz pro dynamické řízení, zda je příkaz viditelný (viz níže) | - -#### Výrazy podmíněné dostupnosti - -Pole `conditionalAvailabilityExpression` vám umožní řídit viditelnost příkazu na základě aktuálního kontextu stránky. Pro sestavení výrazů importujte typované proměnné a operátory z `twenty-sdk`: - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - pageType, - numberOfSelectedRecords, - objectPermissions, - everyEquals, - isDefined, -} from 'twenty-sdk/front-component'; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'bulk-action', - component: BulkAction, - command: { - universalIdentifier: '...', - label: 'Bulk Update', - availabilityType: 'RECORD_SELECTION', - conditionalAvailabilityExpression: everyEquals( - objectPermissions, - 'canUpdateObjectRecords', - true, - ), - }, -}); -``` - -**Kontextové proměnné** — reprezentují aktuální stav stránky: - -| Proměnná | Typ | Popis | -| ------------------------------ | --------- | -------------------------------------------------------------------- | -| `pageType` | `string` | Aktuální typ stránky (např. `'RecordIndexPage'`, `'RecordShowPage'`) | -| `isInSidePanel` | `boolean` | Zda je komponenta vykreslena v postranním panelu | -| `numberOfSelectedRecords` | `number` | Počet aktuálně vybraných záznamů | -| `isSelectAll` | `boolean` | Zda je aktivní "vybrat vše" | -| `selectedRecords` | `array` | Vybrané objekty záznamů | -| `favoriteRecordIds` | `array` | ID oblíbených záznamů | -| `objectPermissions` | `object` | Oprávnění pro aktuální typ objektu | -| `targetObjectReadPermissions` | `object` | Oprávnění ke čtení pro cílový objekt | -| `targetObjectWritePermissions` | `object` | Oprávnění k zápisu pro cílový objekt | -| `featureFlags` | `object` | Aktivní příznaky funkcí | -| `objectMetadataItem` | `object` | Metadata aktuálního typu objektu | -| `hasAnySoftDeleteFilterOnView` | `boolean` | Zda má aktuální zobrazení filtr soft-delete | - -**Operátory** — kombinují proměnné do logických výrazů: - -| Operátor | Popis | -| ----------------------------------- | ------------------------------------------------------------------------------ | -| `isDefined(value)` | `true`, pokud hodnota není null/undefined | -| `isNonEmptyString(value)` | `true`, pokud je hodnota neprázdný řetězec | -| `includes(array, value)` | `true`, pokud pole obsahuje danou hodnotu | -| `includesEvery(array, prop, value)` | `true`, pokud vlastnost každé položky zahrnuje danou hodnotu | -| `every(array, prop)` | `true`, pokud je vlastnost u každé položky pravdivá (truthy) | -| `everyDefined(array, prop)` | `true`, pokud je vlastnost definována u každé položky | -| `everyEquals(array, prop, value)` | `true`, pokud se vlastnost rovná hodnotě u každé položky | -| `some(array, prop)` | `true`, pokud je vlastnost pravdivá (truthy) alespoň u jedné položky | -| `someDefined(array, prop)` | `true`, pokud je vlastnost definována alespoň u jedné položky | -| `someEquals(array, prop, value)` | `true`, pokud se vlastnost rovná hodnotě alespoň u jedné položky | -| `someNonEmptyString(array, prop)` | `true`, pokud má vlastnost alespoň u jedné položky hodnotu neprázdného řetězce | -| `none(array, prop)` | `true`, pokud je vlastnost u všech položek nepravdivá (falsy) | -| `noneDefined(array, prop)` | `true`, pokud je vlastnost u všech položek nedefinovaná | -| `noneEquals(array, prop, value)` | `true`, pokud se vlastnost nerovná hodnotě u žádné položky | - -#### Veřejné soubory - -Frontendové komponenty mohou přistupovat k souborům ze složky aplikace `public/` pomocí `getPublicAssetUrl`: - -```tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -const Logo = () => Logo; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'logo', - component: Logo, -}); -``` - -Podrobnosti viz [sekci veřejných souborů](#accessing-public-assets-with-getpublicasseturl). - -#### Styling - -Frontendové komponenty podporují více přístupů ke stylování. Můžete použít: - -* **Inline styly** — `style={{ color: 'red' }}` -* **Komponenty Twenty UI** — import z `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar a další) -* **Emotion** — CSS-in-JS s `@emotion/react` -* **Styled-components** — vzory `styled.div` -* **Tailwind CSS** — utilitní třídy -* **Jakákoli CSS-in-JS knihovna** kompatibilní s Reactem - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Button, Tag, Status } from 'twenty-sdk/ui'; - -const StyledWidget = () => { - return ( -
-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', - name: 'styled-widget', - component: StyledWidget, -}); -``` - -
- - - -Dovednosti definují znovupoužitelné pokyny a schopnosti, které mohou agenti AI používat ve vašem pracovním prostoru. K definování dovedností s vestavěnou validací použijte `defineSkill()`: - -```ts src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk/define'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -Hlavní body: -* `name` je jedinečný identifikátor dovednosti (doporučuje se kebab-case). -* `label` je uživatelsky čitelný název zobrazovaný v UI. -* `content` obsahuje pokyny dovednosti — je to text, který agent AI používá. -* `icon` (volitelné) nastavuje ikonu zobrazovanou v UI. -* `description` (volitelné) poskytuje doplňující kontext o účelu dovednosti. - - - - -Agenti jsou asistenti AI, kteří běží ve vašem pracovním prostoru. K vytvoření agentů s vlastním systémovým promptem použijte `defineAgent()`: - -```ts src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk/define'; - -export default defineAgent({ - universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'Helps the sales team draft outreach emails and research prospects', - icon: 'IconRobot', - prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', -}); -``` - -Hlavní body: -* `name` je jedinečný identifikátor agenta (doporučuje se kebab-case). -* `label` je zobrazovaný název v UI. -* `prompt` je systémový prompt, který definuje chování agenta. -* `description` (volitelné) poskytuje kontext o tom, co agent dělá. -* `icon` (volitelné) nastavuje ikonu zobrazovanou v UI. -* `modelId` (volitelné) přepíše výchozí model AI používaný agentem. - - - - -Zobrazení jsou uložené konfigurace toho, jak se zobrazují záznamy objektu — včetně toho, která pole jsou viditelná, jejich pořadí a jaké filtry či seskupení jsou použity. Pomocí `defineView()` můžete k aplikaci přidat předkonfigurovaná zobrazení: - -```ts src/views/example-view.ts -import { defineView, ViewKey } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; - -export default defineView({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'All example items', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - icon: 'IconList', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', - fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 200, - }, - ], -}); -``` - -Hlavní body: -* `objectUniversalIdentifier` určuje, na který objekt se toto zobrazení vztahuje. -* `key` určuje typ zobrazení (např. `ViewKey.INDEX` pro hlavní seznam). -* `fields` určuje, které sloupce se zobrazí a v jakém pořadí. Každé pole odkazuje na `fieldMetadataUniversalIdentifier`. -* Pro pokročilejší konfigurace můžete definovat také `filters`, `filterGroups`, `groups` a `fieldGroups`. -* `position` určuje pořadí, pokud pro stejný objekt existuje více zobrazení. - - - - -Položky navigační nabídky přidávají vlastní položky do postranního panelu pracovního prostoru. Použijte `defineNavigationMenuItem()` k odkazování na zobrazení, externí URL nebo objekty: - -```ts src/navigation-menu-items/example-navigation-menu-item.ts -import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; -import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; - -export default defineNavigationMenuItem({ - universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', - name: 'example-navigation-menu-item', - icon: 'IconList', - color: 'blue', - position: 0, - type: NavigationMenuItemType.VIEW, - viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, -}); -``` - -Hlavní body: -* `type` určuje, na co položka menu odkazuje: `NavigationMenuItemType.VIEW` pro uložené zobrazení nebo `NavigationMenuItemType.LINK` pro externí URL. -* Pro odkazy na zobrazení nastavte `viewUniversalIdentifier`. Pro externí odkazy nastavte `link`. -* `position` určuje pořadí v postranním panelu. -* `icon` a `color` (volitelné) upravují vzhled. - - - - -Rozvržení stránek vám umožní přizpůsobit vzhled stránky s detailem záznamu — které karty se zobrazí, jaké widgety jsou uvnitř každé karty a jak jsou uspořádány. Pomocí `definePageLayout()` můžete k aplikaci přidat vlastní rozvržení: - -```ts src/page-layouts/example-record-page-layout.ts -import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; - -export default definePageLayout({ - universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', - name: 'Example Record Page', - type: 'RECORD_PAGE', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - tabs: [ - { - universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', - title: 'Hello World', - position: 50, - icon: 'IconWorld', - layoutMode: PageLayoutTabLayoutMode.CANVAS, - widgets: [ - { - universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', - title: 'Hello World', - type: 'FRONT_COMPONENT', - configuration: { - configurationType: 'FRONT_COMPONENT', - frontComponentUniversalIdentifier: - HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, - }, - }, - ], - }, - ], -}); -``` - -Hlavní body: -* `type` je obvykle `'RECORD_PAGE'` pro úpravu detailního zobrazení konkrétního objektu. -* `objectUniversalIdentifier` určuje, na který objekt se toto rozvržení vztahuje. -* Každá `tab` definuje sekci stránky s `title`, `position` a `layoutMode` (`CANVAS` pro volné rozvržení). -* Každý `widget` uvnitř karty může vykreslit frontendovou komponentu, seznam relací nebo jiné vestavěné typy widgetů. -* `position` na kartách určuje jejich pořadí. Použijte vyšší hodnoty (např. 50) pro umístění vlastních karet za vestavěné. - - -
- -## Veřejné prostředky (složka `public/`) - -Složka `public/` v kořenu vaší aplikace obsahuje statické soubory — obrázky, ikony, písma a další prostředky, které vaše aplikace potřebuje za běhu. Tyto soubory jsou automaticky zahrnuty do buildů, synchronizovány během vývojového režimu a nahrávány na server. - -Soubory umístěné v `public/` jsou: - -* **Veřejně přístupné** — po synchronizaci na server jsou prostředky dostupné na veřejné URL. K přístupu k nim není potřeba žádná autentizace. -* **Dostupné ve frontendových komponentách** — použijte URL prostředků k zobrazení obrázků, ikon či jiných médií uvnitř komponent Reactu. -* **Dostupné v logických funkcích** — odkazujte na URL prostředků v e-mailech, odpovědích API či jiné serverové logice. -* **Používány pro metadata Marketplace** — pole `logoUrl` a `screenshots` v `defineApplication()` odkazují na soubory z této složky (např. `public/logo.png`). Tyto se zobrazují v Marketplace, když je vaše aplikace zveřejněna. -* **Automaticky synchronizované ve vývojovém režimu** — když v `public/` přidáte, aktualizujete nebo smažete soubor, je automaticky synchronizován na server. Není potřeba restart. -* **Zahrnuté do buildů** — `yarn twenty build` zabalí všechny veřejné prostředky do distribučního výstupu. - -### Přístup k veřejným prostředkům pomocí `getPublicAssetUrl` - -K získání plné URL souboru ve vaší složce `public/` použijte pomocnou funkci `getPublicAssetUrl` z `twenty-sdk`. Funguje jak v logických funkcích, tak ve frontendových komponentách. - -**V logické funkci:** - -```ts src/logic-functions/send-invoice.ts -import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; - -const handler = async (): Promise => { - const logoUrl = getPublicAssetUrl('logo.png'); - const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); - - // Fetch the file content (no auth required — public endpoint) - const response = await fetch(invoiceUrl); - const buffer = await response.arrayBuffer(); - - return { logoUrl, size: buffer.byteLength }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'a1b2c3d4-...', - name: 'send-invoice', - description: 'Sends an invoice with the app logo', - timeoutSeconds: 10, - handler, -}); -``` - -**Ve frontendové komponentě:** - -```tsx src/front-components/company-card.tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -export default defineFrontComponent(() => { - const logoUrl = getPublicAssetUrl('logo.png'); - - return App logo; -}); -``` - -Argument `path` je relativní ke složce `public/` vaší aplikace. Jak `getPublicAssetUrl('logo.png')`, tak `getPublicAssetUrl('public/logo.png')` se vyhodnotí na stejnou URL — předpona `public/` je, je-li přítomna, automaticky odstraněna. - -## Používání balíčků npm - -Ve své aplikaci můžete nainstalovat a používat libovolný balíček npm. Logické funkce i frontendové komponenty se bundlují pomocí [esbuild](https://esbuild.github.io/), který vloží všechny závislosti přímo do výstupu — za běhu nejsou potřeba žádné `node_modules`. - -### Instalace balíčku - -```bash filename="Terminal" -yarn add axios -``` - -Poté jej importujte ve svém kódu: - -```ts src/logic-functions/fetch-data.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import axios from 'axios'; - -const handler = async (): Promise => { - const { data } = await axios.get('https://api.example.com/data'); - - return { data }; -}; - -export default defineLogicFunction({ - universalIdentifier: '...', - name: 'fetch-data', - description: 'Fetches data from an external API', - timeoutSeconds: 10, - handler, -}); -``` - -Stejně to funguje i pro frontendové komponenty: - -```tsx src/front-components/chart.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { format } from 'date-fns'; - -const DateWidget = () => { - return

Today is {format(new Date(), 'MMMM do, yyyy')}

; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Jak funguje bundlování - -Krok sestavení používá esbuild k vytvoření jediného samostatného souboru pro každou logickou funkci a každou frontendovou komponentu. Všechny importované balíčky jsou vloženy přímo do bundlu. - -**Logické funkce** běží v prostředí Node.js. Vestavěné moduly Node (`fs`, `path`, `crypto`, `http` atd.) jsou k dispozici a není je třeba instalovat. - -**Frontendové komponenty** běží ve Web Workeru. Vestavěné moduly Node nejsou k dispozici — pouze prohlížečová API a balíčky npm, které fungují v prohlížečovém prostředí. - -V obou prostředích jsou jako předpřipravené moduly k dispozici `twenty-client-sdk/core` a `twenty-client-sdk/metadata` — nejsou součástí bundlu, ale server je za běhu načítá. - -## Generování entit pomocí `yarn twenty add` - -Místo ručního vytváření souborů entit můžete použít interaktivní generátor: - -```bash filename="Terminal" -yarn twenty add -``` - -Požádá vás o výběr typu entity a provede vás požadovanými poli. Vygeneruje soubor připravený k použití se stabilním `universalIdentifier` a správným voláním `defineEntity()`. - -Můžete také předat typ entity přímo a přeskočit první dotaz: - -```bash filename="Terminal" -yarn twenty add object -yarn twenty add logicFunction -yarn twenty add frontComponent -``` - -### Dostupné typy entit - -| Typ entity | Příkaz | Vygenerovaný soubor | -| ------------------------- | ------------------------------------ | ------------------------------------------------------- | -| Objekt | `yarn twenty add object` | `src/objects/\.ts` | -| Pole | `yarn twenty add field` | `src/fields/\.ts` | -| Logická funkce | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | -| Frontendová komponenta | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | -| Role | `yarn twenty add role` | `src/roles/\.ts` | -| Dovednost | `yarn twenty add skill` | `src/skills/\.ts` | -| Agent | `yarn twenty add agent` | `src/agents/\.ts` | -| Pohled | `yarn twenty add view` | `src/views/\.ts` | -| Položka navigační nabídky | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | -| Rozvržení stránky | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | - -### Co generátor vytváří - -Každý typ entity má vlastní šablonu. Například `yarn twenty add object` se zeptá na: - -1. **Název (jednotné číslo)** — např. `invoice` -2. **Název (množné číslo)** — např. `invoices` -3. **Štítek (jednotné číslo)** — automaticky doplněn z názvu (např. `Invoice`) -4. **Štítek (množné číslo)** — automaticky doplněn (např. `Invoices`) -5. **Vytvořit zobrazení a položku navigace?** — pokud odpovíte ano, generátor také vytvoří odpovídající zobrazení a odkaz v postranním panelu pro nový objekt. - -Ostatní typy entit mají jednodušší dotazy — většinou se ptají pouze na název. - -Typ entity `field` je podrobnější: ptá se na název pole, štítek, typ (ze seznamu všech dostupných typů polí jako `TEXT`, `NUMBER`, `SELECT`, `RELATION` atd.) a `universalIdentifier` cílového objektu. - -### Vlastní výstupní cesta - -Pomocí příznaku `--path` umístíte vygenerovaný soubor do vlastního umístění: - -```bash filename="Terminal" -yarn twenty add logicFunction --path src/custom-folder -``` - -## Typovaní klienti API (twenty-client-sdk) - -Balíček `twenty-client-sdk` poskytuje dva typované klienty GraphQL pro práci s Twenty API z vašich logických funkcí a frontendových komponent. - -| Klient | Importovat | Koncový bod | Generováno? | -| ------------------- | ---------------------------- | ---------------------------------------------------------------- | ------------------------------ | -| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — data pracovního prostoru (záznamy, objekty) | Ano, při vývoji/sestavení | -| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — konfigurace pracovního prostoru, nahrávání souborů | Ne, dodává se předem sestavený | - - - - -`CoreApiClient` je hlavní klient pro dotazování a mutace dat pracovního prostoru. Generuje se z vašeho schématu pracovního prostoru během `yarn twenty dev` nebo `yarn twenty build`, takže je plně typovaný tak, aby odpovídal vašim objektům a polím. - -```ts -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const client = new CoreApiClient(); - -// Query records -const { companies } = await client.query({ - companies: { - edges: { - node: { - id: true, - name: true, - domainName: { - primaryLinkLabel: true, - primaryLinkUrl: true, - }, - }, - }, - }, -}); - -// Create a record -const { createCompany } = await client.mutation({ - createCompany: { - __args: { - data: { - name: 'Acme Corp', - }, - }, - id: true, - name: true, - }, -}); -``` - -Klient používá syntaxi výběrové sady (selection-set): předáním `true` zahrnete pole, pro argumenty použijte `__args` a pro relace vnořujte objekty. Získáte plné automatické doplňování a kontrolu typů založené na schématu vašeho pracovního prostoru. - - -**CoreApiClient je generován při vývoji/sestavení.** Pokud jej použijete bez předchozího spuštění `yarn twenty dev` nebo `yarn twenty build`, vyvolá chybu. Generování probíhá automaticky — CLI prozkoumá GraphQL schéma vašeho pracovního prostoru a vygeneruje typovaného klienta pomocí `@genql/cli`. - - -#### Použití CoreSchema pro anotace typů - -`CoreSchema` poskytuje typy TypeScriptu odpovídající objektům vašeho pracovního prostoru — hodí se pro typování stavu komponent nebo parametrů funkcí: - -```ts -import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; -import { useState } from 'react'; - -const [company, setCompany] = useState< - Pick | undefined ->(undefined); - -const client = new CoreApiClient(); -const result = await client.query({ - company: { - __args: { filter: { position: { eq: 1 } } }, - id: true, - name: true, - }, -}); -setCompany(result.company); -``` - - - - -`MetadataApiClient` je součástí SDK již předem sestavený (není vyžadována žádná generace). Odesílá dotazy na endpoint `/metadata` pro konfiguraci pracovního prostoru, aplikace a nahrávání souborů. - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const metadataClient = new MetadataApiClient(); - -// List first 10 objects in the workspace -const { objects } = await metadataClient.query({ - objects: { - edges: { - node: { - id: true, - nameSingular: true, - namePlural: true, - labelSingular: true, - isCustom: true, - }, - }, - __args: { - filter: {}, - paging: { first: 10 }, - }, - }, -}); -``` - -#### Nahrávání souborů - -`MetadataApiClient` obsahuje metodu `uploadFile` pro připojování souborů k polím typu souboru: - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -| Parametr | Typ | Popis | -| ---------------------------------- | -------- | ------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | Surový obsah souboru | -| `filename` | `string` | Název souboru (používá se pro ukládání a zobrazení) | -| `contentType` | `string` | Typ MIME (pokud je vynechán, výchozí je `application/octet-stream`) | -| `fieldMetadataUniversalIdentifier` | `string` | `universalIdentifier` pole typu souboru ve vašem objektu | - -Hlavní body: -* Používá `universalIdentifier` pole (nikoli jeho ID specifické pro pracovní prostor), takže váš kód pro nahrávání funguje v jakémkoli pracovním prostoru, kde je vaše aplikace nainstalována. -* Vrácená hodnota `url` je podepsaná adresa URL, kterou můžete použít k přístupu k nahranému souboru. - - - - - - Když váš kód běží na Twenty (logické funkce nebo frontendové komponenty), platforma vloží přihlašovací údaje jako proměnné prostředí: - - * `TWENTY_API_URL` — Základní URL Twenty API - * `TWENTY_APP_ACCESS_TOKEN` — krátkodobý klíč s rozsahem omezeným na výchozí roli funkce vaší aplikace - - Není nutné je předávat klientům — čtou je automaticky z `process.env`. Oprávnění API klíče jsou určena rolí uvedenou v `defaultRoleUniversalIdentifier` ve vašem `application-config.ts`. - - -## Testování vaší aplikace - -SDK poskytuje programová rozhraní, která vám umožní z testovacího kódu aplikaci sestavit, nasadit, nainstalovat a odinstalovat. V kombinaci s [Vitest](https://vitest.dev/) a typovanými klienty API můžete psát integrační testy, které ověří, že vaše aplikace funguje end-to-end proti reálnému serveru Twenty. - -### Nastavení - -Vygenerovaná aplikace již obsahuje Vitest. Pokud to nastavujete ručně, nainstalujte závislosti: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Vytvořte `vitest.config.ts` v kořeni vaší aplikace: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Vytvořte soubor nastavení, který před spuštěním testů ověří dostupnost serveru: - -```ts src/__tests__/setup-test.ts -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { beforeAll } from 'vitest'; - -const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; -const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### Programová rozhraní SDK - -Subcesta `twenty-sdk/cli` exportuje funkce, které můžete volat přímo z testovacího kódu: - -| Funkce | Popis | -| -------------- | ----------------------------------------------------- | -| `appBuild` | Sestaví aplikaci a volitelně zabalí tarball | -| `appDeploy` | Nahraje tarball na server | -| `appInstall` | Nainstaluje aplikaci do aktivního pracovního prostoru | -| `appUninstall` | Odinstaluje aplikaci z aktivního pracovního prostoru | - -Každá funkce vrací objekt výsledku se `success: boolean` a buď `data`, nebo `error`. - -### Psání integračního testu - -Zde je kompletní příklad, který aplikaci sestaví, nasadí a nainstaluje a poté ověří, že se objeví v pracovním prostoru: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Spuštění testů - -Ujistěte se, že běží váš lokální server Twenty, a poté: - -```bash filename="Terminal" -yarn test -``` - -Nebo v režimu watch během vývoje: - -```bash filename="Terminal" -yarn test:watch -``` - -### Kontrola typů - -Kontrolu typů můžete spustit i na vaší aplikaci bez spuštění testů: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Spustí se `tsc --noEmit` a nahlásí se případné chyby typů. - -## Referenční dokumentace CLI - -Kromě `dev`, `build`, `add` a `typecheck` poskytuje CLI příkazy pro spouštění funkcí, zobrazení logů a správu instalací aplikací. - -### Spouštění funkcí (`yarn twenty exec`) - -Spusťte logickou funkci ručně bez vyvolání přes HTTP, cron nebo databázovou událost: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Zobrazení logů funkcí (`yarn twenty logs`) - -Streamujte výstupní logy běhu logických funkcí vaší aplikace: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - - -To je jiné než `yarn twenty server logs`, které zobrazují logy kontejneru Docker. `yarn twenty logs` zobrazuje logy spuštění funkcí vaší aplikace ze serveru Twenty. - - -### Odinstalace aplikace (`yarn twenty uninstall`) - -Odeberte svou aplikaci z aktivního pracovního prostoru: - -```bash filename="Terminal" -yarn twenty uninstall - -# Skip the confirmation prompt -yarn twenty uninstall --yes -``` - -## Správa vzdálených serverů - -**Remote** je server Twenty, ke kterému se vaše aplikace připojuje. Během nastavení jej generátor kostry automaticky vytvoří. Můžete kdykoli přidat další vzdálené servery nebo mezi nimi přepínat. - -```bash filename="Terminal" -# Add a new remote (opens a browser for OAuth login) -yarn twenty remote add - -# Connect to a local Twenty server (auto-detects port 2020 or 3000) -yarn twenty remote add --local - -# Add a remote non-interactively (useful for CI) -yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote - -# List all configured remotes -yarn twenty remote list - -# Switch the active remote -yarn twenty remote switch -``` - -Vaše přihlašovací údaje jsou uloženy v `~/.twenty/config.json`. - -## CI s GitHub Actions - -Generátor kostry vytvoří připravený k použití workflow GitHub Actions v `.github/workflows/ci.yml`. Automaticky spouští integrační testy při každém pushi do `main` a u pull requestů. - -Workflow: - -1. Načte váš kód (checkout). -2. Spustí dočasný server Twenty pomocí akce `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` -3. Nainstaluje závislosti pomocí `yarn install --immutable` -4. Spustí `yarn test` s proměnnými `TWENTY_API_URL` a `TWENTY_API_KEY` vloženými z výstupů akce - -```yaml .github/workflows/ci.yml -name: CI - -on: - push: - branches: - - main - pull_request: {} - -env: - TWENTY_VERSION: latest - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Spawn Twenty instance - id: twenty - uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main - with: - twenty-version: ${{ env.TWENTY_VERSION }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Enable Corepack - run: corepack enable - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - - name: Install dependencies - run: yarn install --immutable - - - name: Run integration tests - run: yarn test - env: - TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} - TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} -``` - -Není potřeba konfigurovat žádné secrets — akce `spawn-twenty-docker-image` spustí efemérní server Twenty přímo v runneru a vypíše podrobnosti připojení. Secret `GITHUB_TOKEN` je poskytován GitHubem automaticky. - -Chcete-li připnout konkrétní verzi Twenty místo `latest`, změňte proměnnou prostředí `TWENTY_VERSION` na začátku workflow. +* **`yarn twenty dev`** — watches your source files and live-syncs changes to a connected Twenty server. The typed API client is regenerated automatically when the schema changes. +* **`yarn twenty build`** — compiles TypeScript, bundles logic functions and front components with esbuild, and produces a manifest. +* **Pre/post-install hooks** — optional logic functions that run during installation. See [Logic Functions](/l/cs/developers/extend/apps/logic-functions) for details. + +## Další kroky + + + + Define objects, fields, roles, and relations. + + + Server-side functions with HTTP, cron, and event triggers. + + + Sandboxed React components inside Twenty's UI. + + + Views, navigation items, and record page layouts. + + + AI skills and agents with custom prompts. + + + CLI commands, testing, assets, remotes, and CI. + + + Deploy to a server or publish to the marketplace. + + diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/cli-and-testing.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/cli-and-testing.mdx new file mode 100644 index 00000000000..14e1a83cc1a --- /dev/null +++ b/packages/twenty-docs/l/cs/developers/extend/apps/cli-and-testing.mdx @@ -0,0 +1,434 @@ +--- +title: CLI & Testing +description: CLI commands, testing setup, public assets, npm packages, remotes, and CI configuration. +icon: terminal +--- + +## Veřejné prostředky (složka `public/`) + +Složka `public/` v kořenu vaší aplikace obsahuje statické soubory — obrázky, ikony, písma a další prostředky, které vaše aplikace potřebuje za běhu. Tyto soubory jsou automaticky zahrnuty do buildů, synchronizovány během vývojového režimu a nahrávány na server. + +Soubory umístěné v `public/` jsou: + +* **Veřejně přístupné** — po synchronizaci na server jsou prostředky dostupné na veřejné URL. K přístupu k nim není potřeba žádná autentizace. +* **Dostupné ve frontendových komponentách** — použijte URL prostředků k zobrazení obrázků, ikon či jiných médií uvnitř komponent Reactu. +* **Dostupné v logických funkcích** — odkazujte na URL prostředků v e-mailech, odpovědích API či jiné serverové logice. +* **Používány pro metadata Marketplace** — pole `logoUrl` a `screenshots` v `defineApplication()` odkazují na soubory z této složky (např. `public/logo.png`). Tyto se zobrazují v Marketplace, když je vaše aplikace zveřejněna. +* **Automaticky synchronizované ve vývojovém režimu** — když v `public/` přidáte, aktualizujete nebo smažete soubor, je automaticky synchronizován na server. Není potřeba restart. +* **Zahrnuté do buildů** — `yarn twenty build` zabalí všechny veřejné prostředky do distribučního výstupu. + +### Přístup k veřejným prostředkům pomocí `getPublicAssetUrl` + +K získání plné URL souboru ve vaší složce `public/` použijte pomocnou funkci `getPublicAssetUrl` z `twenty-sdk`. Funguje jak v logických funkcích, tak ve frontendových komponentách. + +**V logické funkci:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; + +const handler = async (): Promise => { + const logoUrl = getPublicAssetUrl('logo.png'); + const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); + + // Fetch the file content (no auth required — public endpoint) + const response = await fetch(invoiceUrl); + const buffer = await response.arrayBuffer(); + + return { logoUrl, size: buffer.byteLength }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'a1b2c3d4-...', + name: 'send-invoice', + description: 'Sends an invoice with the app logo', + timeoutSeconds: 10, + handler, +}); +``` + +**Ve frontendové komponentě:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +Argument `path` je relativní ke složce `public/` vaší aplikace. Jak `getPublicAssetUrl('logo.png')`, tak `getPublicAssetUrl('public/logo.png')` se vyhodnotí na stejnou URL — předpona `public/` je, je-li přítomna, automaticky odstraněna. + +## Používání balíčků npm + +Ve své aplikaci můžete nainstalovat a používat libovolný balíček npm. Logické funkce i frontendové komponenty se bundlují pomocí [esbuild](https://esbuild.github.io/), který vloží všechny závislosti přímo do výstupu — za běhu nejsou potřeba žádné `node_modules`. + +### Instalace balíčku + +```bash filename="Terminal" +yarn add axios +``` + +Poté jej importujte ve svém kódu: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +Stejně to funguje i pro frontendové komponenty: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### Jak funguje bundlování + +Krok sestavení používá esbuild k vytvoření jediného samostatného souboru pro každou logickou funkci a každou frontendovou komponentu. Všechny importované balíčky jsou vloženy přímo do bundlu. + +**Logické funkce** běží v prostředí Node.js. Vestavěné moduly Node (`fs`, `path`, `crypto`, `http` atd.) jsou k dispozici a není je třeba instalovat. + +**Frontendové komponenty** běží ve Web Workeru. Vestavěné moduly Node nejsou k dispozici — pouze prohlížečová API a balíčky npm, které fungují v prohlížečovém prostředí. + +V obou prostředích jsou jako předpřipravené moduly k dispozici `twenty-client-sdk/core` a `twenty-client-sdk/metadata` — nejsou součástí bundlu, ale server je za běhu načítá. + +## Testování vaší aplikace + +SDK poskytuje programová rozhraní, která vám umožní z testovacího kódu aplikaci sestavit, nasadit, nainstalovat a odinstalovat. V kombinaci s [Vitest](https://vitest.dev/) a typovanými klienty API můžete psát integrační testy, které ověří, že vaše aplikace funguje end-to-end proti reálnému serveru Twenty. + +### Nastavení + +Vygenerovaná aplikace již obsahuje Vitest. Pokud to nastavujete ručně, nainstalujte závislosti: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Vytvořte `vitest.config.ts` v kořeni vaší aplikace: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Vytvořte soubor nastavení, který před spuštěním testů ověří dostupnost serveru: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### Programová rozhraní SDK + +Subcesta `twenty-sdk/cli` exportuje funkce, které můžete volat přímo z testovacího kódu: + +| Funkce | Popis | +| -------------- | ----------------------------------------------------- | +| `appBuild` | Sestaví aplikaci a volitelně zabalí tarball | +| `appDeploy` | Nahraje tarball na server | +| `appInstall` | Nainstaluje aplikaci do aktivního pracovního prostoru | +| `appUninstall` | Odinstaluje aplikaci z aktivního pracovního prostoru | + +Každá funkce vrací objekt výsledku se `success: boolean` a buď `data`, nebo `error`. + +### Psání integračního testu + +Zde je kompletní příklad, který aplikaci sestaví, nasadí a nainstaluje a poté ověří, že se objeví v pracovním prostoru: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Spuštění testů + +Ujistěte se, že běží váš lokální server Twenty, a poté: + +```bash filename="Terminal" +yarn test +``` + +Nebo v režimu watch během vývoje: + +```bash filename="Terminal" +yarn test:watch +``` + +### Kontrola typů + +Kontrolu typů můžete spustit i na vaší aplikaci bez spuštění testů: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +Spustí se `tsc --noEmit` a nahlásí se případné chyby typů. + +## Referenční dokumentace CLI + +Kromě `dev`, `build`, `add` a `typecheck` poskytuje CLI příkazy pro spouštění funkcí, zobrazení logů a správu instalací aplikací. + +### Spouštění funkcí (`yarn twenty exec`) + +Spusťte logickou funkci ručně bez vyvolání přes HTTP, cron nebo databázovou událost: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute the post-install function +yarn twenty exec --postInstall +``` + +### Zobrazení logů funkcí (`yarn twenty logs`) + +Streamujte výstupní logy běhu logických funkcí vaší aplikace: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +To je jiné než `yarn twenty server logs`, které zobrazují logy kontejneru Docker. `yarn twenty logs` zobrazuje logy spuštění funkcí vaší aplikace ze serveru Twenty. + + +### Odinstalace aplikace (`yarn twenty uninstall`) + +Odeberte svou aplikaci z aktivního pracovního prostoru: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` + +## Správa vzdálených serverů + +**Remote** je server Twenty, ke kterému se vaše aplikace připojuje. Během nastavení jej generátor kostry automaticky vytvoří. Můžete kdykoli přidat další vzdálené servery nebo mezi nimi přepínat. + +```bash filename="Terminal" +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add + +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local + +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote + +# List all configured remotes +yarn twenty remote list + +# Switch the active remote +yarn twenty remote switch +``` + +Vaše přihlašovací údaje jsou uloženy v `~/.twenty/config.json`. + +## CI s GitHub Actions + +Generátor kostry vytvoří připravený k použití workflow GitHub Actions v `.github/workflows/ci.yml`. Automaticky spouští integrační testy při každém pushi do `main` a u pull requestů. + +Workflow: + +1. Načte váš kód (checkout). +2. Spustí dočasný server Twenty pomocí akce `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` +3. Nainstaluje závislosti pomocí `yarn install --immutable` +4. Spustí `yarn test` s proměnnými `TWENTY_API_URL` a `TWENTY_API_KEY` vloženými z výstupů akce + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +Není potřeba konfigurovat žádné secrets — akce `spawn-twenty-docker-image` spustí efemérní server Twenty přímo v runneru a vypíše podrobnosti připojení. Secret `GITHUB_TOKEN` je poskytován GitHubem automaticky. + +Chcete-li připnout konkrétní verzi Twenty místo `latest`, změňte proměnnou prostředí `TWENTY_VERSION` na začátku workflow. diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/data-model.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/data-model.mdx new file mode 100644 index 00000000000..b26cebf4445 --- /dev/null +++ b/packages/twenty-docs/l/cs/developers/extend/apps/data-model.mdx @@ -0,0 +1,494 @@ +--- +title: Datový model +description: Define objects, fields, roles, and application metadata with the Twenty SDK. +icon: database +--- + +The `twenty-sdk` package provides `defineEntity` functions to declare your app's data model. Abyste umožnili SDK detekovat vaše entity, musíte použít `export default defineEntity({...})`. Tyto funkce validují vaši konfiguraci v době sestavení a poskytují automatické doplňování v IDE a typovou bezpečnost. + + + **Uspořádání souborů je na vás.** + Detekce entit je založená na AST — SDK najde volání `export default defineEntity(...)` bez ohledu na to, kde se soubor nachází. Seskupování souborů podle typu (např. `logic-functions/`, `roles/`) je pouze konvence, nikoli požadavek. + + + + + +Role zapouzdřují oprávnění k objektům a akcím ve vašem pracovním prostoru. + +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export default defineRole({ + universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', + label: 'My new role', + description: 'A role that can be used in your workspace', + canReadAllObjectRecords: false, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + canReadObjectRecords: true, + canUpdateObjectRecords: true, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + }, + ], + fieldPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + fieldUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, + canReadFieldValue: false, + canUpdateFieldValue: false, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Každá aplikace musí mít právě jedno volání `defineApplication`, které popisuje: + +* **Identita**: identifikátory, zobrazovaný název a popis. +* **Oprávnění**: jakou roli používají její funkce a frontendové komponenty. +* **(Volitelné) proměnné**: dvojice klíč–hodnota zpřístupněné vašim funkcím jako proměnné prostředí. +* **(Volitelné) předinstalační / postinstalační funkce**: logické funkce, které se spouštějí před nebo po instalaci. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk/define'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +Poznámky: +* Pole `universalIdentifier` jsou deterministické identifikátory, které vlastníte. Vygenerujte je jednou a zachovejte je stabilní napříč synchronizacemi. +* `applicationVariables` se stanou proměnnými prostředí pro vaše funkce a frontendové komponenty (například `DEFAULT_RECIPIENT_NAME` je dostupné jako `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` musí odkazovat na roli definovanou pomocí `defineRole()` (viz výše). +* Předinstalační a postinstalační funkce jsou při sestavení manifestu detekovány automaticky — není třeba na ně odkazovat v `defineApplication()`. + +#### Metadata tržiště + +Pokud plánujete [zveřejnit svou aplikaci](/l/cs/developers/extend/apps/publishing), tato volitelná pole určují, jak se vaše aplikace zobrazuje v tržišti: + +| Pole | Popis | +| ------------------ | ------------------------------------------------------------------------------------------------------------- | +| `author` | Jméno autora nebo název společnosti | +| `category` | Kategorie aplikace pro filtrování v tržišti | +| `logoUrl` | Cesta k logu vaší aplikace (např. `public/logo.png`) | +| `screenshots` | Pole cest ke snímkům obrazovky (např. `public/screenshot-1.png`) | +| `aboutDescription` | Delší popis v Markdownu pro kartu "O aplikaci". Pokud je vynecháno, tržiště použije `README.md` balíčku z npm | +| `websiteUrl` | Odkaz na váš web | +| `termsUrl` | Odkaz na podmínky služby | +| `emailSupport` | E-mailová adresa podpory | +| `issueReportUrl` | Odkaz na nástroj pro sledování problémů | + +#### Role a oprávnění + +Pole `defaultRoleUniversalIdentifier` v `application-config.ts` určuje výchozí roli používanou logickými funkcemi a frontendovými komponentami vaší aplikace. Podrobnosti viz výše u `defineRole`. + +* Běhový token vložený jako `TWENTY_APP_ACCESS_TOKEN` je odvozen z této role. +* Typovaný klient bude omezen oprávněními udělenými této roli. +* Dodržujte princip nejmenších oprávnění: vytvořte vyhrazenou roli pouze s oprávněními, která vaše funkce potřebují. + +##### Výchozí role funkce + +Když vygenerujete novou aplikaci, CLI vytvoří výchozí soubor role: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk/define'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + label: 'Default function role', + description: 'Default role for function Twenty client', + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [], + fieldPermissions: [], + permissionFlags: [], +}); +``` + +Na `universalIdentifier` této role se v `application-config.ts` odkazuje jako na `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** definuje, co daná role může dělat. +* **application-config.ts** ukazuje na tuto roli, aby vaše funkce zdědily její oprávnění. + +Poznámky: +* Začněte vygenerovanou rolí a postupně ji omezujte podle principu nejmenších oprávnění. +* Nahraďte `objectPermissions` a `fieldPermissions` objekty a poli, které vaše funkce skutečně potřebují. +* `permissionFlags` řídí přístup k schopnostem na úrovni platformy. Udržujte je co nejmenší. +* Podívejte se na funkční příklad: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + + +Vlastní objekty popisují jak schéma, tak chování záznamů ve vašem pracovním prostoru. K definování objektů s vestavěnou validací použijte `defineObject()`: + +```ts postCard.object.ts +import { defineObject, FieldType } from 'twenty-sdk/define'; + +enum PostCardStatus { + DRAFT = 'DRAFT', + SENT = 'SENT', + DELIVERED = 'DELIVERED', + RETURNED = 'RETURNED', +} + +export default defineObject({ + universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', + nameSingular: 'postCard', + namePlural: 'postCards', + labelSingular: 'Post Card', + labelPlural: 'Post Cards', + description: 'A post card object', + icon: 'IconMail', + fields: [ + { + universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', + name: 'content', + type: FieldType.TEXT, + label: 'Content', + description: "Postcard's content", + icon: 'IconAbc', + }, + { + universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', + name: 'recipientName', + type: FieldType.FULL_NAME, + label: 'Recipient name', + icon: 'IconUser', + }, + { + universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', + name: 'recipientAddress', + type: FieldType.ADDRESS, + label: 'Recipient address', + icon: 'IconHome', + }, + { + universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', + name: 'status', + type: FieldType.SELECT, + label: 'Status', + icon: 'IconSend', + defaultValue: `'${PostCardStatus.DRAFT}'`, + options: [ + { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, + { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, + { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, + { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, + ], + }, + { + universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', + name: 'deliveredAt', + type: FieldType.DATE_TIME, + label: 'Delivered at', + icon: 'IconCheck', + isNullable: true, + defaultValue: null, + }, + ], +}); +``` + +Hlavní body: + +* Použijte `defineObject()` pro vestavěnou validaci a lepší podporu v IDE. +* Hodnota `universalIdentifier` musí být jedinečná a stabilní napříč nasazeními. +* Každé pole vyžaduje `name`, `type`, `label` a svůj vlastní stabilní `universalIdentifier`. +* Pole `fields` je volitelné — objekty můžete definovat i bez vlastních polí. +* Nové objekty můžete vygenerovat pomocí `yarn twenty add`, který vás provede pojmenováním, poli a vztahy. + + +**Základní pole jsou vytvořena automaticky.** Když definujete vlastní objekt, Twenty automaticky přidá standardní pole +jako `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` a `deletedAt`. +Nemusíte je definovat v poli `fields` — přidejte pouze svá vlastní pole. +Výchozí pole můžete přepsat definováním pole se stejným názvem v poli `fields`, +ale to se nedoporučuje. + + + + + +Pomocí `defineField()` přidejte pole k objektům, které nevlastníte — například ke standardním objektům Twenty (Person, Company atd.). nebo k objektům z jiných aplikací. Na rozdíl od inline polí v `defineObject()` vyžadují samostatná pole `objectUniversalIdentifier` k určení, který objekt rozšiřují: + +```ts src/fields/company-loyalty-tier.field.ts +import { defineField, FieldType } from 'twenty-sdk/define'; + +export default defineField({ + universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', + objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object + name: 'loyaltyTier', + type: FieldType.SELECT, + label: 'Loyalty Tier', + icon: 'IconStar', + options: [ + { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, + { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, + { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, + ], +}); +``` + +Hlavní body: +* `objectUniversalIdentifier` identifikuje cílový objekt. Pro standardní objekty použijte `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` exportovaný z `twenty-sdk`. +* Při definování polí inline v `defineObject()` `objectUniversalIdentifier` nepotřebujete — dědí se z nadřazeného objektu. +* `defineField()` je jediný způsob, jak přidat pole k objektům, které jste nevytvořili pomocí `defineObject()`. + + + + +Relace propojují objekty. Ve Twenty jsou relace vždy obousměrné — definujete obě strany a každá strana odkazuje na tu druhou. + +Existují dva typy relací: + +| Typ vztahu | Popis | Má cizí klíč? | +| ------------- | --------------------------------------------------------------------- | ---------------------- | +| `MANY_TO_ONE` | Mnoho záznamů tohoto objektu ukazuje na jeden záznam cílového objektu | Ano (`joinColumnName`) | +| `ONE_TO_MANY` | Jeden záznam tohoto objektu má mnoho záznamů cílového objektu | Ne (inverzní strana) | + +#### Jak fungují relace + +Každá relace vyžaduje dvě pole, která na sebe vzájemně odkazují: + +1. Strana MANY_TO_ONE — je na objektu, který drží cizí klíč +2. Strana ONE_TO_MANY — je na objektu, který vlastní kolekci + +Obě pole používají `FieldType.RELATION` a vzájemně se odkazují prostřednictvím `relationTargetFieldMetadataUniversalIdentifier`. + +#### Příklad: Pohlednice má mnoho příjemců + +Předpokládejme, že `PostCard` lze odeslat mnoha záznamům `PostCardRecipient`. Každý příjemce náleží přesně jedné pohlednici. + +**Krok 1: Definujte stranu ONE_TO_MANY na PostCard** (strana "one"): + +```ts src/fields/post-card-recipients-on-post-card.field.ts +import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; +// Import from the other side +import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; + +export default defineField({ + universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCardRecipients', + label: 'Post Card Recipients', + icon: 'IconUsers', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); +``` + +**Krok 2: Definujte stranu MANY_TO_ONE na PostCardRecipient** (strana "many" — drží cizí klíč): + +```ts src/fields/post-card-on-post-card-recipient.field.ts +import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; +// Import from the other side +import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; + +export default defineField({ + universalIdentifier: POST_CARD_FIELD_ID, + objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + icon: 'IconMail', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, +}); +``` + + +**Cyklické importy:** Obě relační pole odkazují na `universalIdentifier` toho druhého. Abyste předešli problémům s cyklickými importy, exportujte ID polí jako pojmenované konstanty z každého souboru a v druhém souboru je importujte. Build systém je vyřeší v době kompilace. + + +#### Vazby na standardní objekty + +Chcete-li vytvořit relaci s vestavěným objektem Twenty (Person, Company atd.), použijte `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: + +```ts src/fields/person-on-self-hosting-user.field.ts +import { + defineField, + FieldType, + RelationType, + OnDeleteAction, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; +import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; + +export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; +export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; + +export default defineField({ + universalIdentifier: PERSON_FIELD_ID, + objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'person', + label: 'Person', + description: 'Person matching with the self hosting user', + isNullable: true, + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'personId', + }, +}); +``` + +#### Vlastnosti relačních polí + +| Vlastnost | Povinné | Popis | +| ------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------- | +| `type` | Ano | Musí být `FieldType.RELATION` | +| `relationTargetObjectMetadataUniversalIdentifier` | Ano | `universalIdentifier` cílového objektu | +| `relationTargetFieldMetadataUniversalIdentifier` | Ano | `universalIdentifier` odpovídajícího pole na cílovém objektu | +| `universalSettings.relationType` | Ano | `RelationType.MANY_TO_ONE` nebo `RelationType.ONE_TO_MANY` | +| `universalSettings.onDelete` | Pouze MANY_TO_ONE | Co se stane, když je smazán odkazovaný záznam: `CASCADE`, `SET_NULL`, `RESTRICT` nebo `NO_ACTION` | +| `universalSettings.joinColumnName` | Pouze MANY_TO_ONE | Název databázového sloupce pro cizí klíč (např. `postCardId`) | + +#### Vložená relační pole v defineObject + +Relační pole můžete také definovat přímo uvnitř `defineObject()`. V takovém případě vynechejte `objectUniversalIdentifier` — dědí se z nadřazeného objektu: + +```ts +export default defineObject({ + universalIdentifier: '...', + nameSingular: 'postCardRecipient', + // ... + fields: [ + { + universalIdentifier: POST_CARD_FIELD_ID, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, + }, + // ... other fields + ], +}); +``` + + + +## Generování entit pomocí `yarn twenty add` + +Místo ručního vytváření souborů entit můžete použít interaktivní generátor: + +```bash filename="Terminal" +yarn twenty add +``` + +Požádá vás o výběr typu entity a provede vás požadovanými poli. Vygeneruje soubor připravený k použití se stabilním `universalIdentifier` a správným voláním `defineEntity()`. + +Můžete také předat typ entity přímo a přeskočit první dotaz: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Dostupné typy entit + +| Typ entity | Příkaz | Vygenerovaný soubor | +| ------------------------- | ------------------------------------ | ------------------------------------------------------- | +| Objekt | `yarn twenty add object` | `src/objects/\.ts` | +| Pole | `yarn twenty add field` | `src/fields/\.ts` | +| Logická funkce | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | +| Frontendová komponenta | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | +| Role | `yarn twenty add role` | `src/roles/\.ts` | +| Dovednost | `yarn twenty add skill` | `src/skills/\.ts` | +| Agent | `yarn twenty add agent` | `src/agents/\.ts` | +| Pohled | `yarn twenty add view` | `src/views/\.ts` | +| Položka navigační nabídky | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | +| Rozvržení stránky | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | + +### Co generátor vytváří + +Každý typ entity má vlastní šablonu. Například `yarn twenty add object` se zeptá na: + +1. **Název (jednotné číslo)** — např. `invoice` +2. **Název (množné číslo)** — např. `invoices` +3. **Štítek (jednotné číslo)** — automaticky doplněn z názvu (např. `Invoice`) +4. **Štítek (množné číslo)** — automaticky doplněn (např. `Invoices`) +5. **Vytvořit zobrazení a položku navigace?** — pokud odpovíte ano, generátor také vytvoří odpovídající zobrazení a odkaz v postranním panelu pro nový objekt. + +Ostatní typy entit mají jednodušší dotazy — většinou se ptají pouze na název. + +Typ entity `field` je podrobnější: ptá se na název pole, štítek, typ (ze seznamu všech dostupných typů polí jako `TEXT`, `NUMBER`, `SELECT`, `RELATION` atd.) a `universalIdentifier` cílového objektu. + +### Vlastní výstupní cesta + +Pomocí příznaku `--path` umístíte vygenerovaný soubor do vlastního umístění: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/front-components.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/front-components.mdx new file mode 100644 index 00000000000..aa21dc4fc01 --- /dev/null +++ b/packages/twenty-docs/l/cs/developers/extend/apps/front-components.mdx @@ -0,0 +1,419 @@ +--- +title: Frontendové komponenty +description: Build React components that render inside Twenty's UI with sandboxed isolation. +icon: window-maximize +--- + +Frontendové komponenty jsou React komponenty, které se vykreslují přímo v uživatelském rozhraní Twenty. Běží v **izolovaném Web Workeru** s využitím Remote DOM — váš kód je sandboxovaný, ale vykresluje se nativně na stránce, nikoli v iframu. + +## Kde lze použít frontendové komponenty + +Frontendové komponenty se mohou vykreslovat na dvou místech v rámci Twenty: + +* **Postranní panel** — Frontendové komponenty, které nejsou headless, se otevírají v pravém postranním panelu. Toto je výchozí chování, když je frontendová komponenta vyvolána z příkazového menu. +* **Widgety (nástěnky a stránky záznamů)** — Frontendové komponenty lze vkládat jako widgety do rozložení stránek. Při konfiguraci nástěnky nebo rozložení stránky záznamu mohou uživatelé přidat widget frontendové komponenty. + +## Základní příklad + +Nejrychlejší způsob, jak vidět frontendovou komponentu v akci, je zaregistrovat ji jako **příkaz**. Přidáním pole `command` s `isPinned: true` se zobrazí jako tlačítko rychlé akce v pravém horním rohu stránky — není potřeba žádné rozvržení stránky: + +```tsx src/front-components/hello-world.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; + +const HelloWorld = () => { + return ( +
+

Hello from my app!

+

This component renders inside Twenty.

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, +}); +``` + +Po synchronizaci pomocí `yarn twenty dev` (nebo po jednorázovém spuštění `yarn twenty dev --once`) se rychlá akce zobrazí v pravém horním rohu stránky: + +
+ Tlačítko rychlé akce v pravém horním rohu +
+ +Kliknutím na něj vykreslíte komponentu přímo ve stránce. + +## Konfigurační pole + +| Pole | Povinné | Popis | +| --------------------- | ------- | ------------------------------------------------------------------------------------ | +| `universalIdentifier` | Ano | Stabilní jedinečné ID pro tuto komponentu | +| `component` | Ano | Funkce komponenty React | +| `name` | Ne | Zobrazovaný název | +| `description` | Ne | Popis toho, co komponenta dělá | +| `isHeadless` | Ne | Nastavte na `true`, pokud komponenta nemá viditelné UI (viz níže) | +| `command` | Ne | Zaregistrujte komponentu jako příkaz (viz [možnosti příkazu](#command-options) níže) | + +## Umístění frontendové komponenty na stránku + +Mimo příkazy můžete frontendovou komponentu vložit přímo na stránku záznamu přidáním jako widget v **rozvržení stránky**. Podrobnosti viz sekce [definePageLayout](/l/cs/developers/extend/apps/skills-and-agents#definepagelayout). + +## Headless vs. ne-headless + +Front-endové komponenty existují ve dvou režimech vykreslování řízených volbou `isHeadless`: + +**Ne-headless (výchozí)** — Komponenta vykreslí viditelné uživatelské rozhraní. Po vyvolání z menu příkazů se otevře v postranním panelu. Toto je výchozí chování, když je `isHeadless` `false` nebo když tato volba není uvedena. + +**Headless (`isHeadless: true`)** — Komponenta se neviditelně inicializuje na pozadí. Neotevírá postranní panel. Headless komponenty jsou určené pro akce, které provedou logiku a poté se odpojí — například spuštění asynchronního úkolu, navigaci na stránku nebo zobrazení potvrzovacího modálního okna. Přirozeně se hodí ke komponentám SDK Command popsaným níže. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Protože komponenta vrací `null`, Twenty přeskočí vykreslení kontejneru — v rozvržení se neobjeví žádné prázdné místo. Komponenta má však stále přístup ke všem hookům a API komunikace s hostitelem. + +## Komponenty SDK Command + +Balíček `twenty-sdk` poskytuje čtyři pomocné komponenty Command navržené pro headless front-endové komponenty. Každá komponenta při připojení provede akci, chyby zpracuje zobrazením oznámení ve snackbaru a po dokončení automaticky odpojí front-endovou komponentu. + +Importujte je z `twenty-sdk/command`: + +* **`Command`** — Spustí asynchronní callback přes prop `execute`. +* **`CommandLink`** — Naviguje na cestu v aplikaci. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Otevře potvrzovací modální okno. Pokud uživatel potvrdí, provede callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Otevře konkrétní stránku postranního panelu. Props: `page`, `pageTitle`, `pageIcon`. + +Zde je kompletní příklad headless front-endové komponenty, která pomocí `Command` spouští akci z menu příkazů: + +```tsx src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +A příklad s použitím `CommandModal` k vyžádání potvrzení před provedením: + +```tsx src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +## Přístup k běhovému kontextu + +Uvnitř komponenty použijte hooky SDK pro přístup k aktuálnímu uživateli, záznamu a instanci komponenty: + +```tsx src/front-components/record-info.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk/front-component'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Dostupné hooky: + +| Hook | Vrací | Popis | +| --------------------------------------------- | -------------------- | ------------------------------------------------------------ | +| `useUserId()` | `string` nebo `null` | ID aktuálního uživatele | +| `useRecordId()` | `string` nebo `null` | ID aktuálního záznamu (pokud je umístěna na stránce záznamu) | +| `useFrontComponentId()` | `string` | ID této instance komponenty | +| `useFrontComponentExecutionContext(selector)` | různé | Přístup k úplnému kontextu běhu pomocí selektorové funkce | + +## API komunikace s hostitelem + +Frontendové komponenty mohou pomocí funkcí z `twenty-sdk` vyvolávat navigaci, modály a oznámení: + +| Funkce | Popis | +| ----------------------------------------------- | ------------------------------ | +| `navigate(to, params?, queryParams?, options?)` | Přejít na stránku v aplikaci | +| `openSidePanelPage(params)` | Otevřít postranní panel | +| `closeSidePanel()` | Zavřít postranní panel | +| `openCommandConfirmationModal(params)` | Zobrazit potvrzovací dialog | +| `enqueueSnackbar(params)` | Zobrazit oznámení typu toast | +| `unmountFrontComponent()` | Odpojit komponentu | +| `updateProgress(progress)` | Aktualizovat indikátor průběhu | + +Zde je příklad, který používá hostitelské API k zobrazení snackbaru a zavření postranního panelu po dokončení akce: + +```tsx src/front-components/archive-record.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId } from 'twenty-sdk/front-component'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + +## Možnosti příkazu + +Přidání pole `command` do `defineFrontComponent` zaregistruje komponentu v příkazovém menu (Cmd+K). Pokud je `isPinned` nastaveno na `true`, zobrazí se také jako tlačítko rychlé akce v pravém horním rohu stránky. + +| Pole | Povinné | Popis | +| --------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `universalIdentifier` | Ano | Stabilní jedinečné ID pro příkaz | +| `label` | Ano | Plný popisek zobrazený v příkazovém menu (Cmd+K) | +| `shortLabel` | Ne | Kratší popisek zobrazený na připnutém tlačítku rychlé akce | +| `icon` | Ne | Název ikony zobrazený vedle popisku (např. `'IconBolt'`, `'IconSend'`) | +| `isPinned` | Ne | Pokud je `true`, zobrazí příkaz jako tlačítko rychlé akce v pravém horním rohu stránky | +| `availabilityType` | Ne | Určuje, kde se příkaz zobrazuje: `'GLOBAL'` (vždy dostupné), `'RECORD_SELECTION'` (pouze když jsou vybrány záznamy) nebo `'FALLBACK'` (zobrazeno, když neodpovídají žádné jiné příkazy) | +| `availabilityObjectUniversalIdentifier` | Ne | Omezí příkaz na stránky konkrétního typu objektu (např. pouze u záznamů Company) | +| `conditionalAvailabilityExpression` | Ne | Logický výraz pro dynamické řízení, zda je příkaz viditelný (viz níže) | + +## Výrazy podmíněné dostupnosti + +Pole `conditionalAvailabilityExpression` vám umožní řídit viditelnost příkazu na základě aktuálního kontextu stránky. Pro sestavení výrazů importujte typované proměnné a operátory z `twenty-sdk`: + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk/front-component'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Kontextové proměnné** — reprezentují aktuální stav stránky: + +| Proměnná | Typ | Popis | +| ------------------------------ | --------- | -------------------------------------------------------------------- | +| `pageType` | `string` | Aktuální typ stránky (např. `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Zda je komponenta vykreslena v postranním panelu | +| `numberOfSelectedRecords` | `number` | Počet aktuálně vybraných záznamů | +| `isSelectAll` | `boolean` | Zda je aktivní "vybrat vše" | +| `selectedRecords` | `array` | Vybrané objekty záznamů | +| `favoriteRecordIds` | `array` | ID oblíbených záznamů | +| `objectPermissions` | `object` | Oprávnění pro aktuální typ objektu | +| `targetObjectReadPermissions` | `object` | Oprávnění ke čtení pro cílový objekt | +| `targetObjectWritePermissions` | `object` | Oprávnění k zápisu pro cílový objekt | +| `featureFlags` | `object` | Aktivní příznaky funkcí | +| `objectMetadataItem` | `object` | Metadata aktuálního typu objektu | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Zda má aktuální zobrazení filtr soft-delete | + +**Operátory** — kombinují proměnné do logických výrazů: + +| Operátor | Popis | +| ----------------------------------- | ------------------------------------------------------------------------------ | +| `isDefined(value)` | `true`, pokud hodnota není null/undefined | +| `isNonEmptyString(value)` | `true`, pokud je hodnota neprázdný řetězec | +| `includes(array, value)` | `true`, pokud pole obsahuje danou hodnotu | +| `includesEvery(array, prop, value)` | `true`, pokud vlastnost každé položky zahrnuje danou hodnotu | +| `every(array, prop)` | `true`, pokud je vlastnost u každé položky pravdivá (truthy) | +| `everyDefined(array, prop)` | `true`, pokud je vlastnost definována u každé položky | +| `everyEquals(array, prop, value)` | `true`, pokud se vlastnost rovná hodnotě u každé položky | +| `some(array, prop)` | `true`, pokud je vlastnost pravdivá (truthy) alespoň u jedné položky | +| `someDefined(array, prop)` | `true`, pokud je vlastnost definována alespoň u jedné položky | +| `someEquals(array, prop, value)` | `true`, pokud se vlastnost rovná hodnotě alespoň u jedné položky | +| `someNonEmptyString(array, prop)` | `true`, pokud má vlastnost alespoň u jedné položky hodnotu neprázdného řetězce | +| `none(array, prop)` | `true`, pokud je vlastnost u všech položek nepravdivá (falsy) | +| `noneDefined(array, prop)` | `true`, pokud je vlastnost u všech položek nedefinovaná | +| `noneEquals(array, prop, value)` | `true`, pokud se vlastnost nerovná hodnotě u žádné položky | + +## Veřejné soubory + +Frontendové komponenty mohou přistupovat k souborům ze složky aplikace `public/` pomocí `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +Podrobnosti viz [sekci veřejných souborů](/l/cs/developers/extend/apps/cli-and-testing#public-assets-public-folder). + +## Styling + +Frontendové komponenty podporují více přístupů ke stylování. Můžete použít: + +* **Inline styly** — `style={{ color: 'red' }}` +* **Komponenty Twenty UI** — import z `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar a další) +* **Emotion** — CSS-in-JS s `@emotion/react` +* **Styled-components** — vzory `styled.div` +* **Tailwind CSS** — utilitní třídy +* **Jakákoli CSS-in-JS knihovna** kompatibilní s Reactem + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/getting-started.mdx index 7cd5ecb82c8..b513fd1a24c 100644 --- a/packages/twenty-docs/l/cs/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/cs/developers/extend/apps/getting-started.mdx @@ -1,12 +1,9 @@ --- title: Začínáme +icon: rocket description: Vytvořte svou první aplikaci Twenty během několika minut. --- - -Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí. - - ## Co jsou aplikace? Aplikace vám umožňují rozšířit Twenty o vlastní objekty, pole, logické funkce, frontendové komponenty, AI schopnosti a další — vše je spravováno jako kód. Místo konfigurace všeho přes uživatelské rozhraní definujete v TypeScriptu svůj datový model a logiku a nasadíte je do jednoho nebo více pracovních prostorů. diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/layout.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/layout.mdx new file mode 100644 index 00000000000..9a4c20a1c60 --- /dev/null +++ b/packages/twenty-docs/l/cs/developers/extend/apps/layout.mdx @@ -0,0 +1,131 @@ +--- +title: Rozvržení +description: Define views, navigation menu items, and page layouts to shape how your app appears in Twenty. +icon: table-columns +--- + +Layout entities control how your app surfaces inside Twenty's UI — what lives in the sidebar, which saved views ship with the app, and how a record detail page is arranged. + +## Layout concepts + +| Concept | What it controls | Entita | +| ------------------------ | --------------------------------------------------------------------------------- | -------------------------- | +| **View** | A saved list configuration for an object — visible fields, order, filters, groups | `defineView` | +| **Navigation Menu Item** | An entry in the left sidebar that links to a view or an external URL | `defineNavigationMenuItem` | +| **Page Layout** | The tabs and widgets that make up a record's detail page | `definePageLayout` | + +Views, navigation items, and page layouts reference each other by `universalIdentifier`: + +* A **navigation menu item** of type `VIEW` points at a `defineView` identifier, so the sidebar link opens that saved view. +* A **page layout** of type `RECORD_PAGE` targets an object and can embed [front components](/l/cs/developers/extend/apps/front-components) inside its tabs as widgets. + + + + +Zobrazení jsou uložené konfigurace toho, jak se zobrazují záznamy objektu — včetně toho, která pole jsou viditelná, jejich pořadí a jaké filtry či seskupení jsou použity. Pomocí `defineView()` můžete k aplikaci přidat předkonfigurovaná zobrazení: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +Hlavní body: +* `objectUniversalIdentifier` určuje, na který objekt se toto zobrazení vztahuje. +* `key` určuje typ zobrazení (např. `ViewKey.INDEX` pro hlavní seznam). +* `fields` určuje, které sloupce se zobrazí a v jakém pořadí. Každé pole odkazuje na `fieldMetadataUniversalIdentifier`. +* Pro pokročilejší konfigurace můžete definovat také `filters`, `filterGroups`, `groups` a `fieldGroups`. +* `position` určuje pořadí, pokud pro stejný objekt existuje více zobrazení. + + + + +Položky navigační nabídky přidávají vlastní položky do postranního panelu pracovního prostoru. Použijte `defineNavigationMenuItem()` k odkazování na zobrazení, externí URL nebo objekty: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Hlavní body: +* `type` určuje, na co položka menu odkazuje: `NavigationMenuItemType.VIEW` pro uložené zobrazení nebo `NavigationMenuItemType.LINK` pro externí URL. +* Pro odkazy na zobrazení nastavte `viewUniversalIdentifier`. Pro externí odkazy nastavte `link`. +* `position` určuje pořadí v postranním panelu. +* `icon` a `color` (volitelné) upravují vzhled. + + + + +Rozvržení stránek vám umožní přizpůsobit vzhled stránky s detailem záznamu — které karty se zobrazí, jaké widgety jsou uvnitř každé karty a jak jsou uspořádány. Pomocí `definePageLayout()` můžete k aplikaci přidat vlastní rozvržení: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Hlavní body: +* `type` je obvykle `'RECORD_PAGE'` pro úpravu detailního zobrazení konkrétního objektu. +* `objectUniversalIdentifier` určuje, na který objekt se toto rozvržení vztahuje. +* Každá `tab` definuje sekci stránky s `title`, `position` a `layoutMode` (`CANVAS` pro volné rozvržení). +* Každý `widget` uvnitř karty může vykreslit frontendovou komponentu, seznam relací nebo jiné vestavěné typy widgetů. +* `position` na kartách určuje jejich pořadí. Použijte vyšší hodnoty (např. 50) pro umístění vlastních karet za vestavěné. + + + diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/logic-functions.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/logic-functions.mdx new file mode 100644 index 00000000000..ca8a3b335c0 --- /dev/null +++ b/packages/twenty-docs/l/cs/developers/extend/apps/logic-functions.mdx @@ -0,0 +1,560 @@ +--- +title: Logické funkce +description: Define server-side TypeScript functions with HTTP, cron, and database event triggers. +icon: bolt +--- + +Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents. + + + + +Každý soubor funkce používá `defineLogicFunction()` k exportu konfigurace s obslužnou funkcí (handlerem) a volitelnými spouštěči. + +```ts src/logic-functions/createPostCard.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; + +const handler = async (params: RoutePayload) => { + const client = new CoreApiClient(); + const name = 'name' in params.queryStringParameters + ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' + : 'Hello world'; + + const result = await client.mutation({ + createPostCard: { + __args: { data: { name } }, + id: true, + name: true, + }, + }); + return result; +}; + +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'create-new-post-card', + timeoutSeconds: 2, + handler, + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: true, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ +}); +``` + +Dostupné typy spouštěčů: +* **httpRoute**: Zpřístupní vaši funkci na HTTP cestě a metodě **pod koncovým bodem `/s/`**: +> např. `path: '/post-card/create'` je volatelné na `https://your-twenty-server.com/s/post-card/create` +* **cron**: Spouští vaši funkci podle plánu pomocí výrazu CRON. +* **databaseEvent**: Spouští se při událostech životního cyklu objektů v pracovním prostoru. Když je operace události `updated`, lze konkrétní sledovaná pole určit v poli `updatedFields`. Pokud zůstane nedefinované nebo prázdné, spustí funkci jakákoli aktualizace. +> např. `person.updated`, `*.created`, `company.*` + + +Funkci můžete také spustit ručně pomocí CLI: + +```bash filename="Terminal" +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' +``` + +```bash filename="Terminal" +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + +Logy můžete sledovat pomocí: + +```bash filename="Terminal" +yarn twenty logs +``` + + +#### Payload spouštěče trasy + +Když spouštěč typu route vyvolá vaši logickou funkci, ta obdrží objekt `RoutePayload`, který odpovídá +[AWS HTTP API v2 formátu](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +Importujte typ `RoutePayload` z `twenty-sdk`: + +```ts +import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; + +const handler = async (event: RoutePayload) => { + const { headers, queryStringParameters, pathParameters, body } = event; + const { method, path } = event.requestContext.http; + + return { message: 'Success' }; +}; +``` + +Typ `RoutePayload` má následující strukturu: + + | Vlastnost | Typ | Popis | Příklad | + | ---------------------------- | ------------------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record\` | Záhlaví HTTP (pouze ta uvedená v `forwardedRequestHeaders`) | viz sekci níže | + | `queryStringParameters` | `Record\` | Parametry query stringu (více hodnot spojených čárkami) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record\` | Parametry cesty extrahované ze vzoru trasy | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Parsované tělo požadavku (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | Zda je tělo kódováno base64 | | + | `requestContext.http.method` | `string` | Metoda HTTP (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | Nezpracovaná cesta požadavku | | + + +#### forwardedRequestHeaders + +Ve výchozím nastavení se záhlaví HTTP z příchozích požadavků z bezpečnostních důvodů do vaší logické funkce **ne** předávají. +Chcete-li zpřístupnit konkrétní záhlaví, výslovně je uveďte v poli `forwardedRequestHeaders`: + +```ts +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'webhook-handler', + handler, + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, +}); +``` + +Ve vašem handleru k přeposlaným záhlavím přistupujte takto: + +```ts +const handler = async (event: RoutePayload) => { + const signature = event.headers['x-webhook-signature']; + const contentType = event.headers['content-type']; + + // Validate webhook signature... + return { received: true }; +}; +``` + + +Názvy záhlaví jsou normalizovány na malá písmena. Přistupujte k nim pomocí klíčů s malými písmeny (například `event.headers['content-type']`). + + +#### Zpřístupnění funkce jako nástroje + +Logické funkce lze zpřístupnit jako **nástroje** pro agenty AI a pracovní postupy. Když je funkce označena jako nástroj, stane se dohledatelnou funkcemi AI produktu Twenty a lze ji použít v automatizacích pracovních postupů. + +Chcete-li označit logickou funkci jako nástroj, nastavte `isTool: true`: + +```ts src/logic-functions/enrich-company.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const handler = async (params: { companyName: string; domain?: string }) => { + const client = new CoreApiClient(); + + const result = await client.mutation({ + createTask: { + __args: { + data: { + title: `Enrich data for ${params.companyName}`, + body: `Domain: ${params.domain ?? 'unknown'}`, + }, + }, + id: true, + }, + }); + + return { taskId: result.createTask.id }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + name: 'enrich-company', + description: 'Enrich a company record with external data', + timeoutSeconds: 10, + handler, + isTool: true, +}); +``` + +Hlavní body: + +* Můžete kombinovat `isTool` se spouštěči — funkce může být zároveň nástrojem (volatelným agenty AI) a současně se spouštět událostmi. +* **`toolInputSchema`** (volitelné): Objekt JSON Schema, který popisuje parametry, jež vaše funkce přijímá. Schéma se určuje automaticky ze statické analýzy zdrojového kódu, ale můžete ho nastavit i explicitně: + +```ts +export default defineLogicFunction({ + ..., + toolInputSchema: { + type: 'object', + properties: { + companyName: { + type: 'string', + description: 'The name of the company to enrich', + }, + domain: { + type: 'string', + description: 'The company website domain (optional)', + }, + }, + required: ['companyName'], + }, +}); +``` + + +**Napište kvalitní `description`.** Agenti AI se spoléhají na pole funkce `description` při rozhodování, kdy nástroj použít. Buďte konkrétní ohledně toho, co nástroj dělá a kdy se má volat. + + + + + +Postinstalační funkce je logická funkce, která se spustí automaticky, jakmile je instalace vaší aplikace v pracovním prostoru dokončena. Server ji provede **poté**, co byla synchronizována metadata aplikace a vygenerován klient SDK, takže je pracovní prostor plně připraven k použití a nové schéma je zavedeno. Mezi typické případy použití patří naplnění výchozími daty, vytvoření počátečních záznamů, konfigurace nastavení pracovního prostoru nebo zřizování prostředků ve službách třetích stran. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + shouldRunSynchronously: false, + handler, +}); +``` + +Postinstalační funkci můžete také kdykoli spustit ručně pomocí CLI: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Hlavní body: +* Postinstalační funkce používají `definePostInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* Obslužná funkce obdrží `InstallPayload` s `{ previousVersion?: string; newVersion: string }` — `newVersion` je verze, která se instaluje, a `previousVersion` je verze, která byla nainstalována dříve (nebo `undefined` při čisté instalaci). Tyto hodnoty použijte k rozlišení čistých instalací od aktualizací a ke spuštění migrační logiky specifické pro verzi. +* **Kdy se hook spouští**: ve výchozím nastavení pouze při čistých instalacích. Předejte `shouldRunOnVersionUpgrade: true`, pokud chcete, aby se spouštěl i při aktualizaci aplikace z předchozí verze. Pokud je vynechán, příznak má výchozí hodnotu `false` a při aktualizacích se hook přeskočí. +* **Model provádění — ve výchozím nastavení asynchronní, synchronní volitelně**: příznak `shouldRunSynchronously` určuje *jak* se spouští post-install. + * `shouldRunSynchronously: false` *(výchozí)* — hook je **zařazen do fronty zpráv** s `retryLimit: 3` a běží asynchronně ve workeru. Odezva instalace se vrátí hned po zařazení úlohy do fronty, takže pomalá nebo chybující obslužná funkce neblokuje volajícího. Worker se pokusí o opakování až třikrát. **Použijte pro dlouho běžící úlohy** — plnění velkých datových sad, volání pomalých externích API, zřizování externích prostředků, cokoli, co by mohlo přesáhnout rozumné časové okno HTTP odezvy. + * `shouldRunSynchronously: true` — hook se provádí **inline během instalačního procesu** (stejný vykonavatel jako pre-install). Instalační požadavek blokuje, dokud obslužná funkce nedokončí, a pokud vyvolá výjimku, volající instalace obdrží `POST_INSTALL_ERROR`. Žádné automatické opakování. **Použijte pro rychlé úlohy, které se musí dokončit před odpovědí** — například vrácení validační chyby uživateli nebo rychlé nastavení, na kterém bude klient záviset ihned po návratu volání instalace. Mějte na paměti, že v době, kdy se spustí post-install, už byla migrace metadat aplikována, takže selhání v synchronním režimu změny schématu **ne**vrací zpět — pouze odhalí chybu. +* Ujistěte se, že vaše obslužná funkce je idempotentní. V asynchronním režimu se může fronta pokusit až třikrát; v obou režimech se může hook znovu spustit při aktualizacích, pokud je `shouldRunOnVersionUpgrade: true`. +* Proměnné prostředí `APPLICATION_ID`, `APP_ACCESS_TOKEN` a `API_URL` jsou dostupné uvnitř obslužné funkce (stejně jako u jakékoli jiné logické funkce), takže můžete volat Twenty API s aplikačním přístupovým tokenem omezeným na vaši aplikaci. +* Na jednu aplikaci je povolena pouze jedna postinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna. +* Atributy funkce `universalIdentifier`, `shouldRunOnVersionUpgrade` a `shouldRunSynchronously` jsou během buildu automaticky připojeny k manifestu aplikace do pole `postInstallLogicFunction` — není potřeba je uvádět v `defineApplication()`. +* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší úlohy nastavení, jako je naplnění daty. +* **Nespouští se v režimu dev**: když je aplikace registrována lokálně (pomocí `yarn twenty dev`), server zcela přeskočí instalační tok a synchronizuje soubory přímo prostřednictvím sledovače CLI — takže se post-install v režimu dev nikdy nespustí bez ohledu na `shouldRunSynchronously`. Použijte `yarn twenty exec --postInstall` k ručnímu spuštění nad běžícím pracovním prostorem. + + + + +Funkce pre-install je logická funkce, která se během instalace spouští automaticky, **před aplikováním migrace metadat pracovního prostoru**. Má stejný tvar payloadu jako post-install (`InstallPayload`), ale je zařazena dříve v instalačním toku, aby mohla připravit stav, na němž nadcházející migrace závisí — typické použití zahrnuje zálohování dat, ověření kompatibility s novým schématem nebo archivaci záznamů, které se chystají přeuspořádat nebo odstranit. + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +Předinstalační funkci můžete také kdykoli spustit ručně pomocí CLI: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Hlavní body: +* Funkce pre-install používají `definePreInstallLogicFunction()` — stejné specializované nastavení jako u post-install, pouze připojené k jiné fázi životního cyklu. +* Obě obslužné funkce pre- i post-install přijímají stejný typ `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Importujte jej jednou a znovu použijte pro oba hooky. +* **Kdy se hook spouští**: umístěn těsně před migrací metadat pracovního prostoru (`synchronizeFromManifest`). Před spuštěním server provede čistě aditivní "zjednodušenou synchronizaci", která v metadatech pracovního prostoru zaregistruje pre-install funkci **nové** verze — ničeho dalšího se nedotkne — a poté ji spustí. Protože tato synchronizace je pouze aditivní, objekty, pole a data předchozí verze zůstávají při spuštění vaší obslužné funkce zachována: můžete bezpečně číst a zálohovat stav před migrací. +* **Model provádění**: pre-install se provádí **synchronně** a **blokuje instalaci**. Pokud obslužná funkce vyvolá výjimku, instalace se přeruší ještě před aplikováním jakýchkoli změn schématu — pracovní prostor zůstane na předchozí verzi v konzistentním stavu. Je to záměrné: pre-install je vaše poslední šance odmítnout rizikovou aktualizaci. +* Stejně jako u post-install je na jednu aplikaci povolena pouze jedna funkce pre-install. Během buildu je automaticky připojena k manifestu aplikace pod `preInstallLogicFunction`. +* **Nespouští se v režimu dev**: stejně jako u post-install — u lokálně registrovaných aplikací je instalační tok zcela přeskočen, takže se pre-install pod `yarn twenty dev` nikdy nespustí. Použijte `yarn twenty exec --preInstall` k ručnímu spuštění. + + + + +Oba hooky jsou součástí téhož instalačního toku a přijímají stejný `InstallPayload`. Rozdíl je v tom, **kdy** se spouštějí vzhledem k migraci metadat pracovního prostoru, a to určuje, jakých dat se mohou bezpečně dotýkat. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ install flow │ +│ │ +│ upload package → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +│ │ +│ old schema visible new schema visible │ +└─────────────────────────────────────────────────────────────┘ +``` + +Pre-install je vždy **synchronní** (blokuje instalaci a může ji přerušit). Post-install je **ve výchozím nastavení asynchronní** — zařazen do workeru s automatickými pokusy o opakování — ale může přejít na synchronní provádění pomocí `shouldRunSynchronously: true`. Viz accordion `definePostInstallLogicFunction` výše, kdy použít jednotlivé režimy. + +**Použijte `post-install` pro cokoli, co vyžaduje existenci nového schématu.** To je běžný případ: + +* Plnění výchozími daty (vytváření počátečních záznamů, výchozích pohledů, demo obsahu) vůči nově přidaným objektům a polím. +* Registrace webhooků u služeb třetích stran poté, co má aplikace své přihlašovací údaje. +* Volání vlastního API k dokončení nastavení, které závisí na synchronizovaných metadatech. +* Idempotentní logika "zajisti, že to existuje", která má při každé aktualizaci uvést stav do souladu — kombinujte s `shouldRunOnVersionUpgrade: true`. + +Příklad — po instalaci naplňte výchozí záznam `PostCard`: + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion }: InstallPayload): Promise => { + if (previousVersion) return; // fresh installs only + + const client = createClient(); + await client.postCard.create({ + data: { title: 'Welcome to Postcard', content: 'Your first card!' }, + }); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Seeds a welcome post card after install.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + handler, +}); +``` + +**Použijte `pre-install`, pokud by migrace jinak zničila nebo poškodila existující data.** Protože pre-install běží proti *předchozímu* schématu a jeho selhání vrací aktualizaci zpět, je to správné místo pro cokoli rizikového: + +* **Zálohování dat, která se chystají odstranit nebo přeuspořádat** — např. odstraňujete pole ve verzi v2 a potřebujete jeho hodnoty zkopírovat do jiného pole nebo je před spuštěním migrace exportovat do úložiště. +* **Archivace záznamů, které by nové omezení zneplatnilo** — např. pole se stává `NOT NULL` a je třeba nejprve smazat nebo opravit řádky s hodnotami null. +* **Ověření kompatibility a odmítnutí aktualizace, pokud nelze aktuální data čistě migrovat** — vyhoďte výjimku z obslužné funkce a instalace se ukončí bez provedených změn. Je to bezpečnější, než zjistit nekompatibilitu uprostřed migrace. +* **Přejmenování nebo změna klíčů dat** před změnou schématu, která by ztratila vazby. + +Příklad — archivujte záznamy před destruktivní migrací: + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { + // Only the 1.x → 2.x upgrade drops the legacy `notes` field. + if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { + return; + } + + const client = createClient(); + const legacyRecords = await client.postCard.findMany({ + where: { notes: { isNotNull: true } }, + }); + + if (legacyRecords.length === 0) return; + + // Copy legacy `notes` into the new `description` field before the migration + // drops the `notes` column. If this fails, the upgrade is aborted and the + // workspace stays on v1 with all data intact. + await Promise.all( + legacyRecords.map((record) => + client.postCard.update({ + where: { id: record.id }, + data: { description: record.notes }, + }), + ), + ); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Backs up legacy notes into description before the v2 migration.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +**Zlaté pravidlo:** + +| You want to... | Použít | +| ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| Naplňte výchozí data, nakonfigurujte pracovní prostor, zaregistrujte externí prostředky | `post-install` | +| Spusťte dlouho běžící plnění nebo volání třetích stran, která by neměla blokovat odezvu instalace | `post-install` (výchozí — `shouldRunSynchronously: false`, s opakovanými pokusy workeru) | +| Spusťte rychlé nastavení, na které bude volající spoléhat ihned po návratu volání instalace | `post-install` s `shouldRunSynchronously: true` | +| Čtěte nebo zálohujte data, která by nadcházející migrace ztratila | `pre-install` | +| Odmítněte aktualizaci, která by poškodila existující data | `pre-install` (vyhoďte výjimku z obslužné funkce) | +| Spouštějte srovnání stavu při každé aktualizaci | `post-install` s `shouldRunOnVersionUpgrade: true` | +| Proveďte jednorázové nastavení pouze při první instalaci | `post-install` s `shouldRunOnVersionUpgrade: false` (výchozí) | + + +Pokud si nejste jisti, výchozí volbou je **post-install**. Po pre-install sáhněte pouze tehdy, když je samotná migrace destruktivní a potřebujete zachytit předchozí stav, než zmizí. + + + + + +## Typovaní klienti API (twenty-client-sdk) + +Balíček `twenty-client-sdk` poskytuje dva typované klienty GraphQL pro práci s Twenty API z vašich logických funkcí a frontendových komponent. + +| Klient | Importovat | Koncový bod | Generováno? | +| ------------------- | ---------------------------- | ---------------------------------------------------------------- | ------------------------------ | +| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — data pracovního prostoru (záznamy, objekty) | Ano, při vývoji/sestavení | +| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — konfigurace pracovního prostoru, nahrávání souborů | Ne, dodává se předem sestavený | + + + + +`CoreApiClient` je hlavní klient pro dotazování a mutace dat pracovního prostoru. Generuje se z vašeho schématu pracovního prostoru během `yarn twenty dev` nebo `yarn twenty build`, takže je plně typovaný tak, aby odpovídal vašim objektům a polím. + +```ts +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const client = new CoreApiClient(); + +// Query records +const { companies } = await client.query({ + companies: { + edges: { + node: { + id: true, + name: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, + }, + }, + }, +}); + +// Create a record +const { createCompany } = await client.mutation({ + createCompany: { + __args: { + data: { + name: 'Acme Corp', + }, + }, + id: true, + name: true, + }, +}); +``` + +Klient používá syntaxi výběrové sady (selection-set): předáním `true` zahrnete pole, pro argumenty použijte `__args` a pro relace vnořujte objekty. Získáte plné automatické doplňování a kontrolu typů založené na schématu vašeho pracovního prostoru. + + +**CoreApiClient je generován při vývoji/sestavení.** Pokud jej použijete bez předchozího spuštění `yarn twenty dev` nebo `yarn twenty build`, vyvolá chybu. Generování probíhá automaticky — CLI prozkoumá GraphQL schéma vašeho pracovního prostoru a vygeneruje typovaného klienta pomocí `@genql/cli`. + + +#### Použití CoreSchema pro anotace typů + +`CoreSchema` poskytuje typy TypeScriptu odpovídající objektům vašeho pracovního prostoru — hodí se pro typování stavu komponent nebo parametrů funkcí: + +```ts +import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; +import { useState } from 'react'; + +const [company, setCompany] = useState< + Pick | undefined +>(undefined); + +const client = new CoreApiClient(); +const result = await client.query({ + company: { + __args: { filter: { position: { eq: 1 } } }, + id: true, + name: true, + }, +}); +setCompany(result.company); +``` + + + + +`MetadataApiClient` je dodáván předem sestavený v rámci SDK (není vyžadováno žádné generování). Odesílá dotazy na endpoint `/metadata` pro konfiguraci pracovního prostoru, aplikace a nahrávání souborů. + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; + +const metadataClient = new MetadataApiClient(); + +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, + }, +}); +``` + +#### Nahrávání souborů + +`MetadataApiClient` obsahuje metodu `uploadFile` pro připojování souborů k polím typu souboru: + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import * as fs from 'fs'; + +const metadataClient = new MetadataApiClient(); + +const fileBuffer = fs.readFileSync('./invoice.pdf'); + +const uploadedFile = await metadataClient.uploadFile( + fileBuffer, // file contents as a Buffer + 'invoice.pdf', // filename + 'application/pdf', // MIME type + '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier +); + +console.log(uploadedFile); +// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } +``` + +| Parametr | Typ | Popis | +| ---------------------------------- | -------- | ------------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | Surový obsah souboru | +| `filename` | `string` | Název souboru (používá se pro ukládání a zobrazení) | +| `contentType` | `string` | Typ MIME (pokud je vynechán, výchozí je `application/octet-stream`) | +| `fieldMetadataUniversalIdentifier` | `string` | `universalIdentifier` pole typu souboru ve vašem objektu | + +Hlavní body: +* Používá `universalIdentifier` pole (nikoli jeho ID specifické pro pracovní prostor), takže váš kód pro nahrávání funguje v jakémkoli pracovním prostoru, kde je vaše aplikace nainstalována. +* Vrácená hodnota `url` je podepsaná adresa URL, kterou můžete použít k přístupu k nahranému souboru. + + + + + + Když váš kód běží na Twenty (logické funkce nebo frontendové komponenty), platforma vloží přihlašovací údaje jako proměnné prostředí: + + * `TWENTY_API_URL` — Základní URL Twenty API + * `TWENTY_APP_ACCESS_TOKEN` — krátkodobý klíč s rozsahem omezeným na výchozí roli funkce vaší aplikace + + Není nutné je předávat klientům — čtou je automaticky z `process.env`. Oprávnění API klíče jsou určena rolí uvedenou v `defaultRoleUniversalIdentifier` ve vašem `application-config.ts`. + diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/publishing.mdx index 90d389717f5..7557e432fc2 100644 --- a/packages/twenty-docs/l/cs/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/cs/developers/extend/apps/publishing.mdx @@ -1,12 +1,9 @@ --- title: Publikování +icon: nahrát description: Distribuujte svou aplikaci Twenty do Marketplace nebo ji nasaďte interně. --- - - Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí. - - ## Přehled Jakmile je vaše aplikace [sestavena a otestována lokálně](/l/cs/developers/extend/apps/building), máte dvě cesty, jak ji distribuovat: diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/skills-and-agents.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/skills-and-agents.mdx new file mode 100644 index 00000000000..84569404bf7 --- /dev/null +++ b/packages/twenty-docs/l/cs/developers/extend/apps/skills-and-agents.mdx @@ -0,0 +1,69 @@ +--- +title: Dovednosti a agenti +description: Define AI skills and agents for your app. +icon: robot +--- + + + Skills and agents are currently in alpha. Funkce funguje, ale stále se vyvíjí. + + +Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts. + + + + +Dovednosti definují znovupoužitelné pokyny a schopnosti, které mohou agenti AI používat ve vašem pracovním prostoru. K definování dovedností s vestavěnou validací použijte `defineSkill()`: + +```ts src/skills/example-skill.ts +import { defineSkill } from 'twenty-sdk/define'; + +export default defineSkill({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'sales-outreach', + label: 'Sales Outreach', + description: 'Guides the AI agent through a structured sales outreach process', + icon: 'IconBrain', + content: `You are a sales outreach assistant. When reaching out to a prospect: +1. Research the company and recent news +2. Identify the prospect's role and likely pain points +3. Draft a personalized message referencing specific details +4. Keep the tone professional but conversational`, +}); +``` + +Hlavní body: +* `name` je jedinečný identifikátor dovednosti (doporučuje se kebab-case). +* `label` je uživatelsky čitelný název zobrazovaný v UI. +* `content` obsahuje pokyny dovednosti — je to text, který agent AI používá. +* `icon` (volitelné) nastavuje ikonu zobrazovanou v UI. +* `description` (volitelné) poskytuje doplňující kontext o účelu dovednosti. + + + + +Agenti jsou asistenti AI, kteří běží ve vašem pracovním prostoru. K vytvoření agentů s vlastním systémovým promptem použijte `defineAgent()`: + +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk/define'; + +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +Hlavní body: +* `name` je jedinečný identifikátor agenta (doporučuje se kebab-case). +* `label` je zobrazovaný název v UI. +* `prompt` je systémový prompt, který definuje chování agenta. +* `description` (volitelné) poskytuje kontext o tom, co agent dělá. +* `icon` (volitelné) nastavuje ikonu zobrazovanou v UI. +* `modelId` (volitelné) přepíše výchozí model AI používaný agentem. + + + diff --git a/packages/twenty-docs/l/cs/developers/extend/oauth.mdx b/packages/twenty-docs/l/cs/developers/extend/oauth.mdx new file mode 100644 index 00000000000..141c7e04105 --- /dev/null +++ b/packages/twenty-docs/l/cs/developers/extend/oauth.mdx @@ -0,0 +1,189 @@ +--- +title: OAuth +icon: klíč +description: Authorization code flow with PKCE and client credentials for server-to-server access. +--- + +Twenty implements OAuth 2.0 with authorization code + PKCE for user-facing apps and client credentials for server-to-server access. Clients are registered dynamically via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) — no manual setup in a dashboard. + +## When to Use OAuth + +| Scénář | Auth Method | +| --------------------------------------- | -------------------------------------------------------------------------------- | +| Internal scripts, automation | [API Key](/l/cs/developers/extend/api#authentication) | +| External app acting on behalf of a user | **OAuth — Authorization Code** | +| Server-to-server, no user context | **OAuth — Client Credentials** | +| Twenty App with UI extensions | [Apps](/l/cs/developers/extend/apps/getting-started) (OAuth is handled automatically) | + +## Register a Client + +Twenty supports **dynamic client registration** per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). No manual setup needed — register programmatically: + +```bash +POST /oauth/register +Content-Type: application/json + +{ + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"], + "grant_types": ["authorization_code"], + "token_endpoint_auth_method": "client_secret_post" +} +``` + +**Response:** + +```json +{ + "client_id": "abc123", + "client_secret": "secret456", + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"] +} +``` + + +Store the `client_secret` securely — it cannot be retrieved later. + + +## Oprávnění + +| Scope | Přístup | +| -------- | ---------------------------------------------------- | +| `api` | Full read/write access to the Core and Metadata APIs | +| `profil` | Read the authenticated user's profile information | + +Request scopes as a space-separated string: `scope=api profile` + +## Authorization Code Flow + +Use this flow when your app acts on behalf of a Twenty user. + +### 1. Redirect the user to authorize + +``` +GET /oauth/authorize? + client_id=YOUR_CLIENT_ID& + response_type=code& + redirect_uri=https://myapp.com/callback& + scope=api& + state=random_state_value& + code_challenge=CHALLENGE& + code_challenge_method=S256 +``` + +| Parametr | Povinné | Popis | +| ----------------------- | ---------- | ------------------------------------------------------------ | +| `client_id` | Ano | Your registered client ID | +| `response_type` | Ano | Must be `code` | +| `redirect_uri` | Ano | Must match a registered redirect URI | +| `scope` | Ne | Space-separated scopes (defaults to `api`) | +| `stav` | Doporučeno | Random string to prevent CSRF attacks | +| `code_challenge` | Doporučeno | PKCE challenge (SHA-256 hash of verifier, base64url-encoded) | +| `code_challenge_method` | Doporučeno | Must be `S256` when using PKCE | + +The user sees a consent screen and approves or denies access. + +### 2. Handle the callback + +After authorization, Twenty redirects back to your `redirect_uri`: + +``` +https://myapp.com/callback?code=AUTH_CODE&state=random_state_value +``` + +Verify that `state` matches what you sent. + +### 3. Exchange the code for tokens + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=authorization_code& +code=AUTH_CODE& +redirect_uri=https://myapp.com/callback& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +code_verifier=YOUR_PKCE_VERIFIER +``` + +**Response:** + +```json +{ + "access_token": "eyJhbG...", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "dGhpcyBpcyBh..." +} +``` + +### 4. Use the access token + +```bash +GET /rest/companies +Authorization: Bearer ACCESS_TOKEN +``` + +### 5. Refresh when expired + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=refresh_token& +refresh_token=YOUR_REFRESH_TOKEN& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET +``` + +## Client Credentials Flow + +For server-to-server integrations with no user interaction: + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=client_credentials& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +scope=api +``` + +The returned token has workspace-level access, not tied to any specific user. + +## Server Discovery + +Twenty publishes its OAuth configuration at a standard discovery endpoint: + +``` +GET /.well-known/oauth-authorization-server +``` + +This returns all endpoints, supported grant types, scopes, and capabilities — useful for building generic OAuth clients. + +## API Endpoints Summary + +| Koncový bod | Účel | +| ----------------------------------------- | --------------------------- | +| `/.well-known/oauth-authorization-server` | Server metadata discovery | +| `/oauth/register` | Dynamic client registration | +| `/oauth/authorize` | User authorization | +| `/oauth/token` | Token exchange and refresh | + +| Prostředí | Základní URL | +| ------------------- | ------------------------ | +| **Cloud** | `https://api.twenty.com` | +| **Vlastní hosting** | `https://{your-domain}` | + +## OAuth vs API Keys + +| | API Klíče | OAuth | +| ------------------ | ----------------------- | -------------------------------------- | +| **Nastavení** | Generate in Settings | Register a client, implement flow | +| **User context** | None (workspace-level) | Specific user's permissions | +| **Vhodné pro** | Scripts, internal tools | External apps, multi-user integrations | +| **Token rotation** | Ruční | Automatic via refresh tokens | +| **Scoped access** | Full API access | Granular via scopes | diff --git a/packages/twenty-docs/l/cs/developers/extend/webhooks.mdx b/packages/twenty-docs/l/cs/developers/extend/webhooks.mdx index 814e59780e9..747eef6ac40 100644 --- a/packages/twenty-docs/l/cs/developers/extend/webhooks.mdx +++ b/packages/twenty-docs/l/cs/developers/extend/webhooks.mdx @@ -1,11 +1,12 @@ --- title: Webhooky -description: Dostávejte oznámení v reálném čase, když ve vašem CRM dojde k událostem. +icon: satellite-dish +description: Get notified when records change — HTTP POST to your endpoint on every create, update, or delete. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -Webhooky posílají data do vašich systémů v reálném čase, když v Twenty dojde k událostem — bez potřeby průběžného dotazování. Použijte je k udržování externích systémů v synchronizaci, spouštění automatizací nebo zasílání upozornění. +Twenty sends an HTTP POST to your URL whenever a record is created, updated, or deleted. All object types are covered, including custom objects. ## Vytvořit Webhook diff --git a/packages/twenty-docs/l/cs/developers/introduction.mdx b/packages/twenty-docs/l/cs/developers/introduction.mdx index a155d446d2d..60c37fd46fa 100644 --- a/packages/twenty-docs/l/cs/developers/introduction.mdx +++ b/packages/twenty-docs/l/cs/developers/introduction.mdx @@ -1,23 +1,28 @@ --- -title: Začínáme -description: Vítejte v dokumentaci pro vývojáře Twenty, která je vaším zdrojem informací pro rozšiřování, vlastní hostování a přispívání do Twenty. +title: Vývojáři +description: Build apps, use the API, self-host, or contribute to the codebase. --- import { CardTitle } from "/snippets/card-title.mdx" - - Rozšiřte - Vytvářejte integrace pomocí API, webhooků a vlastních aplikací. + + Apps + Extend Twenty with custom objects, server-side logic, UI components, and AI agents — all as TypeScript packages. - - Hostujte sami - Nasaďte a spravujte Twenty na vlastní infrastruktuře. + + API + REST and GraphQL APIs, webhooks, and OAuth. - - Přispějte - Připojte se k naší open-source komunitě a přispívejte do Twenty. + + Self-Host + Run Twenty on your own infrastructure. + + + + Contribute + Set up the monorepo locally and submit PRs. diff --git a/packages/twenty-docs/l/cs/developers/self-host/capabilities/cloud-providers.mdx b/packages/twenty-docs/l/cs/developers/self-host/capabilities/cloud-providers.mdx index 582a401f86e..7ee80f1f18f 100644 --- a/packages/twenty-docs/l/cs/developers/self-host/capabilities/cloud-providers.mdx +++ b/packages/twenty-docs/l/cs/developers/self-host/capabilities/cloud-providers.mdx @@ -1,5 +1,6 @@ --- title: Další metody +icon: cloud --- diff --git a/packages/twenty-docs/l/cs/developers/self-host/capabilities/docker-compose.mdx b/packages/twenty-docs/l/cs/developers/self-host/capabilities/docker-compose.mdx index 8f824a96d95..76b0fbe6504 100644 --- a/packages/twenty-docs/l/cs/developers/self-host/capabilities/docker-compose.mdx +++ b/packages/twenty-docs/l/cs/developers/self-host/capabilities/docker-compose.mdx @@ -1,5 +1,6 @@ --- -title: Docker Compose jedním kliknutím +title: Docker Compose +icon: docker --- diff --git a/packages/twenty-docs/l/cs/developers/self-host/capabilities/setup.mdx b/packages/twenty-docs/l/cs/developers/self-host/capabilities/setup.mdx index 51579762b8a..7be493c838d 100644 --- a/packages/twenty-docs/l/cs/developers/self-host/capabilities/setup.mdx +++ b/packages/twenty-docs/l/cs/developers/self-host/capabilities/setup.mdx @@ -1,5 +1,6 @@ --- title: Nastavení +icon: gear --- # Správa konfigurace diff --git a/packages/twenty-docs/l/cs/developers/self-host/capabilities/troubleshooting.mdx b/packages/twenty-docs/l/cs/developers/self-host/capabilities/troubleshooting.mdx index fde534cf80c..5c7bc87c04f 100644 --- a/packages/twenty-docs/l/cs/developers/self-host/capabilities/troubleshooting.mdx +++ b/packages/twenty-docs/l/cs/developers/self-host/capabilities/troubleshooting.mdx @@ -1,5 +1,6 @@ --- title: Řešení potíží +icon: wrench --- ## Řešení potíží diff --git a/packages/twenty-docs/l/cs/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/cs/developers/self-host/capabilities/upgrade-guide.mdx index f20c1d6c57a..e37e6d5fda4 100644 --- a/packages/twenty-docs/l/cs/developers/self-host/capabilities/upgrade-guide.mdx +++ b/packages/twenty-docs/l/cs/developers/self-host/capabilities/upgrade-guide.mdx @@ -1,5 +1,6 @@ --- title: Průvodce upgradem +icon: arrow-up-right-dots --- ## Obecné pokyny @@ -16,366 +17,14 @@ Pokud jste použili Docker Compose, postupujte takto: 3. Opětovně zapněte Twenty pomocí `docker compose up -d` -Chcete-li upgradovat svou instanci o několik verzí, například z v0.33.0 na v0.35.0, musíte svou instanci postupně upgradovat, v tomto příkladu z v0.33.0 na v0.34.0 a poté z v0.34.0 na v0.35.0. - **Ujistěte se, že máte po každém upgradu nepoškozenou zálohu.** ## Kroky upgradu specifické pro danou verzi -## v1.0 +## After v1.21 -Ahoj Twenty v1.0! 🎉 +We know support sequential upgrades. You don't need to go through each version one by one. -## v0.60 +## Before v1.21 -### Vylepšení výkonu - -Všechny interakce s metadatovým API byly optimalizovány pro lepší výkon, zejména pro manipulaci s metadaty objektů a operace vytváření pracovních prostorů. - -Refaktorovali jsme naši strategii mezipaměti tak, aby upřednostňovala, pokud je to možné, zásahy do mezipaměti před databázovými dotazy, což výrazně zlepšilo výkon operací rozhraní API metadat. - -Pokud po upgradu narazíte na problémy s výkonem, může být nutné vyprázdnit cache, aby bylo zajištěno její sladění s nejnovějšími změnami. Spusťte tento příkaz v kontejneru twenty-server: - -```bash -yarn command:prod cache:flush -``` - -### v0.55 - -Upgradujte svou instanci Twenty pro použití v0.55 image - -Už nemusíte spouštět žádný příkaz, nový obraz se automaticky postará o spuštění všech požadovaných migrací. - -### Chyba `Uživatel nemá oprávnění` - -Pokud po upgradu narazíte na chyby autorizace na většině požadavků, může být nutné vyprázdnit cache, abyste znovu provedli vyhodnocení nejnovějších oprávnění. - -Ve svém kontejneru `twenty-server` spusťte: - -```bash -yarn command:prod cache:flush -``` - -Tento problém je specifický pro tuto verzi Twenty a neměl by být vyžadován pro budoucí upgrady. - -### v0.54 - -Od verze `0.53`, nejsou nutné žádné manuální akce. - -#### Omezení metadatového schématu - -Sloučili jsme schéma `metadata` do `core`, abychom zjednodušili načítání dat z `TypeORM`. -Sloučili jsme krok příkazu `migrate` do příkazu `upgrade`. Nedoporučujeme ručně spouštět `migrate` v žádném z vašich kontejnerů server/worker. - -### Od v0.53 - -Od `0.53` je upgrade programově upraven v rámci `DockerFile`, což znamená, že od této chvíle již nemusíte ručně spouštět žádný příkaz. - -Ujistěte se, že upgradujete svou instanci postupně, aniž byste přeskočili hlavní verzi (např. `0.43.3` na `0.44.0` je povoleno, ale `0.43.1` na `0.45.0` nikoli), aby se předešlo asynchronizaci verzí pracovního prostoru, což by mohlo mít za následek chybu při běhu a chybějící funkčnost. - -Chcete-li zkontrolovat, zda byl pracovní prostor správně migrován, můžete zkontrolovat jeho verzi v databázi v tabulce `core.workspace`. - -Měla by se vždy nacházet v rozmezí vaší aktuální instance Twenty `major.minor` verze. Můžete zkontrolovat verzi instance na ovládacím panelu administrátora (na `/settings/admin-panel`, přístupné, pokud máte v databázi nastaveno uživatelské vlastnosti `canAccessFullAdminPanel` na true) nebo spuštěním `echo $APP_VERSION` ve vašem kontejneru `twenty-server`. - -Chcete-li opravit asynchronizaci verzí pracovního prostoru, budete muset upgradovat z odpovídající verze twenty podle souvisejícího průvodce upgradem po jednotlivých krocích, až do dosažení požadované verze. - -#### Odstranění `auditLog` - -Odstranili jsme standardní objekt auditLog, což znamená, že váš záložní soubor může být po této migraci výrazně zmenšen. - -### v0.51 až v0.52 - -Upgradujte svou instanci Twenty pro použití v0.52 image - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### Mám pracovní prostor zablokovaný ve verzi mezi `0.52.0` a `0.52.6` - -Bohužel `0.52.0` a `0.52.6` byly zcela odstraněny z dockerHub. -Budete muset ručně změnit verzi pracovního prostoru na `0.51.0` v databázi a upgradovat pomocí twenty verze `0.52.11` podle jejího výše uvedeného průvodce upgradem. - -### v0.50 až v0.51 - -Upgradujte svou instanci Twenty pro použití v0.51 image - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.44.0 až v0.50.0 - -Upgradujte svou instanci Twenty pro použití v0.50.0 image - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### Mutace Docker-compose.yml - -Tato verze obsahuje mutaci `docker-compose.yml`, která zajišťuje, že služba `worker` má přístup k objemu `server-local-data`. -Aktualizujte svůj místní `docker-compose.yml` pomocí [docker-compose.yml v0.50.0](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml) - -### v0.43.0 až v0.44.0 - -Upgradujte svou instanci Twenty pro použití v0.44.0 image - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.42.0 až v0.43.0 - -Upgradujte svou instanci Twenty pro použití v0.43.0 image - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -V této verzi jsme také přešli na obraz postgres:16 v docker-compose.yml. - -#### (Možnost 1) Migrace databáze - -Zachování existujícího obrazu postgres-spilo je v pořádku, ale budete muset zmrazit verzi v `docker-compose.yml` na 0.43.0. - -#### (Možnost 2) Migrace databáze - -Pokud chcete migrovat svou databázi na nový obraz postgres:16, postupujte podle těchto kroků: - -1. Zálohujte svou databázi z kontejneru postgres-spilo - -``` -docker exec -it twenty-db-1 sh -pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql -exit -docker cp twenty-db-1:/home/postgres/databases_backup.sql . -``` - -Ujistěte se, že váš záložní soubor není prázdný. - -2. Upgradujte svůj `docker-compose.yml` na použití obrazu postgres:16 podle [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) soubor. - -3. Obnovte databázi do nového kontejneru postgres:16 - -``` -docker cp databases_backup.sql twenty-db-1:/databases_backup.sql -docker exec -it twenty-db-1 sh -psql -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} -f databases_backup.sql -exit -``` - -### v0.41.0 až v0.42.0 - -Upgradujte svou instanci Twenty pro použití v0.42.0 image - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.42 -``` - -**Proměnné prostředí** - -* Odstraněno: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT` -* Přidáno: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED` - -### v0.40.0 až v0.41.0 - -Upgradujte svou instanci Twenty pro použití v0.41.0 image - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.41 -``` - -**Proměnné prostředí** - -* Odstraněno: `AUTH_MICROSOFT_TENANT_ID` - -### v0.35.0 až v0.40.0 - -Upgradujte svou instanci Twenty pro použití v0.40.0 image - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.40 -``` - -**Proměnné prostředí** - -* Přidáno: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL` - -### v0.34.0 až v0.35.0 - -Upgradujte svou instanci Twenty pro použití v0.35.0 image - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.35 -``` - -Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata) -Příkaz `yarn command:prod upgrade-0.35` se postará o datovou migraci všech pracovních míst. - -**Proměnné prostředí** - -* Nahradili jsme `ENABLE_DB_MIGRATIONS` s `DISABLE_DB_MIGRATIONS` (výchozí hodnota je nyní `false`, pravděpodobně nemusíte nastavovat nic) - -### v0.33.0 až v0.34.0 - -Upgradujte svou instanci Twenty pro použití v0.34.0 image - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.34 -``` - -Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata) -Příkaz `yarn command:prod upgrade-0.34` se postará o datovou migraci všech pracovních míst. - -**Proměnné prostředí** - -* Odstraněno: `FRONT_BASE_URL` -* Přidáno: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT` - -Aktualizovali jsme způsob, jakým zpracováváme frontend URL. -Nyní můžete nastavit frontend URL pomocí proměnných `FRONT_DOMAIN`, `FRONT_PROTOCOL` a `FRONT_PORT`. -Pokud není FRONT_DOMAIN nastavena, frontend URL se vrátí na `SERVER_URL`. - -### v0.32.0 až v0.33.0 - -Upgradujte svou instanci Twenty pro použití v0.33.0 image - -``` -yarn command:prod cache:flush -yarn database:migrate:prod -yarn command:prod upgrade-0.33 -``` - -Příkaz `yarn command:prod cache:flush` vyprázdní cache Redis. -Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata) -Příkaz `yarn command:prod upgrade-0.33` se postará o datovou migraci všech pracovních míst. - -Od této verze se obraz twenty-postgres pro DB stal zastaralým a místo něj se používá twenty-postgres-spilo. -Pokud chcete pokračovat v používání obrazu twenty-postgres, jednoduše nahraďte `twentycrm/twenty-postgres:${TAG}` za `twentycrm/twenty-postgres` v docker-compose.yml. - -### v0.31.0 až v0.32.0 - -Upgradujte svou instanci Twenty pro použití v0.32.0 image - -**Migrace schématu a dat** - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.32 -``` - -Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata) -Příkaz `yarn command:prod upgrade-0.32` se postará o datovou migraci všech pracovních míst. - -**Proměnné prostředí** - -Aktualizovali jsme způsob, jakým zpracováváme připojení k Redis. - -* Odstraněno: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD` -* Přidáno: `REDIS_URL` - -Aktualizujte svůj `.env` soubor tak, aby používal novou proměnnou `REDIS_URL` místo jednotlivých parametrů připojení k Redis. - -Také jsme zjednodušili způsob, jakým zpracováváme tokeny JWT. - -* Odstraněno: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET` -* Přidáno: `APP_SECRET` - -Aktualizujte svůj `.env` soubor tak, aby používal novou proměnnou `APP_SECRET` místo jednotlivých tokenů (můžete použít stejný tajný řetězec jako dříve nebo vygenerovat nový náhodný řetězec) - -**Propojený účet** - -Pokud používáte propojený účet k synchronizaci vašich emailů a kalendářů Google, budete muset aktivovat [People API](https://developers.google.com/people) na konzoli Google Admin. - -### v0.30.0 až v0.31.0 - -Upgradujte svou instanci Twenty pro použití v0.31.0 image - -**Migrace schématu a dat**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.31 -``` - -Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata) -Příkaz `yarn command:prod upgrade-0.31` se postará o datovou migraci všech pracovních míst. - -### v0.24.0 až v0.30.0 - -Upgradujte svou instanci Twenty pro použití v0.30.0 image - -**Změna, která způsobí nekompatibilitu**: -Pro zvýšení výkonu nyní vyžaduje Twenty konfiguraci cache Redis. Aktualizovali jsme náš [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml), aby to odrážel. -Ujistěte se, že jste aktualizovali svou konfiguraci a své proměnné prostředí odpovídajícím způsobem: - -``` -REDIS_HOST={váš-redis-host} -REDIS_PORT={váš-redis-port} -CACHE_STORAGE_TYPE=redis -``` - -**Migrace schématu a dat**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.30 -``` - -Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata) -Příkaz `yarn command:prod upgrade-0.30` se postará o datovou migraci všech pracovních míst. - -### v0.23.0 až v0.24.0 - -Upgradujte svou instanci Twenty pro použití v0.24.0 image - -Spusťte následující příkazy: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.24 -``` - -Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata) -Příkaz `yarn command:prod upgrade-0.24` se postará o datovou migraci všech pracovních míst. - -### v0.22.0 až v0.23.0 - -Upgradujte svou instanci Twenty pro použití v0.23.0 image - -Spusťte následující příkazy: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.23 -``` - -Příkaz `yarn database:migrate:prod` aplikuje změny na databázi. -Příkaz `yarn command:prod upgrade-0.23` se postará o datovou migraci, včetně přesunu aktivit na úkoly/poznámky. - -### v0.21.0 až v0.22.0 - -Upgradujte svou instanci Twenty pro použití v0.22.0 image - -Spusťte následující příkazy: - -``` -yarn database:migrate:prod -yarn command:prod workspace:sync-metadata -f -yarn command:prod upgrade-0.22 -``` - -Příkaz `yarn database:migrate:prod` aplikuje změny na databázi. -Příkaz `yarn command:prod workspace:sync-metadata -f` synchronizuje definici standardních objektů s tabulkami metadata a aplikuje nezbytné migrace na existující pracovní prostory. -Příkaz `yarn command:prod upgrade-0.22` aplikuje specifické datové transformace pro adaptaci na nové objektové defaultRequestInstrumentationOptions. +Make sure to go through every major tagged version when upgrading (upgrade v1.6.x to v.7.y, then v.7.y to v.8.z, etc.). diff --git a/packages/twenty-docs/l/cs/navigation.json b/packages/twenty-docs/l/cs/navigation.json index 5b8df0cb33c..8fae2ed1f7c 100644 --- a/packages/twenty-docs/l/cs/navigation.json +++ b/packages/twenty-docs/l/cs/navigation.json @@ -1,24 +1,27 @@ { "tabs": { + "gettingStarted": { + "label": "Začínáme", + "groups": { + "welcome": { + "label": "Welcome" + }, + "coreConcepts": { + "label": "Core Concepts" + } + } + }, "userGuide": { "label": "Uživatelská příručka", "groups": { - "discoverTwenty": { - "label": "Objevte Twenty", - "groups": { - "gettingStartedCapabilities": { - "label": "Možnosti" - }, - "gettingStartedHowTos": { - "label": "Návody" - } - } + "userGuideOverview": { + "label": "Přehled" }, "dataModel": { "label": "Datový model", "groups": { - "dataModelCapabilities": { - "label": "Možnosti" + "dataModelReference": { + "label": "Reference" }, "dataModelHowTos": { "label": "Návody" @@ -28,8 +31,8 @@ "dataMigration": { "label": "Migrace dat", "groups": { - "dataMigrationCapabilities": { - "label": "Možnosti" + "dataMigrationReference": { + "label": "Reference" }, "dataMigrationHowTos": { "label": "Návody" @@ -39,8 +42,8 @@ "calendarEmails": { "label": "Kalendář a e-maily", "groups": { - "calendarEmailsCapabilities": { - "label": "Možnosti" + "calendarEmailsReference": { + "label": "Reference" }, "calendarEmailsHowTos": { "label": "Návody" @@ -50,8 +53,8 @@ "workflows": { "label": "Pracovní postupy", "groups": { - "workflowsCapabilities": { - "label": "Možnosti" + "workflowsReference": { + "label": "Reference" }, "workflowsHowTos": { "label": "Návody", @@ -75,21 +78,26 @@ "ai": { "label": "AI", "groups": { - "aiCapabilities": { - "label": "Možnosti" + "aiReference": { + "label": "Reference" }, "aiHowTos": { "label": "Návody" } } }, - "viewsPipelines": { - "label": "Zobrazení a pipeline", + "layout": { + "label": "Rozvržení", "groups": { - "viewsPipelinesCapabilities": { - "label": "Možnosti" + "layoutReference": { + "label": "Reference", + "groups": { + "layoutViews": { + "label": "Zobrazení" + } + } }, - "viewsPipelinesHowTos": { + "layoutHowTos": { "label": "Návody" } } @@ -97,8 +105,8 @@ "dashboards": { "label": "Panely", "groups": { - "dashboardsCapabilities": { - "label": "Možnosti" + "dashboardsReference": { + "label": "Reference" }, "dashboardsHowTos": { "label": "Návody" @@ -108,8 +116,8 @@ "permissionsAccess": { "label": "Oprávnění a přístup", "groups": { - "permissionsAccessCapabilities": { - "label": "Možnosti" + "permissionsAccessReference": { + "label": "Reference" }, "permissionsAccessHowTos": { "label": "Návody" @@ -119,8 +127,8 @@ "billing": { "label": "Fakturace", "groups": { - "billingCapabilities": { - "label": "Možnosti" + "billingReference": { + "label": "Reference" }, "billingHowTos": { "label": "Návody" @@ -130,8 +138,8 @@ "settings": { "label": "Nastavení", "groups": { - "settingsCapabilities": { - "label": "Možnosti" + "settingsReference": { + "label": "Reference" }, "settingsHowTos": { "label": "Návody" @@ -143,59 +151,20 @@ "developers": { "label": "Vývojáři", "groups": { - "developersGroup": { - "label": "Vývojáři" + "developersOverview": { + "label": "Přehled" }, - "extend": { - "label": "Rozšíření", - "groups": { - "apps": { - "label": "Aplikace" - } - } + "apps": { + "label": "Aplikace" + }, + "api": { + "label": "API" }, "selfHost": { - "label": "Vlastní hosting", - "groups": { - "selfHostCapabilities": { - "label": "Možnosti" - } - } + "label": "Vlastní hosting" }, "contribute": { - "label": "Přispět", - "groups": { - "contributeCapabilities": { - "label": "Možnosti", - "groups": { - "frontendDevelopment": { - "label": "Vývoj frontendu", - "groups": { - "twentyUi": { - "label": "Twenty UI", - "groups": { - "display": { - "label": "Zobrazit" - }, - "feedback": { - "label": "Zpětná vazba" - }, - "input": { - "label": "Vstup" - }, - "navigation": { - "label": "Navigace" - } - } - } - } - }, - "backendDevelopment": { - "label": "Vývoj backendu" - } - } - } - } + "label": "Přispět" } } } diff --git a/packages/twenty-docs/l/cs/twenty-ui/display/app-tooltip.mdx b/packages/twenty-docs/l/cs/twenty-ui/display/app-tooltip.mdx index 2841e8e2a41..36c792fae0a 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/display/app-tooltip.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/display/app-tooltip.mdx @@ -1,5 +1,6 @@ --- title: Tooltip aplikace +icon: zpráva --- diff --git a/packages/twenty-docs/l/cs/twenty-ui/display/checkmark.mdx b/packages/twenty-docs/l/cs/twenty-ui/display/checkmark.mdx index d1b182b8f08..3fbf05a8628 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/display/checkmark.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/display/checkmark.mdx @@ -1,5 +1,6 @@ --- title: Zaškrtnutí +icon: circle-check --- diff --git a/packages/twenty-docs/l/cs/twenty-ui/display/icons.mdx b/packages/twenty-docs/l/cs/twenty-ui/display/icons.mdx index c80d94bcc95..0693167ef7b 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/display/icons.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/display/icons.mdx @@ -1,5 +1,6 @@ --- title: Ikony +icon: ikony --- diff --git a/packages/twenty-docs/l/cs/twenty-ui/display/soon-pill.mdx b/packages/twenty-docs/l/cs/twenty-ui/display/soon-pill.mdx index 026193c3f3b..dd78a2d7e7d 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/display/soon-pill.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/display/soon-pill.mdx @@ -2,7 +2,6 @@ title: Štítek „Brzy“ --- - Malý odznak nebo "pilulka" označující, že něco brzy přijde. ```jsx diff --git a/packages/twenty-docs/l/cs/twenty-ui/display/tag.mdx b/packages/twenty-docs/l/cs/twenty-ui/display/tag.mdx index f5f48add352..7da0b279e55 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/display/tag.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/display/tag.mdx @@ -1,8 +1,8 @@ --- title: Štítek +icon: štítek --- - Komponenta pro vizuální kategorizaci nebo označení obsahu. diff --git a/packages/twenty-docs/l/cs/twenty-ui/input/buttons.mdx b/packages/twenty-docs/l/cs/twenty-ui/input/buttons.mdx index a44975c3ef1..355a833f83d 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/input/buttons.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/input/buttons.mdx @@ -1,5 +1,6 @@ --- title: Tlačítka +icon: hand-pointer --- diff --git a/packages/twenty-docs/l/cs/twenty-ui/input/checkbox.mdx b/packages/twenty-docs/l/cs/twenty-ui/input/checkbox.mdx index 6c39026d726..82b0e5c7ec1 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/input/checkbox.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/input/checkbox.mdx @@ -1,5 +1,6 @@ --- title: Zaškrtávací políčko +icon: square-check --- diff --git a/packages/twenty-docs/l/cs/twenty-ui/input/color-scheme.mdx b/packages/twenty-docs/l/cs/twenty-ui/input/color-scheme.mdx index 3385bc81f0a..36c9d763349 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/input/color-scheme.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/input/color-scheme.mdx @@ -1,5 +1,6 @@ --- title: Barevné schéma +icon: paleta --- diff --git a/packages/twenty-docs/l/cs/twenty-ui/input/radio.mdx b/packages/twenty-docs/l/cs/twenty-ui/input/radio.mdx index d9a69c1e7e7..3cdfb366563 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/input/radio.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/input/radio.mdx @@ -1,5 +1,6 @@ --- title: Rádio +icon: circle-dot --- diff --git a/packages/twenty-docs/l/cs/twenty-ui/input/toggle.mdx b/packages/twenty-docs/l/cs/twenty-ui/input/toggle.mdx index 4e4ea9b6eb0..e2b3a52bbe5 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/input/toggle.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/input/toggle.mdx @@ -1,8 +1,8 @@ --- title: Přepínač +icon: toggle-on --- - diff --git a/packages/twenty-docs/l/cs/twenty-ui/introduction.mdx b/packages/twenty-docs/l/cs/twenty-ui/introduction.mdx index df8ce354a11..74bc8da810d 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/introduction.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/introduction.mdx @@ -1,5 +1,6 @@ --- title: Přehled +icon: paleta description: Knihovna komponent pro Twenty CRM --- diff --git a/packages/twenty-docs/l/cs/twenty-ui/navigation.mdx b/packages/twenty-docs/l/cs/twenty-ui/navigation.mdx index e6126f020ce..0d25e8a2e71 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/navigation.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/navigation.mdx @@ -1,5 +1,6 @@ --- title: Navigace +icon: compass --- diff --git a/packages/twenty-docs/l/cs/twenty-ui/navigation/links.mdx b/packages/twenty-docs/l/cs/twenty-ui/navigation/links.mdx index a08beec9925..12c9bf2f644 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/navigation/links.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/navigation/links.mdx @@ -1,5 +1,6 @@ --- title: Odkazy +icon: odkaz --- diff --git a/packages/twenty-docs/l/cs/twenty-ui/navigation/menu-item.mdx b/packages/twenty-docs/l/cs/twenty-ui/navigation/menu-item.mdx index e96feb4054e..5e4884c9b7d 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/navigation/menu-item.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/navigation/menu-item.mdx @@ -1,8 +1,8 @@ --- title: Položka menu +icon: bars --- - Univerzální položka menu navržená k použití v menu nebo navigačním seznamu. diff --git a/packages/twenty-docs/l/cs/twenty-ui/navigation/navigation-bar.mdx b/packages/twenty-docs/l/cs/twenty-ui/navigation/navigation-bar.mdx index e4dddaf779b..c556ecec7bc 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/navigation/navigation-bar.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/navigation/navigation-bar.mdx @@ -1,8 +1,8 @@ --- title: Navigační panel +icon: bars --- - Zobrazuje navigační panel, který obsahuje více komponent `NavigationBarItem`. diff --git a/packages/twenty-docs/l/cs/twenty-ui/progress-bar.mdx b/packages/twenty-docs/l/cs/twenty-ui/progress-bar.mdx index 439da2d2bc4..081168a9472 100644 --- a/packages/twenty-docs/l/cs/twenty-ui/progress-bar.mdx +++ b/packages/twenty-docs/l/cs/twenty-ui/progress-bar.mdx @@ -2,7 +2,6 @@ title: Zpětná vazba --- - Udává průběh nebo odpočítávání a pohybuje se zprava doleva. diff --git a/packages/twenty-docs/l/cs/user-guide/billing/overview.mdx b/packages/twenty-docs/l/cs/user-guide/billing/overview.mdx index abec376ae78..37b51f55b99 100644 --- a/packages/twenty-docs/l/cs/user-guide/billing/overview.mdx +++ b/packages/twenty-docs/l/cs/user-guide/billing/overview.mdx @@ -3,7 +3,6 @@ title: Fakturace description: Seznamte se s cenami Twenty a spravujte své předplatné. --- - Twenty nabízí flexibilní cenové plány, které vyhoví potřebám vašeho týmu. Spravujte své předplatné, sledujte kredity pracovních postupů a přistupujte k fakturám — to vše v **Nastavení → Fakturace**. ## Co najdete v této sekci diff --git a/packages/twenty-docs/l/cs/user-guide/calendar-emails/overview.mdx b/packages/twenty-docs/l/cs/user-guide/calendar-emails/overview.mdx index af55521f5fa..5c2cc77b01f 100644 --- a/packages/twenty-docs/l/cs/user-guide/calendar-emails/overview.mdx +++ b/packages/twenty-docs/l/cs/user-guide/calendar-emails/overview.mdx @@ -3,7 +3,6 @@ title: Kalendář a e-maily description: Připojte své účty e-mailu a kalendáře k Twenty. --- - ## Možnosti Připojení ### Účet Google (Gmail & Google Kalendář) diff --git a/packages/twenty-docs/l/cs/user-guide/dashboards/overview.mdx b/packages/twenty-docs/l/cs/user-guide/dashboards/overview.mdx index 5c1800aaf0b..5016848fea0 100644 --- a/packages/twenty-docs/l/cs/user-guide/dashboards/overview.mdx +++ b/packages/twenty-docs/l/cs/user-guide/dashboards/overview.mdx @@ -3,7 +3,6 @@ title: Panely description: Naučte se základy vytváření sestav a řídicích panelů v Twenty. --- - Řídicí panely jsou momentálně v beta verzi. Aktivujte je v **Nastavení → Aktualizace → Předběžný přístup**. diff --git a/packages/twenty-docs/l/cs/user-guide/data-migration/overview.mdx b/packages/twenty-docs/l/cs/user-guide/data-migration/overview.mdx index 1b9d23fbaed..58fac58efb7 100644 --- a/packages/twenty-docs/l/cs/user-guide/data-migration/overview.mdx +++ b/packages/twenty-docs/l/cs/user-guide/data-migration/overview.mdx @@ -5,7 +5,6 @@ description: Importujte a exportujte svá data CRM pomocí souborů CSV nebo roz import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## Metody importu Twenty podporuje dvě hlavní metody pro import dat: diff --git a/packages/twenty-docs/l/cs/user-guide/data-model/overview.mdx b/packages/twenty-docs/l/cs/user-guide/data-model/overview.mdx index 64db9a4b161..d0ec007a09b 100644 --- a/packages/twenty-docs/l/cs/user-guide/data-model/overview.mdx +++ b/packages/twenty-docs/l/cs/user-guide/data-model/overview.mdx @@ -3,7 +3,6 @@ title: Datový model description: Zjistěte, co je datový model a jak navrhnout takový, který bude vyhovovat vašemu podnikání. --- - ## Co je to datový model? Datový model je struktura, která definuje, jak jsou informace organizovány ve vašem CRM. Představte si ho jako **plán** vašich zákaznických dat — navrhnete ho jednou a potom ho naplníte skutečnými daty. diff --git a/packages/twenty-docs/l/cs/user-guide/introduction.mdx b/packages/twenty-docs/l/cs/user-guide/introduction.mdx index ff07f1c17d7..074547c2f0a 100644 --- a/packages/twenty-docs/l/cs/user-guide/introduction.mdx +++ b/packages/twenty-docs/l/cs/user-guide/introduction.mdx @@ -1,16 +1,11 @@ --- -title: Objevte Twenty +title: Uživatelská příručka description: Vítejte v uživatelské příručce Twenty, která je vaším zdrojem pokročilých konfigurací a osvědčených postupů. --- import { CardTitle } from "/snippets/card-title.mdx" - - Objevte Twenty - Zjistěte, co je Twenty a jak může pomoci vašemu podnikání. - - Datový model Přizpůsobte svůj datový model tak, aby odpovídal vašim obchodním procesům. @@ -36,9 +31,9 @@ import { CardTitle } from "/snippets/card-title.mdx" Posilte svůj tým pomocí agentů AI. - - Zobrazení a pipeline - Uspořádejte svá data pomocí praktických zobrazení a pipeline. + + Layout + Navigation, views, and record page customization. diff --git a/packages/twenty-docs/l/cs/user-guide/layout/capabilities/navigation.mdx b/packages/twenty-docs/l/cs/user-guide/layout/capabilities/navigation.mdx new file mode 100644 index 00000000000..a3489721115 --- /dev/null +++ b/packages/twenty-docs/l/cs/user-guide/layout/capabilities/navigation.mdx @@ -0,0 +1,32 @@ +--- +title: Navigace +description: Customize the left sidebar to match how your team works. +--- + +The left sidebar is your primary way to move around Twenty. It's fully customizable — you can reorganize it to match your workflow without touching any settings page. + +## Reordering items + +Drag and drop any item in the sidebar to change its position. The order is saved per user, so each team member can arrange their own sidebar. + +## Složky + +Group related items into folders. For example, you might create a "Sales" folder containing your pipeline views, a "Support" folder for tickets, or an "Operations" folder for internal objects. + +To create a folder, right-click in the sidebar or use the `+` button. + +## Hiding objects + +Objects you don't use can be hidden from the sidebar. They're not deleted — they're just out of the way. You can show them again anytime from Settings > Data Model. + +## Oblíbené + +Pin views, records, or searches to the Favorites section at the top of the sidebar for one-click access. Favorites are personal — each user manages their own. + +## Custom links + +Add links to external tools directly in the sidebar. Useful for linking to your wiki, dashboards in other tools, or any URL your team uses regularly. + +## Command menu + +Press `Cmd+K` (or `Ctrl+K`) to open the command menu — a quick-access search bar for jumping to any record, view, or action without navigating the sidebar. diff --git a/packages/twenty-docs/l/cs/user-guide/layout/capabilities/record-pages.mdx b/packages/twenty-docs/l/cs/user-guide/layout/capabilities/record-pages.mdx new file mode 100644 index 00000000000..d43d311df9b --- /dev/null +++ b/packages/twenty-docs/l/cs/user-guide/layout/capabilities/record-pages.mdx @@ -0,0 +1,51 @@ +--- +title: Záznamové stránky},{ +description: Přizpůsobte rozvržení jednotlivých stránek detailu záznamu pomocí karet a widgetů. +--- + +Když v Twenty otevřete záznam, stránka detailu se skládá z **karet** a **widgetů**. Obojí lze pro každý typ objektu plně přizpůsobit. + +## Karty + +Každá stránka záznamu může mít více karet — podobně jako karty v prohlížeči. Použijte je k uspořádání různých aspektů záznamu. Například záznam typu Společnost může mít karty Přehled, Komunikace, Úkoly a Soubory. + +Můžete: + +* Přidat a odebrat karty +* Přejmenovat karty +* Změnit pořadí karet tažením +* Nastavit, která karta se zobrazuje ve výchozím stavu + +## Widgety + +Widgety jsou stavební prvky uvnitř každé karty. Mezi dostupné typy widgetů patří: + +| Widget | Co zobrazuje | +| ----------------------- | -------------------------------------------------- | +| **Pole** | Pole záznamu, seskupená nebo jednotlivě | +| **Související záznamy** | Tabulka záznamů propojených prostřednictvím relace | +| **E-maily** | Historie e-mailů z připojených účtů | +| **Kalendář** | Kalendářní události spojené se záznamem | +| **Časová osa** | Historie aktivit a událostí | +| **Úkoly** | Související úkoly | +| **Poznámky** | Poznámky s formátovaným textem | +| **Soubory** | Přílohy souborů | +| **Grafy** | Vizuální data ze souvisejících záznamů | +| **iFrame** | Vložený externí obsah | +| **Formátovaný text** | Statický obsah nebo popisy | + +## Přizpůsobení stránky záznamu + +1. Otevřete libovolný záznam +2. Stiskněte `Cmd+K` a vyhledejte "Upravit rozložení stránky záznamu" +3. Nyní jste v režimu přizpůsobení: + * **Přidávejte widgety** z výběru widgetů + * **Přetahujte widgety** pro jejich přemístění v mřížce + * **Změňte velikost widgetů** tažením jejich okrajů + * **Nakonfigurujte pole** zobrazovaná v jednotlivých widgetech + * **Spravujte karty** — přidávejte, odebírejte, přejmenovávejte, měňte pořadí +4. Uložte změny — budou platit pro všechny záznamy daného typu objektu + +## Viditelnost polí + +Ve widgetu Pole můžete řídit, která pole jsou viditelná a v jakém pořadí. To vám umožní vytvářet cílená rozložení — například zobrazit na kartě Přehled pouze nejdůležitější pole a podrobnější pole umístit do samostatné karty. diff --git a/packages/twenty-docs/l/cs/user-guide/layout/overview.mdx b/packages/twenty-docs/l/cs/user-guide/layout/overview.mdx new file mode 100644 index 00000000000..14281b3c9f0 --- /dev/null +++ b/packages/twenty-docs/l/cs/user-guide/layout/overview.mdx @@ -0,0 +1,45 @@ +--- +title: Rozvržení +description: Customize how you navigate, browse, and view records in Twenty. +--- + +Twenty's layout is customizable at three levels: how you navigate the app, how you browse lists of records, and what you see when you open an individual record. + +## Navigace + +The left sidebar is fully customizable. Můžete: + +* **Reorder items** by dragging and dropping +* **Create folders** to group related objects and views +* **Hide objects** you don't use +* **Add custom links** to external tools +* **Pin favorites** for quick access to views, records, or searches + +[Navigation reference →](/l/cs/user-guide/layout/capabilities/navigation) + +## Zobrazení + +Views control how lists of records are displayed. Twenty supports three view types: + +| Pohled | Best for | +| ------------ | ---------------------------------------------------------------------- | +| **Table** | Working with many records at once — spreadsheet-style rows and columns | +| **Kanban** | Pipeline tracking — drag-and-drop cards organized by stage | +| **Calendar** | Time-based planning — records plotted by a date field | + +Each view saves its own filters, sorting, field visibility, and grouping configuration. Views can be shared with the workspace or kept private. + +[Table views →](/l/cs/user-guide/views-pipelines/capabilities/table-views) · [Kanban views →](/l/cs/user-guide/views-pipelines/capabilities/kanban-views) · [Calendar view →](/l/cs/user-guide/views-pipelines/capabilities/calendar-view) + +## Record pages + +When you open a record, the detail page is built from configurable tabs and widgets. Můžete: + +* **Add, remove, and reorder tabs** on any record type +* **Configure widgets** — fields, related records, emails, timeline, calendar, tasks, notes, files, charts, iframes, and more +* **Drag and resize widgets** on a grid layout +* **Control field visibility** per widget + +Enter layout customization mode from the command menu (`Cmd+K` → "Edit record page layout"). + +[Record pages reference →](/l/cs/user-guide/layout/capabilities/record-pages) diff --git a/packages/twenty-docs/l/cs/user-guide/permissions-access/overview.mdx b/packages/twenty-docs/l/cs/user-guide/permissions-access/overview.mdx index 624c9e58e5d..10b3a4637e6 100644 --- a/packages/twenty-docs/l/cs/user-guide/permissions-access/overview.mdx +++ b/packages/twenty-docs/l/cs/user-guide/permissions-access/overview.mdx @@ -3,7 +3,6 @@ title: Oprávnění a přístup description: Spravujte role, oprávnění a řízení přístupu ve svém pracovním prostoru. --- - Systém oprávnění Twenty vám umožňuje řídit, kdo může ve vašem pracovním prostoru přistupovat k datům a kdo je může upravovat. Vytvářejte role, přiřazujte oprávnění a konfigurujte SSO pro zabezpečený přístup. ## Co je v této sekci diff --git a/packages/twenty-docs/l/cs/user-guide/settings/overview.mdx b/packages/twenty-docs/l/cs/user-guide/settings/overview.mdx index 0d426afe940..2adc82d1698 100644 --- a/packages/twenty-docs/l/cs/user-guide/settings/overview.mdx +++ b/packages/twenty-docs/l/cs/user-guide/settings/overview.mdx @@ -3,7 +3,6 @@ title: Nastavení description: Nastavte svůj pracovní prostor Twenty pomocí klíčových nastavení. --- - ## Úvodní nastavení Když poprvé vytvoříte svůj pracovní prostor, je třeba nakonfigurovat několik klíčových nastavení. diff --git a/packages/twenty-docs/l/cs/user-guide/views-pipelines/overview.mdx b/packages/twenty-docs/l/cs/user-guide/views-pipelines/overview.mdx index 28fb4c6c979..7780b06b290 100644 --- a/packages/twenty-docs/l/cs/user-guide/views-pipelines/overview.mdx +++ b/packages/twenty-docs/l/cs/user-guide/views-pipelines/overview.mdx @@ -5,7 +5,6 @@ description: Zjistěte, jak v Twenty vytvářet a spravovat zobrazení. import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## Porozumění zobrazením Zobrazení jsou uložená nastavení, která určují, jak se vaše data zobrazují. Každé zobrazení může mít vlastní: diff --git a/packages/twenty-docs/l/cs/user-guide/workflows/overview.mdx b/packages/twenty-docs/l/cs/user-guide/workflows/overview.mdx index 47aec131822..860ee2aaf3f 100644 --- a/packages/twenty-docs/l/cs/user-guide/workflows/overview.mdx +++ b/packages/twenty-docs/l/cs/user-guide/workflows/overview.mdx @@ -3,7 +3,6 @@ title: Pracovní postupy description: Naučte se vytvářet automatizace v Twenty. --- - ## Proč na Workflows záleží Twenty bylo vytvořeno, aby svým uživatelům přineslo maximální flexibilitu. Namísto toho, abyste byli nuceni přizpůsobovat vaše obchodní procesy rigidním, předem připraveným funkcím, vám Workflows umožňují vytvářet automatizace, které vytvářejí CRM, které nejlépe podporuje vaše jedinečné obchodní potřeby. diff --git a/packages/twenty-docs/l/de/developers/contribute/capabilities/backend-development/server-commands.mdx b/packages/twenty-docs/l/de/developers/contribute/capabilities/backend-development/server-commands.mdx index 91bde2d596e..59a805c40a5 100644 --- a/packages/twenty-docs/l/de/developers/contribute/capabilities/backend-development/server-commands.mdx +++ b/packages/twenty-docs/l/de/developers/contribute/capabilities/backend-development/server-commands.mdx @@ -1,5 +1,6 @@ --- title: Backend Befehle +icon: terminal --- ## Nützliche Befehle diff --git a/packages/twenty-docs/l/de/developers/contribute/capabilities/bug-and-requests.mdx b/packages/twenty-docs/l/de/developers/contribute/capabilities/bug-and-requests.mdx index 3af9fe9ba4f..a7432b42d12 100644 --- a/packages/twenty-docs/l/de/developers/contribute/capabilities/bug-and-requests.mdx +++ b/packages/twenty-docs/l/de/developers/contribute/capabilities/bug-and-requests.mdx @@ -1,5 +1,6 @@ --- title: Fehlermeldungen, Anfragen & Pull Requests +icon: bug info: Probleme melden, Funktionen vorschlagen und Code beisteuern --- diff --git a/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/best-practices-front.mdx b/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/best-practices-front.mdx index 4defebae251..28895362727 100644 --- a/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/best-practices-front.mdx +++ b/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/best-practices-front.mdx @@ -1,5 +1,6 @@ --- title: Beste Praktiken +icon: star --- Dieses Dokument beschreibt die besten Praktiken, die Sie beim Arbeiten am Frontend beachten sollten. diff --git a/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx b/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx index 2e5f2153b68..3125ac4a737 100644 --- a/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx +++ b/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx @@ -1,5 +1,6 @@ --- title: Ordnerarchitektur +icon: folder-tree info: Ein detaillierter Einblick in unsere Ordnerarchitektur --- diff --git a/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/frontend-commands.mdx b/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/frontend-commands.mdx index 08ff3464231..c870ac12739 100644 --- a/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/frontend-commands.mdx +++ b/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/frontend-commands.mdx @@ -1,5 +1,6 @@ --- title: Frontend-Befehle +icon: terminal --- ## Nützliche Befehle diff --git a/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/style-guide.mdx b/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/style-guide.mdx index af487216a97..8879863604b 100644 --- a/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/style-guide.mdx +++ b/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/style-guide.mdx @@ -1,5 +1,6 @@ --- title: Styleguide +icon: paintbrush --- Dieses Dokument enthält die Regeln, die beim Schreiben von Code beachtet werden müssen. diff --git a/packages/twenty-docs/l/de/developers/contribute/capabilities/local-setup.mdx b/packages/twenty-docs/l/de/developers/contribute/capabilities/local-setup.mdx index 21f80edc34c..cb61770e848 100644 --- a/packages/twenty-docs/l/de/developers/contribute/capabilities/local-setup.mdx +++ b/packages/twenty-docs/l/de/developers/contribute/capabilities/local-setup.mdx @@ -1,5 +1,6 @@ --- title: Lokale Einrichtung +icon: laptop-code description: Der Leitfaden für Mitwirkende (oder neugierige Entwickler), die Twenty lokal ausführen möchten. --- diff --git a/packages/twenty-docs/l/de/developers/contribute/commands.mdx b/packages/twenty-docs/l/de/developers/contribute/commands.mdx new file mode 100644 index 00000000000..c995623dc7a --- /dev/null +++ b/packages/twenty-docs/l/de/developers/contribute/commands.mdx @@ -0,0 +1,77 @@ +--- +title: Commands +icon: terminal +description: Useful commands for developing Twenty. +--- + +Commands can be run from the repository root using `npx nx`. Use `npx nx run {project}:{command}` for explicit targeting. + +## Starting the App + +```bash +npx nx start twenty-front # Frontend dev server (http://localhost:3001) +npx nx start twenty-server # Backend server (http://localhost:3000) +npx nx run twenty-server:worker # Background worker +``` + +## Database + +```bash +npx nx database:reset twenty-server # Reset and seed database +npx nx run twenty-server:database:migrate:prod # Run migrations +npx nx run twenty-server:database:migrate:generate --name --type # Generate a migration +``` + +## Linting + +```bash +npx nx lint:diff-with-main twenty-front # Lint changed files (fastest) +npx nx lint:diff-with-main twenty-server +npx nx lint twenty-front --configuration=fix # Auto-fix +``` + +## Type Checking + +```bash +npx nx typecheck twenty-front +npx nx typecheck twenty-server +``` + +## Tests + +```bash +# Frontend +npx nx test twenty-front # Jest unit tests +npx nx storybook:build twenty-front # Build Storybook +npx nx storybook:test twenty-front # Storybook tests + +# Backend +npx nx run twenty-server:test:unit # Unit tests +npx nx run twenty-server:test:integration # Integration tests +npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset + +# Single file (fastest) +npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs +``` + +## GraphQL + +```bash +npx nx run twenty-front:graphql:generate # Regenerate types +npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema +``` + +## Übersetzungen + +```bash +npx nx run twenty-front:lingui:extract # Extract strings +npx nx run twenty-front:lingui:compile # Compile translations +``` + +## Build + +```bash +npx nx build twenty-shared # Must be built first +npx nx build twenty-front +npx nx build twenty-server +``` diff --git a/packages/twenty-docs/l/de/developers/contribute/style-guide.mdx b/packages/twenty-docs/l/de/developers/contribute/style-guide.mdx new file mode 100644 index 00000000000..e919303f157 --- /dev/null +++ b/packages/twenty-docs/l/de/developers/contribute/style-guide.mdx @@ -0,0 +1,176 @@ +--- +title: Styleguide +icon: paintbrush +description: Code conventions and best practices for contributing to Twenty. +--- + +## React + +### Functional components only + +Always use TSX functional components with named exports. + +```tsx +// ❌ Bad +const MyComponent = () => { + return
Hello World
; +}; +export default MyComponent; + +// ✅ Good +export function MyComponent() { + return
Hello World
; +}; +``` + +### Props + +Create a type named `{ComponentName}Props`. Use destructuring. Don't use `React.FC`. + +```tsx +type MyComponentProps = { + name: string; +}; + +export const MyComponent = ({ name }: MyComponentProps) =>
Hello {name}
; +``` + +### No single-variable prop spreading + +```tsx +// ❌ Bad +const MyComponent = (props: MyComponentProps) => ; + +// ✅ Good +const MyComponent = ({ prop1, prop2 }: MyComponentProps) => ; +``` + +## Zustandsverwaltung + +### Jotai atoms for global state + +```tsx +import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; +import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; + +export const myAtomState = createAtomState({ + key: 'myAtomState', + defaultValue: 'default value', +}); +``` + +* Prefer atoms over prop drilling +* Don't use `useRef` for state — use `useState` or atoms +* Use atom families and selectors for lists + +### Avoid unnecessary re-renders + +* Extract `useEffect` and data fetching into sibling sidecar components +* Prefer event handlers (`handleClick`, `handleChange`) over `useEffect` +* Don't use `React.memo()` — fix the root cause instead +* Limit `useCallback` / `useMemo` usage + +```tsx +// ❌ Bad — useEffect in the same component causes re-renders +export const Page = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return
{data}
; +}; + +// ✅ Good — extract into sibling +export const PageData = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return <>; +}; +export const Page = () => { + const [data] = useAtomState(dataState); + return
{data}
; +}; +``` + +## TypeScript + +* **`type` over `interface`** — more flexible, easier to compose +* **String literals over enums** — except for GraphQL codegen enums and internal library APIs +* **No `any`** — strict TypeScript enforced +* **No type imports** — use regular imports (enforced by Oxlint `typescript/consistent-type-imports`) +* **Use [Zod](https://github.com/colinhacks/zod)** for runtime validation of untyped objects + +## JavaScript + +```tsx +// Use nullish-coalescing (??) instead of || +const value = process.env.MY_VALUE ?? 'default'; + +// Use optional chaining +onClick?.(); +``` + +## Namensgebung + +* **Variables**: camelCase, descriptive (`email` not `value`, `fieldMetadata` not `fm`) +* **Constants**: SCREAMING_SNAKE_CASE +* **Types/Classes**: PascalCase +* **Files/directories**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`) +* **Event handlers**: `handleClick` (not `onClick` for the handler function) +* **Component props**: prefix with component name (`ButtonProps`) +* **Styled components**: prefix with `Styled` (`StyledTitle`) + +## Styling + +Use [Linaria](https://github.com/callstack/linaria) styled components. Use theme values — avoid hardcoded `px`, `rem`, or colors. + +```tsx +// ❌ Bad +const StyledButton = styled.button` + color: #333333; + font-size: 1rem; + margin-left: 4px; +`; + +// ✅ Good +const StyledButton = styled.button` + color: ${({ theme }) => theme.font.color.primary}; + font-size: ${({ theme }) => theme.font.size.md}; + margin-left: ${({ theme }) => theme.spacing(1)}; +`; +``` + +## Importe + +Use aliases instead of relative paths: + +```tsx +// ❌ Bad +import { Foo } from '../../../../../testing/decorators/Foo'; + +// ✅ Good +import { Foo } from '~/testing/decorators/Foo'; +import { Bar } from '@/modules/bar/components/Bar'; +``` + +## Folder Structure + +``` +front +└── modules/ # Feature modules +│ └── module1/ +│ ├── components/ +│ ├── constants/ +│ ├── contexts/ +│ ├── graphql/ (fragments, queries, mutations) +│ ├── hooks/ +│ ├── states/ (atoms, selectors) +│ ├── types/ +│ └── utils/ +└── pages/ # Route-level components +└── ui/ # Reusable UI components (display, input, feedback, ...) +``` + +* Modules can import from other modules, but `ui/` should stay dependency-free +* Use `internal/` subfolders for module-private code +* Components under 300 lines, services under 500 lines diff --git a/packages/twenty-docs/l/de/developers/extend/api.mdx b/packages/twenty-docs/l/de/developers/extend/api.mdx index 4b6aa0b8ec0..07c09cfad82 100644 --- a/packages/twenty-docs/l/de/developers/extend/api.mdx +++ b/packages/twenty-docs/l/de/developers/extend/api.mdx @@ -1,147 +1,55 @@ --- title: APIs -description: Abfragen und ändern Sie Ihre CRM-Daten programmatisch mit REST oder GraphQL. +icon: plug +description: REST and GraphQL APIs generated from your workspace schema. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -Twenty wurde so entwickelt, dass es entwicklerfreundlich ist und leistungsstarke APIs bietet, die sich an Ihr individuelles Datenmodell anpassen. Wir bieten vier verschiedene API-Typen, um unterschiedlichen Integrationsanforderungen gerecht zu werden. +## Schema-per-tenant APIs -## Developer-First-Ansatz +There is no static API reference for Twenty. Each workspace has its own schema — when you add a custom object (say `Invoice`), it immediately gets REST and GraphQL endpoints identical to built-in objects like `Company` or `Person`. The API is generated from the schema, so endpoints use your object and field names directly — no opaque IDs. -Twenty generiert APIs speziell für Ihr Datenmodell: +Your workspace-specific API documentation is available under **Settings → API & Webhooks** after creating an API key. It includes an interactive playground where you can execute real calls against your data. -* **Keine langen IDs erforderlich**: Verwenden Sie Ihre Objekt- und Feldnamen direkt in Endpunkten -* **Standard- und benutzerdefinierte Objekte werden gleich behandelt**: Ihre benutzerdefinierten Objekte erhalten dieselbe API-Behandlung wie integrierte Objekte -* **Dedizierte Endpunkte**: Jedes Objekt und Feld erhält seinen eigenen API-Endpunkt -* **Benutzerdefinierte Dokumentation**: Speziell für das Datenmodell Ihres Arbeitsbereichs generiert +## Two APIs - -Ihre personalisierte API-Dokumentation ist nach dem Erstellen eines API-Schlüssels unter **Einstellungen → API & Webhooks** verfügbar. Da Twenty APIs erzeugt, die Ihrem benutzerdefinierten Datenmodell entsprechen, ist die Dokumentation für Ihren Arbeitsbereich einzigartig. - +**Core API** — `/rest/` and `/graphql/` -## Die beiden API-Typen +CRUD on records: People, Companies, Opportunities, your custom objects. Query, filter, traverse relations. -### Core API +**Metadata API** — `/rest/metadata/` and `/metadata/` -Zugriff auf `/rest/` oder `/graphql/` +Schema management: create/modify/delete objects, fields, and relations. This is how you programmatically change your data model. -Arbeiten Sie mit Ihren tatsächlichen **Datensätzen** (den Daten): +Both are available as REST and GraphQL. GraphQL adds batch upserts and the ability to traverse relations in a single query. Same underlying data either way. -* Personen, Unternehmen, Opportunities usw. erstellen, lesen, aktualisieren und löschen -* Daten abfragen und filtern -* Datensatzbeziehungen verwalten +## Base URLs -### Metadata API - -Zugriff auf `/rest/metadata/` oder `/metadata/` - -Verwalten Sie Ihren **Arbeitsbereich und Ihr Datenmodell**: - -* Objekte und Felder erstellen, ändern oder löschen -* Arbeitsbereichseinstellungen konfigurieren -* Beziehungen zwischen Objekten definieren - -## REST vs GraphQL - -Sowohl Core- als auch Metadata-APIs sind in REST- und GraphQL-Formaten verfügbar: - -| Format | Verfügbare Vorgänge | -| ----------- | ------------------------------------------------------------------- | -| **REST** | CRUD, Batch-Vorgänge, Upserts | -| **GraphQL** | Dasselbe plus **Batch-Upserts**, Beziehungsabfragen in einem Aufruf | - -Wählen Sie je nach Bedarf — beide Formate greifen auf dieselben Daten zu. - -## API-Endpunkte - -| Umgebung | Basis-URL | -| ----------------- | ------------------------- | -| **Cloud** | `https://api.twenty.com/` | -| **Selbsthosting** | `https://{your-domain}/` | +| Umgebung | Basis-URL | +| ----------- | ------------------------- | +| Cloud | `https://api.twenty.com/` | +| Self-Hosted | `https://{your-domain}/` | ## Authentifizierung -Jede API-Anfrage erfordert einen API-Schlüssel im Header: - ``` Authorization: Bearer YOUR_API_KEY ``` -### API-Schlüssel erstellen - -1. Gehen Sie zu **Einstellungen → APIs & Webhooks** -2. Klicken Sie auf **+ Schlüssel erstellen** -3. Konfigurieren: - * **Name**: Beschreibender Name für den Schlüssel - * **Ablaufdatum**: Wann der Schlüssel abläuft -4. Klicken Sie auf **Speichern** -5. **Sofort kopieren** — der Schlüssel wird nur einmal angezeigt +Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access. - -Ihr API-Schlüssel gewährt Zugriff auf sensible Daten. Teilen Sie ihn nicht mit nicht vertrauenswürdigen Diensten. Wenn er kompromittiert wurde, deaktivieren Sie ihn umgehend und erstellen Sie einen neuen. - +For OAuth-based access (external apps acting on behalf of users), see [OAuth](/l/de/developers/extend/oauth). -### Einem API-Schlüssel eine Rolle zuweisen +## Batch operations -Für mehr Sicherheit weisen Sie eine spezifische Rolle zu, um den Zugriff zu beschränken: +Both REST and GraphQL support batching up to 60 records per request — create, update, or delete. GraphQL also supports batch upsert (create-or-update in one call) using plural names like `CreateCompanies`. -1. Gehen Sie zu **Einstellungen → Rollen** -2. Klicken Sie auf die Rolle, die Sie zuweisen möchten -3. Öffnen Sie den Tab **Zuweisungen** -4. Unter **API-Schlüssel** auf **+ API-Schlüssel zuweisen** klicken -5. Wählen Sie den API-Schlüssel aus +## Rate limits -Der Schlüssel übernimmt die Berechtigungen dieser Rolle. Siehe [Berechtigungen](/l/de/user-guide/permissions-access/capabilities/permissions) für Details. - -### API-Schlüssel verwalten - -**Neu generieren**: Einstellungen → APIs & Webhooks → Schlüssel anklicken → **Neu generieren** - -**Löschen**: Einstellungen → APIs & Webhooks → Schlüssel anklicken → **Löschen** - -## API-Playground - -Testen Sie Ihre APIs direkt im Browser mit unserem integrierten Playground — verfügbar für **REST** und **GraphQL**. - -### Auf den Playground zugreifen - -1. Gehen Sie zu **Einstellungen → APIs & Webhooks** -2. API-Schlüssel erstellen (erforderlich) -3. Klicken Sie auf **REST API** oder **GraphQL API**, um den Playground zu öffnen - -### Was Sie erhalten - -* **Interaktive Dokumentation**: Für Ihr spezifisches Datenmodell generiert -* **Live-Tests**: Führen Sie echte API-Aufrufe gegen Ihren Arbeitsbereich aus -* **Schema-Explorer**: Verfügbare Objekte, Felder und Beziehungen durchsuchen -* **Request-Builder**: Abfragen mit Autovervollständigung erstellen - -Der Playground spiegelt Ihre benutzerdefinierten Objekte und Felder wider, sodass die Dokumentation für Ihren Arbeitsbereich stets korrekt ist. - -## Batch-Vorgänge - -Sowohl REST als auch GraphQL unterstützen Batch-Vorgänge: - -* **Batch-Größe**: Bis zu 60 Datensätze pro Anfrage -* **Vorgänge**: Mehrere Datensätze erstellen, aktualisieren, löschen - -**GraphQL-Exklusivfunktionen:** - -* **Batch-Upsert**: Erstellen oder Aktualisieren in einem Aufruf -* Verwenden Sie Pluralobjektnamen (z. B. `CreateCompanies` statt `CreateCompany`) - -## Rate Limits - -API-Anfragen werden gedrosselt, um die Stabilität der Plattform zu gewährleisten: - -| Limit | Wert | -| --------------- | ------------------------ | -| **Anfragen** | 100 Aufrufe pro Minute | -| **Batch-Größe** | 60 Datensätze pro Aufruf | - - -Verwenden Sie Batch-Vorgänge, um den Durchsatz zu maximieren — verarbeiten Sie bis zu 60 Datensätze in einem einzelnen API-Aufruf, statt einzelne Anfragen zu senden. - +| Limit | Wert | +| ---------- | ------------------------ | +| Requests | 100 per minute | +| Batch size | 60 Datensätze pro Aufruf | diff --git a/packages/twenty-docs/l/de/developers/extend/apps/building.mdx b/packages/twenty-docs/l/de/developers/extend/apps/building.mdx index dbb9912b2a6..1d551408b00 100644 --- a/packages/twenty-docs/l/de/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/de/developers/extend/apps/building.mdx @@ -1,2062 +1,104 @@ --- -title: Apps erstellen -description: Definieren Sie Objekte, Logikfunktionen, Frontend-Komponenten und mehr mit dem Twenty SDK. +title: Architektur +description: How Twenty apps work — sandboxing, lifecycle, and the building blocks. +icon: sitemap --- - - Apps befinden sich derzeit in der Alpha-Phase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter. - +Twenty apps are TypeScript packages that extend your workspace with custom objects, logic, UI components, and AI capabilities. They run on the Twenty platform with full sandboxing and permission controls. -Das Paket `twenty-sdk` stellt typisierte Bausteine zum Erstellen Ihrer App bereit. Diese Seite behandelt alle im SDK verfügbaren Entitätstypen und API-Clients. +## How apps work -## DefineEntity-Funktionen +An app is a collection of **entities** declared using `defineEntity()` functions from the `twenty-sdk` package. The SDK detects these declarations via AST analysis at build time and produces a **manifest** — a complete description of what your app adds to a workspace. -Das SDK stellt Funktionen bereit, um die Entitäten Ihrer App zu definieren. Sie müssen `export default defineEntity({...})` verwenden, damit das SDK Ihre Entitäten erkennt. Diese Funktionen validieren Ihre Konfiguration zur Build-Zeit und bieten IDE-Autovervollständigung sowie Typsicherheit. - - - **Die Dateiorganisation liegt bei Ihnen.** - Die Entitätserkennung ist AST-basiert — das SDK findet Aufrufe von `export default defineEntity(...)`, unabhängig davon, wo sich die Datei befindet. Das Gruppieren von Dateien nach Typ (z. B. `logic-functions/`, `roles/`) ist lediglich eine Konvention, keine Voraussetzung. - - - - - -Rollen kapseln Berechtigungen für die Objekte und Aktionen Ihres Workspaces. - -```ts restricted-company-role.ts -import { - defineRole, - PermissionFlag, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -export default defineRole({ - universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', - label: 'My new role', - description: 'A role that can be used in your workspace', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - fieldUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); ``` - - - - -Jede App muss genau einen Aufruf von `defineApplication` haben, der Folgendes beschreibt: - -* **Identität**: Bezeichner, Anzeigename und Beschreibung. -* **Berechtigungen**: welche Rolle ihre Funktionen und Frontend-Komponenten verwenden. -* **(Optional) Variablen**: Schlüssel–Wert-Paare, die Ihren Funktionen als Umgebungsvariablen zur Verfügung gestellt werden. -* **(Optional) Pre-/Post-Installationsfunktionen**: Logikfunktionen, die vor oder nach der Installation ausgeführt werden. - -```ts src/application-config.ts -import { defineApplication } from 'twenty-sdk/define'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Notizen: -* `universalIdentifier`-Felder sind deterministische IDs, die Ihnen gehören. Erzeugen Sie sie einmal und halten Sie sie über Synchronisierungen hinweg stabil. -* `applicationVariables` werden zu Umgebungsvariablen für Ihre Funktionen und Frontend-Komponenten (z. B. ist `DEFAULT_RECIPIENT_NAME` als `process.env.DEFAULT_RECIPIENT_NAME` verfügbar). -* `defaultRoleUniversalIdentifier` muss auf eine mit `defineRole()` definierte Rolle verweisen (siehe oben). -* Pre- und Post-Installationsfunktionen werden während des Manifest-Builds automatisch erkannt — Sie müssen sie in `defineApplication()` nicht referenzieren. - -#### Marktplatz-Metadaten - -Wenn Sie planen, [Ihre App zu veröffentlichen](/l/de/developers/extend/apps/publishing), steuern diese optionalen Felder, wie Ihre App im Marktplatz erscheint: - -| Feld | Beschreibung | -| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- | -| `author` | Name des Autors oder des Unternehmens | -| `category` | App-Kategorie für die Filterung im Marktplatz | -| `logoUrl` | Pfad zu Ihrem App-Logo (z. B. `public/logo.png`) | -| `screenshots` | Array von Screenshot-Pfaden (z. B. `public/screenshot-1.png`) | -| `aboutDescription` | Längere Markdown-Beschreibung für den Tab "Info". Wenn weggelassen, verwendet der Marketplace die `README.md` des Pakets von npm | -| `websiteUrl` | Link zu Ihrer Website | -| `termsUrl` | Link zu den Nutzungsbedingungen | -| `emailSupport` | Support-E-Mail-Adresse | -| `issueReportUrl` | Link zum Issue-Tracker | - -#### Rollen und Berechtigungen - -Das Feld `defaultRoleUniversalIdentifier` in `application-config.ts` legt die Standardrolle fest, die von den Logikfunktionen und Frontend-Komponenten Ihrer App verwendet wird. Details finden Sie oben unter `defineRole`. - -* Das zur Laufzeit als `TWENTY_APP_ACCESS_TOKEN` injizierte Token wird aus dieser Rolle abgeleitet. -* Der typisierte Client ist auf die dieser Rolle gewährten Berechtigungen beschränkt. -* Befolgen Sie das Least-Privilege-Prinzip: Erstellen Sie eine dedizierte Rolle nur mit den Berechtigungen, die Ihre Funktionen benötigen. - -##### Standard-Funktionsrolle - -Wenn Sie eine neue App erzeugen, erstellt die CLI eine Standard-Rolldatei: - -```ts src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk/define'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: true, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [], - fieldPermissions: [], - permissionFlags: [], -}); -``` - -Der `universalIdentifier` dieser Rolle wird in `application-config.ts` als `defaultRoleUniversalIdentifier` referenziert: - -* **\*.role.ts** definiert, was die Rolle darf. -* **application-config.ts** verweist auf diese Rolle, sodass Ihre Funktionen deren Berechtigungen erben. - -Notizen: -* Beginnen Sie mit der vorab erstellten Rolle und schränken Sie sie schrittweise gemäß dem Least-Privilege-Prinzip ein. -* Ersetzen Sie `objectPermissions` und `fieldPermissions` durch die Objekte und Felder, die Ihre Funktionen tatsächlich benötigen. -* `permissionFlags` steuern den Zugriff auf Funktionen auf Plattformebene. Halten Sie sie minimal. -* Ein funktionierendes Beispiel finden Sie unter: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - - - - -Benutzerdefinierte Objekte beschreiben sowohl Schema als auch Verhalten für Datensätze in Ihrem Workspace. Verwenden Sie `defineObject()`, um Objekte mit eingebauter Validierung zu definieren: - -```ts postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk/define'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Hauptpunkte: - -* Verwenden Sie `defineObject()` für eingebaute Validierung und bessere IDE-Unterstützung. -* Der `universalIdentifier` muss eindeutig und über Deployments hinweg stabil sein. -* Jedes Feld benötigt `name`, `type`, `label` und einen eigenen stabilen `universalIdentifier`. -* Das Array `fields` ist optional — Sie können Objekte ohne benutzerdefinierte Felder definieren. -* Sie können mit `yarn twenty add` neue Objekte erzeugen; der Assistent führt Sie durch Benennung, Felder und Beziehungen. - - -**Basisfelder werden automatisch erstellt.** Wenn Sie ein benutzerdefiniertes Objekt definieren, fügt Twenty automatisch Standardfelder hinzu -wie `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` und `deletedAt`. -Sie müssen diese nicht in Ihrem `fields`-Array definieren — fügen Sie nur Ihre benutzerdefinierten Felder hinzu. -Sie können Standardfelder überschreiben, indem Sie in Ihrem `fields`-Array ein Feld mit demselben Namen definieren, -dies wird jedoch nicht empfohlen. - - - - - -Verwenden Sie `defineField()`, um Objekten, die Ihnen nicht gehören — etwa Standardobjekten von Twenty (Person, Company usw.) — Felder hinzuzufügen oder Objekten aus anderen Apps. Im Gegensatz zu Inline-Feldern in `defineObject()` benötigen eigenständige Felder einen `objectUniversalIdentifier`, um anzugeben, welches Objekt sie erweitern: - -```ts src/fields/company-loyalty-tier.field.ts -import { defineField, FieldType } from 'twenty-sdk/define'; - -export default defineField({ - universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', - objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object - name: 'loyaltyTier', - type: FieldType.SELECT, - label: 'Loyalty Tier', - icon: 'IconStar', - options: [ - { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, - { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, - { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, - ], -}); -``` - -Hauptpunkte: -* Der `objectUniversalIdentifier` identifiziert das Zielobjekt. Für Standardobjekte verwenden Sie `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`, die aus `twenty-sdk` exportiert werden. -* Wenn Sie Felder inline in `defineObject()` definieren, benötigen Sie `objectUniversalIdentifier` **nicht** — er wird vom übergeordneten Objekt geerbt. -* `defineField()` ist die einzige Möglichkeit, Felder zu Objekten hinzuzufügen, die Sie nicht mit `defineObject()` erstellt haben. - - - - -Relationen verbinden Objekte miteinander. In Twenty sind Relationen stets **bidirektional** — Sie definieren beide Seiten, und jede Seite referenziert die andere. - -Es gibt zwei Relationstypen: - -| Beziehungstyp | Beschreibung | Fremdschlüssel vorhanden? | -| ------------- | ----------------------------------------------------------------------- | ------------------------- | -| `MANY_TO_ONE` | Viele Datensätze dieses Objekts verweisen auf einen Datensatz des Ziels | Ja (`joinColumnName`) | -| `ONE_TO_MANY` | Ein Datensatz dieses Objekts hat viele Datensätze des Ziels | Nein (inverse Seite) | - -#### Wie Relationen funktionieren - -Jede Relation erfordert **zwei Felder**, die sich gegenseitig referenzieren: - -1. Die **MANY_TO_ONE**-Seite — befindet sich auf dem Objekt, das den Fremdschlüssel hält -2. Die **ONE_TO_MANY**-Seite — befindet sich auf dem Objekt, dem die Sammlung gehört - -Beide Felder verwenden `FieldType.RELATION` und verweisen über `relationTargetFieldMetadataUniversalIdentifier` gegenseitig aufeinander. - -#### Beispiel: Postkarte hat viele Empfänger - -Angenommen, eine `PostCard` kann an viele `PostCardRecipient`-Datensätze gesendet werden. Jeder Empfänger gehört genau zu einer Postkarte. - -**Schritt 1: Definieren Sie die ONE_TO_MANY-Seite auf PostCard** (die "eine" Seite): - -```ts src/fields/post-card-recipients-on-post-card.field.ts -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; -// Import from the other side -import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; - -export default defineField({ - universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCardRecipients', - label: 'Post Card Recipients', - icon: 'IconUsers', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); -``` - -**Schritt 2: Definieren Sie die MANY_TO_ONE-Seite auf PostCardRecipient** (die "viele" Seite — hält den Fremdschlüssel): - -```ts src/fields/post-card-on-post-card-recipient.field.ts -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; -// Import from the other side -import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; - -export default defineField({ - universalIdentifier: POST_CARD_FIELD_ID, - objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - icon: 'IconMail', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, -}); +your-app/ +├── src/ +│ ├── application-config.ts ← defineApplication (required, one per app) +│ ├── roles/ ← defineRole +│ ├── objects/ ← defineObject +│ ├── fields/ ← defineField +│ ├── logic-functions/ ← defineLogicFunction +│ ├── front-components/ ← defineFrontComponent +│ ├── skills/ ← defineSkill +│ ├── agents/ ← defineAgent +│ ├── views/ ← defineView +│ ├── navigation-menu-items/ ← defineNavigationMenuItem +│ └── page-layouts/ ← definePageLayout +├── public/ ← Static assets (images, icons) +└── package.json ``` -**Zyklische Importe:** Beide Relationsfelder referenzieren gegenseitig den `universalIdentifier` des jeweils anderen. Um Probleme mit zyklischen Importen zu vermeiden, exportieren Sie Ihre Feld-IDs als benannte Konstanten aus jeder Datei und importieren Sie sie in der jeweils anderen Datei. Das Build-System löst dies zur Kompilierzeit auf. + **File organization is up to you.** Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. The folder structure above is a convention, not a requirement. -#### Relationen zu Standardobjekten - -Um eine Relation mit einem integrierten Twenty-Objekt (Person, Company usw.) zu erstellen, verwenden Sie `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: - -```ts src/fields/person-on-self-hosting-user.field.ts -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; -import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; - -export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; -export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; - -export default defineField({ - universalIdentifier: PERSON_FIELD_ID, - objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - description: 'Person matching with the self hosting user', - isNullable: true, - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'personId', - }, -}); -``` - -#### Eigenschaften von Relationsfeldern - -| Eigenschaft | Erforderlich | Beschreibung | -| ------------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------- | -| `type` | Ja | Muss `FieldType.RELATION` sein | -| `relationTargetObjectMetadataUniversalIdentifier` | Ja | Der `universalIdentifier` des Zielobjekts | -| `relationTargetFieldMetadataUniversalIdentifier` | Ja | Der `universalIdentifier` des entsprechenden Felds auf dem Zielobjekt | -| `universalSettings.relationType` | Ja | `RelationType.MANY_TO_ONE` oder `RelationType.ONE_TO_MANY` | -| `universalSettings.onDelete` | Nur für MANY_TO_ONE | Was passiert, wenn der referenzierte Datensatz gelöscht wird: `CASCADE`, `SET_NULL`, `RESTRICT` oder `NO_ACTION` | -| `universalSettings.joinColumnName` | Nur für MANY_TO_ONE | Datenbankspaltenname für den Fremdschlüssel (z. B. `postCardId`) | - -#### Inline-Relationsfelder in defineObject - -Sie können Relationsfelder auch direkt innerhalb von `defineObject()` definieren. In diesem Fall lassen Sie `objectUniversalIdentifier` weg — er wird vom übergeordneten Objekt geerbt: - -```ts -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCardRecipient', - // ... - fields: [ - { - universalIdentifier: POST_CARD_FIELD_ID, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, - }, - // ... other fields - ], -}); -``` - - - -Jede Funktionsdatei verwendet `defineLogicFunction()`, um eine Konfiguration mit einem Handler und optionalen Triggern zu exportieren. - -```ts src/logic-functions/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - httpRouteTriggerSettings: { - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: true, - }, - /*databaseEventTriggerSettings: { - eventName: 'people.created', - },*/ - /*cronTriggerSettings: { - pattern: '0 0 1 1 *', - },*/ -}); -``` - -Verfügbare Trigger-Typen: -* **httpRoute**: Stellt Ihre Funktion unter einem HTTP-Pfad und einer Methode **unter dem Endpunkt `/s/`** bereit: -> z. B. `path: '/post-card/create'` ist unter `https://your-twenty-server.com/s/post-card/create` aufrufbar -* **cron**: Führt Ihre Funktion nach Zeitplan mithilfe eines CRON-Ausdrucks aus. -* **databaseEvent**: Wird bei Lebenszyklusereignissen von Workspace-Objekten ausgeführt. Wenn die Ereignisoperation `updated` ist, können bestimmte zu überwachende Felder im Array `updatedFields` angegeben werden. Wenn das Array undefiniert oder leer ist, löst jede Aktualisierung die Funktion aus. -> z. B. `person.updated`, `*.created`, `company.*` - - -Sie können eine Funktion auch manuell über die CLI ausführen: - -```bash filename="Terminal" -yarn twenty exec -n create-new-post-card -p '{"key": "value"}' -``` - -```bash filename="Terminal" -yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -Sie können Protokolle mit folgendem Befehl ansehen: - -```bash filename="Terminal" -yarn twenty logs -``` - - -#### Routen-Trigger-Payload - -Wenn ein Route-Trigger Ihre Logikfunktion aufruft, erhält sie ein `RoutePayload`-Objekt, das dem [AWS-HTTP-API-v2-Format](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) folgt. -Importieren Sie den Typ `RoutePayload` aus `twenty-sdk`: - -```ts -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; - -const handler = async (event: RoutePayload) => { - const { headers, queryStringParameters, pathParameters, body } = event; - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -Der Typ `RoutePayload` hat die folgende Struktur: - - | Eigenschaft | Typ | Beschreibung | Beispiel | - | ---------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------- | - | `headers` | `Record\` | HTTP-Header (nur die in `forwardedRequestHeaders` aufgelisteten) | siehe Abschnitt unten | - | `queryStringParameters` | `Record\` | Query-String-Parameter (mehrere Werte mit Kommas verbunden) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | - | `pathParameters` | `Record\` | Aus dem Routenmuster extrahierte Pfadparameter | `/users/:id`, `/users/123` -> `{ id: '123' }` | - | `body` | `object \| null` | Geparster Request-Body (JSON) | `{ id: 1 }` -> `{ id: 1 }` | - | `isBase64Encoded` | `boolean` | Gibt an, ob der Body Base64-codiert ist | | - | `requestContext.http.method` | `string` | HTTP-Methode (GET, POST, PUT, PATCH, DELETE) | | - | `requestContext.http.path` | `string` | Rohpfad der Anfrage | | - - -#### forwardedRequestHeaders - -Standardmäßig werden HTTP-Header von eingehenden Anfragen aus Sicherheitsgründen nicht an Ihre Logikfunktion weitergegeben. -Um auf bestimmte Header zuzugreifen, listen Sie diese im Array `forwardedRequestHeaders` auf: - -```ts -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - httpRouteTriggerSettings: { - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, -}); -``` - -Greifen Sie in Ihrem Handler wie folgt auf die weitergeleiteten Header zu: - -```ts -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - -Header-Namen werden in Kleinbuchstaben normalisiert. Greifen Sie mit Schlüsseln in Kleinbuchstaben darauf zu (z. B. `event.headers['content-type']`). - - -#### Eine Funktion als Tool bereitstellen - -Logikfunktionen können als **Tools** für KI-Agenten und Workflows verfügbar gemacht werden. Wenn eine Funktion als Tool markiert ist, wird sie von den KI-Funktionen von Twenty auffindbar und kann in Workflow-Automatisierungen verwendet werden. - -Um eine Logikfunktion als Tool zu markieren, setzen Sie `isTool: true`: - -```ts src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, -}); -``` - -Hauptpunkte: - -* Sie können `isTool` mit Triggern kombinieren — eine Funktion kann gleichzeitig sowohl ein Tool (von KI-Agenten aufrufbar) als auch durch Ereignisse ausgelöst werden. -* **`toolInputSchema`** (optional): Ein JSON-Schema-Objekt, das die Parameter beschreibt, die Ihre Funktion akzeptiert. Das Schema wird automatisch durch statische Analyse des Quellcodes ermittelt, Sie können es jedoch auch explizit festlegen: - -```ts -export default defineLogicFunction({ - ..., - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - - -**Schreiben Sie eine gute `description`.** KI-Agenten verlassen sich auf das `description`-Feld der Funktion, um zu entscheiden, wann das Tool verwendet werden soll. Seien Sie konkret darin, was das Tool tut und wann es aufgerufen werden soll. - - - - - -Eine Post-Installationsfunktion ist eine Logikfunktion, die automatisch ausgeführt wird, nachdem Ihre App in einem Arbeitsbereich installiert wurde. Der Server führt sie **nach** der Synchronisierung der Metadaten der App und der Generierung des SDK-Clients aus, sodass der Arbeitsbereich vollständig einsatzbereit ist und das neue Schema bereitsteht. Typische Anwendungsfälle umfassen das Befüllen von Standarddaten, das Erstellen anfänglicher Datensätze, das Konfigurieren von Arbeitsbereichseinstellungen oder das Bereitstellen von Ressourcen bei Diensten von Drittanbietern. - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - shouldRunSynchronously: false, - handler, -}); -``` - -Sie können die Post-Installationsfunktion auch jederzeit manuell über die CLI ausführen: - -```bash filename="Terminal" -yarn twenty exec --postInstall -``` - -Hauptpunkte: -* Post-Installationsfunktionen verwenden `definePostInstallLogicFunction()` — eine spezialisierte Variante, die Trigger-Einstellungen (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) weglässt. -* Der Handler erhält ein `InstallPayload` mit `{ previousVersion?: string; newVersion: string }` — `newVersion` ist die zu installierende Version, und `previousVersion` ist die zuvor installierte Version (oder `undefined` bei einer Neuinstallation). Verwenden Sie diese Werte, um Neuinstallationen von Upgrades zu unterscheiden und versionsspezifische Migrationslogik auszuführen. -* **Wann der Hook ausgeführt wird**: standardmäßig nur bei Neuinstallationen. Übergeben Sie `shouldRunOnVersionUpgrade: true`, wenn er auch beim Upgrade der App von einer vorherigen Version ausgeführt werden soll. Wenn weggelassen, ist das Flag standardmäßig `false` und Upgrades überspringen den Hook. -* **Ausführungsmodell — standardmäßig asynchron, synchron optional**: Das Flag `shouldRunSynchronously` steuert, *wie* Post-Install ausgeführt wird. - * `shouldRunSynchronously: false` *(Standard)* — der Hook wird **in die Nachrichtenwarteschlange eingereiht** mit `retryLimit: 3` und läuft asynchron in einem Worker. Die Installationsantwort kommt zurück, sobald der Job eingereiht ist, sodass ein langsamer oder fehlschlagender Handler den Aufrufer nicht blockiert. Der Worker versucht es bis zu dreimal erneut. **Verwenden Sie dies für lang laufende Jobs** — das Befüllen großer Datensätze, Aufrufe langsamer Drittanbieter-APIs, Bereitstellung externer Ressourcen, alles, was ein vernünftiges HTTP-Antwortfenster überschreiten könnte. - * `shouldRunSynchronously: true` — der Hook wird **inline während des Installationsablaufs** ausgeführt (gleicher Executor wie bei Pre-Install). Die Installationsanforderung blockiert, bis der Handler fertig ist, und wenn er einen Fehler wirft, erhält der Installationsaufrufer einen `POST_INSTALL_ERROR`. Keine automatischen Wiederholungen. **Verwenden Sie dies für schnelle Aufgaben, die vor der Antwort abgeschlossen sein müssen** — z. B. um dem Benutzer einen Validierungsfehler auszugeben oder für eine schnelle Einrichtung, auf die der Client unmittelbar nach der Rückkehr des Installationsaufrufs angewiesen ist. Beachten Sie, dass die Metadatenmigration bereits angewendet wurde, wenn Post-Install läuft, sodass ein Fehler im Synchronmodus die Schemaänderungen **nicht** rückgängig macht — er zeigt lediglich den Fehler an. -* Stellen Sie sicher, dass Ihr Handler idempotent ist. Im asynchronen Modus kann die Warteschlange bis zu dreimal erneut versuchen; in beiden Modi kann der Hook bei Upgrades erneut laufen, wenn `shouldRunOnVersionUpgrade: true`. -* Die Umgebungsvariablen `APPLICATION_ID`, `APP_ACCESS_TOKEN` und `API_URL` sind im Handler verfügbar (wie bei jeder anderen Logikfunktion), sodass Sie die Twenty API mit einem auf Ihre App beschränkten Anwendungszugriffstoken aufrufen können. -* Pro Anwendung ist nur eine Post-Installationsfunktion zulässig. Der Manifest-Build schlägt fehl, wenn mehr als eine erkannt wird. -* Die `universalIdentifier`, `shouldRunOnVersionUpgrade` und `shouldRunSynchronously` der Funktion werden während des Builds automatisch dem Anwendungsmanifest unter dem Feld `postInstallLogicFunction` hinzugefügt — Sie müssen sie in `defineApplication()` nicht referenzieren. -* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Einrichtungsvorgänge wie Daten-Seeding zu ermöglichen. -* **Nicht im Dev-Modus ausgeführt**: Wenn eine App lokal registriert ist (über `yarn twenty dev`), überspringt der Server den Installationsablauf vollständig und synchronisiert Dateien direkt über den CLI-Watcher — daher läuft Post-Install im Dev-Modus nie, unabhängig von `shouldRunSynchronously`. Verwenden Sie `yarn twenty exec --postInstall`, um es manuell gegen einen laufenden Workspace auszulösen. - - - - -Eine Pre-Install-Funktion ist eine Logikfunktion, die automatisch während der Installation ausgeführt wird, **bevor die Metadatenmigration des Workspaces angewendet wird**. Sie hat die gleiche Payload-Struktur wie Post-Install (`InstallPayload`), ist aber früher im Installationsablauf positioniert, sodass sie Zustände vorbereiten kann, von denen die bevorstehende Migration abhängt — typische Anwendungsfälle sind das Sichern von Daten, die Validierung der Kompatibilität mit dem neuen Schema oder das Archivieren von Datensätzen, die umstrukturiert oder entfernt werden sollen. - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -Sie können die Pre-Installationsfunktion auch jederzeit manuell über die CLI ausführen: - -```bash filename="Terminal" -yarn twenty exec --preInstall -``` - -Hauptpunkte: -* Pre-Install-Funktionen verwenden `definePreInstallLogicFunction()` — dieselbe spezialisierte Konfiguration wie bei Post-Install, nur an einen anderen Lifecycle-Slot gebunden. -* Sowohl Pre- als auch Post-Install-Handler erhalten denselben `InstallPayload`-Typ: `{ previousVersion?: string; newVersion: string }`. Importieren Sie ihn einmal und verwenden Sie ihn für beide Hooks wieder. -* **Wann der Hook ausgeführt wird**: positioniert direkt vor der Metadatenmigration des Workspaces (`synchronizeFromManifest`). Vor der Ausführung führt der Server einen rein additiven "pared-down sync" durch, der die Pre-Install-Funktion der **neuen** Version in den Workspace-Metadaten registriert — sonst wird nichts angefasst — und führt sie dann aus. Da dieser Sync nur additiv ist, sind die Objekte, Felder und Daten der vorherigen Version noch intakt, wenn Ihr Handler läuft: Sie können den Zustand vor der Migration gefahrlos lesen und sichern. -* **Ausführungsmodell**: Pre-Install wird **synchron** ausgeführt und **blockiert die Installation**. Wenn der Handler einen Fehler wirft, wird die Installation abgebrochen, bevor Schemaänderungen angewendet werden — der Workspace verbleibt in der vorherigen Version in einem konsistenten Zustand. Das ist beabsichtigt: Pre-Install ist Ihre letzte Chance, ein riskantes Upgrade abzulehnen. -* Wie bei Post-Install ist pro Anwendung nur eine Pre-Installationsfunktion zulässig. Sie wird während des Builds automatisch dem Anwendungsmanifest unter `preInstallLogicFunction` hinzugefügt. -* **Nicht im Dev-Modus ausgeführt**: wie bei Post-Install — der Installationsablauf wird für lokal registrierte Apps vollständig übersprungen, daher läuft Pre-Install unter `yarn twenty dev` nie. Verwenden Sie `yarn twenty exec --preInstall`, um es manuell auszulösen. - - - - -Beide Hooks sind Teil desselben Installationsablaufs und erhalten dasselbe `InstallPayload`. Der Unterschied besteht darin, **wann** sie relativ zur Metadatenmigration des Workspaces ausgeführt werden, und das ändert, auf welche Daten sie gefahrlos zugreifen können. +## Entity types + +| Entität | Zweck | Dokumentation | +| ------------------------ | ----------------------------------------- | ------------------------------------------------------------ | +| **Application** | App identity, permissions, variables | [Data Model](/l/de/developers/extend/apps/data-model) | +| **Role** | Permission sets for objects and fields | [Data Model](/l/de/developers/extend/apps/data-model) | +| **Object** | Custom data tables with fields | [Data Model](/l/de/developers/extend/apps/data-model) | +| **Feld** | Extend existing objects, define relations | [Data Model](/l/de/developers/extend/apps/data-model) | +| **Logic Function** | Server-side TypeScript with triggers | [Logikfunktionen](/l/de/developers/extend/apps/logic-functions) | +| **Front Component** | Sandboxed React UI in Twenty's page | [Front Components](/l/de/developers/extend/apps/front-components) | +| **Skill** | Reusable AI agent instructions | [Skills & Agents](/l/de/developers/extend/apps/skills-and-agents) | +| **Agent** | AI assistants with custom prompts | [Skills & Agents](/l/de/developers/extend/apps/skills-and-agents) | +| **View** | Pre-configured record list views | [Layout](/l/de/developers/extend/apps/layout) | +| **Navigation Menu Item** | Custom sidebar entries | [Layout](/l/de/developers/extend/apps/layout) | +| **Page Layout** | Custom record page tabs and widgets | [Layout](/l/de/developers/extend/apps/layout) | + +## Sandboxing + +* **Logic functions** run in isolated Node.js processes on the server. They only access data through the typed API client, scoped to the app's role permissions. +* **Front components** run in Web Workers using Remote DOM — sandboxed from the main page but rendering native DOM elements (not iframes). They communicate with Twenty via a message-passing host API. +* **Permissions** are enforced at the API level. The runtime token (`TWENTY_APP_ACCESS_TOKEN`) is derived from the role defined in `defineApplication()`. + +## App lifecycle ``` -┌─────────────────────────────────────────────────────────────┐ -│ install flow │ -│ │ -│ upload package → [pre-install] → metadata migration → │ -│ generate SDK → [post-install] │ -│ │ -│ old schema visible new schema visible │ -└─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────┐ +│ Development │ +│ npx create-twenty-app → yarn twenty dev (live sync) │ +├─────────────────────────────────────────────────────────┤ +│ Build & Deploy │ +│ yarn twenty build → yarn twenty deploy │ +├─────────────────────────────────────────────────────────┤ +│ Install flow │ +│ upload → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +├─────────────────────────────────────────────────────────┤ +│ Publish │ +│ npm publish → appears in Twenty marketplace │ +└─────────────────────────────────────────────────────────┘ ``` -Pre-Install ist immer **synchron** (blockiert die Installation und kann sie abbrechen). Post-Install ist **standardmäßig asynchron** — in einen Worker eingereiht mit automatischen Wiederholungen — kann aber per `shouldRunSynchronously: true` in die synchrone Ausführung wechseln. Siehe das Akkordeon zu `definePostInstallLogicFunction` oben, wann welcher Modus zu verwenden ist. - -**Verwenden Sie `post-install` für alles, wofür das neue Schema existieren muss.** Dies ist der Regelfall: - -* Standarddaten befüllen (Anlegen anfänglicher Datensätze, Standardansichten, Demo-Inhalte) für neu hinzugefügte Objekte und Felder. -* Registrieren von Webhooks bei Drittanbieter-Diensten, jetzt, da die App ihre Anmeldedaten hat. -* Aufrufen Ihrer eigenen API, um eine Einrichtung abzuschließen, die von den synchronisierten Metadaten abhängt. -* Idempotente "Stelle sicher, dass dies existiert"-Logik, die bei jedem Upgrade den Zustand abgleichen soll — kombinieren Sie dies mit `shouldRunOnVersionUpgrade: true`. - -Beispiel — nach der Installation einen Standard-`PostCard`-Datensatz anlegen: - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion }: InstallPayload): Promise => { - if (previousVersion) return; // fresh installs only - - const client = createClient(); - await client.postCard.create({ - data: { title: 'Welcome to Postcard', content: 'Your first card!' }, - }); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Seeds a welcome post card after install.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - handler, -}); -``` - -**Verwenden Sie `pre-install`, wenn eine Migration ansonsten vorhandene Daten löschen oder beschädigen würde.** Da Pre-Install gegen das vorherige Schema läuft und ein Fehlschlag das Upgrade zurückrollt, ist es der richtige Ort für alles Riskante: - -* **Sichern von Daten, die gleich gelöscht oder umstrukturiert werden** — z. B. Sie entfernen in v2 ein Feld und müssen dessen Werte vor der Migration in ein anderes Feld kopieren oder in einen Speicher exportieren. -* **Archivieren von Datensätzen, die eine neue Einschränkung ungültig machen würde** — z. B. ein Feld wird `NOT NULL` und Sie müssen zuerst Zeilen mit Null-Werten löschen oder korrigieren. -* **Kompatibilität validieren und das Upgrade ablehnen, wenn die aktuellen Daten nicht sauber migriert werden können** — werfen Sie im Handler einen Fehler, und die Installation wird ohne Änderungen abgebrochen. Das ist sicherer, als die Inkompatibilität mitten in der Migration zu entdecken. -* **Daten umbenennen oder Schlüssel neu zuweisen** vor einer Schemaänderung, bei der sonst die Zuordnung verloren ginge. - -Beispiel — Datensätze vor einer destruktiven Migration archivieren: - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { - // Only the 1.x → 2.x upgrade drops the legacy `notes` field. - if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { - return; - } - - const client = createClient(); - const legacyRecords = await client.postCard.findMany({ - where: { notes: { isNotNull: true } }, - }); - - if (legacyRecords.length === 0) return; - - // Copy legacy `notes` into the new `description` field before the migration - // drops the `notes` column. If this fails, the upgrade is aborted and the - // workspace stays on v1 with all data intact. - await Promise.all( - legacyRecords.map((record) => - client.postCard.update({ - where: { id: record.id }, - data: { description: record.notes }, - }), - ), - ); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Backs up legacy notes into description before the v2 migration.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -**Faustregel:** - -| Sie möchten … | Verwenden | -| ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| Standarddaten befüllen, den Workspace konfigurieren, externe Ressourcen registrieren | `post-install` | -| Lang laufendes Seeding oder Drittanbieteraufrufe ausführen, die die Installationsantwort nicht blockieren sollten | `post-install` (Standard — `shouldRunSynchronously: false`, mit Worker-Wiederholungen) | -| Schnelle Einrichtung ausführen, auf die sich der Aufrufer unmittelbar nach der Rückkehr des Installationsaufrufs verlassen wird | `post-install` mit `shouldRunSynchronously: true` | -| Daten lesen oder sichern, die bei der bevorstehenden Migration verloren gingen | `pre-install` | -| Ein Upgrade ablehnen, das vorhandene Daten beschädigen würde | `pre-install` (`throw` im Handler) | -| Bei jedem Upgrade einen Abgleich ausführen | `post-install` mit `shouldRunOnVersionUpgrade: true` | -| Einmalige Einrichtung nur bei der ersten Installation durchführen | `post-install` mit `shouldRunOnVersionUpgrade: false` (Standard) | - - -Im Zweifel auf **Post-Install** setzen. Greifen Sie nur zu Pre-Install, wenn die Migration selbst destruktiv ist und Sie den vorherigen Zustand abfangen müssen, bevor er verloren geht. - - - - - -Frontend-Komponenten sind React-Komponenten, die direkt innerhalb der Twenty-UI gerendert werden. Sie laufen in einem **isolierten Web Worker** unter Verwendung von Remote DOM — Ihr Code wird in einer Sandbox ausgeführt, rendert jedoch nativ auf der Seite, nicht in einem iframe. - -#### Wo Front-Komponenten verwendet werden können - -Front-Komponenten können an zwei Stellen innerhalb von Twenty gerendert werden: - -* **Seitenpanel** — Nicht-Headless-Front-Komponenten werden im rechten Seitenpanel geöffnet. Dies ist das Standardverhalten, wenn eine Front-Komponente über das Befehlsmenü ausgelöst wird. -* **Widgets (Dashboards und Datensatzseiten)** — Front-Komponenten können als Widgets in Seitenlayouts eingebettet werden. Beim Konfigurieren eines Dashboards oder eines Datensatzseiten-Layouts können Benutzer ein Front-Komponenten-Widget hinzufügen. - -#### Einfaches Beispiel - -Der schnellste Weg, eine Frontend-Komponente in Aktion zu sehen, ist, sie als Befehl zu registrieren. Das Hinzufügen eines `command`-Felds mit `isPinned: true` lässt sie als Schnellaktionsschaltfläche oben rechts auf der Seite erscheinen — kein Seitenlayout erforderlich: - -```tsx src/front-components/hello-world.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; - -const HelloWorld = () => { - return ( -
-

Hello from my app!

-

This component renders inside Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', - name: 'hello-world', - description: 'A simple front component', - component: HelloWorld, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', - shortLabel: 'Hello', - label: 'Hello World', - icon: 'IconBolt', - isPinned: true, - availabilityType: 'GLOBAL', - }, -}); -``` - -Nach dem Synchronisieren mit `yarn twenty dev` (oder durch einmaliges Ausführen von `yarn twenty dev --once`) erscheint die Schnellaktion oben rechts auf der Seite: - -
- Schnellaktionsschaltfläche oben rechts -
- -Klicken Sie darauf, um die Komponente inline zu rendern. - -{/* TODO: add screenshot of the rendered front component */} - -#### Konfigurationsfelder - -| Feld | Erforderlich | Beschreibung | -| --------------------- | ------------ | ---------------------------------------------------------------------------------------- | -| `universalIdentifier` | Ja | Stabile eindeutige ID für diese Komponente | -| `component` | Ja | Eine React-Komponentenfunktion | -| `name` | Nein | Anzeigename | -| `description` | Nein | Beschreibung dessen, was die Komponente macht | -| `isHeadless` | Nein | Auf `true` setzen, wenn die Komponente keine sichtbare UI hat (siehe unten) | -| `command` | Nein | Die Komponente als Befehl registrieren (siehe unten [Befehlsoptionen](#command-options)) | - -#### Eine Frontend-Komponente auf einer Seite platzieren - -Über Befehle hinaus können Sie eine Frontend-Komponente direkt in eine Datensatzseite einbetten, indem Sie sie als Widget in einem **Seitenlayout** hinzufügen. Details finden Sie im Abschnitt [definePageLayout](#definepagelayout). - -#### Headless vs. Nicht-Headless - -Front-Komponenten gibt es in zwei Rendering-Modi, die durch die Option `isHeadless` gesteuert werden: - -**Nicht-Headless (Standard)** — Die Komponente rendert eine sichtbare UI. Wird sie über das Befehlsmenü ausgelöst, öffnet sie sich im Seitenpanel. Dies ist das Standardverhalten, wenn `isHeadless` `false` ist oder weggelassen wird. - -**Headless (`isHeadless: true`)** — Die Komponente wird unsichtbar im Hintergrund gemountet. Sie öffnet das Seitenpanel nicht. Headless-Komponenten sind für Aktionen konzipiert, die Logik ausführen und sich anschließend selbst unmounten — zum Beispiel das Ausführen einer asynchronen Aufgabe, das Navigieren zu einer Seite oder das Anzeigen eines Bestätigungsdialogs. Sie lassen sich gut mit den unten beschriebenen SDK-Command-Komponenten kombinieren. - -```tsx src/front-components/sync-tracker.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; -import { useEffect } from 'react'; - -const SyncTracker = () => { - const recordId = useRecordId(); - - useEffect(() => { - enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); - }, [recordId]); - - return null; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'sync-tracker', - description: 'Tracks record views silently', - isHeadless: true, - component: SyncTracker, -}); -``` - -Da die Komponente `null` zurückgibt, überspringt Twenty das Rendern eines Containers dafür — im Layout entsteht kein Leerraum. Die Komponente hat dennoch Zugriff auf alle Hooks und die Host-Kommunikations-API. - -#### SDK-Command-Komponenten - -Das Paket `twenty-sdk` stellt vier Command-Hilfskomponenten bereit, die für Headless-Front-Komponenten ausgelegt sind. Jede Komponente führt beim Mounten eine Aktion aus, behandelt Fehler durch Anzeige einer Snackbar-Benachrichtigung und unmountet die Front-Komponente nach Abschluss automatisch. - -Importieren Sie sie aus `twenty-sdk/command`: - -* **`Command`** — Führt einen asynchronen Callback über das Prop `execute` aus. -* **`CommandLink`** — Navigiert zu einem App-Pfad. Props: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — Öffnet einen Bestätigungsdialog. Bestätigt der Benutzer, wird der Callback `execute` ausgeführt. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — Öffnet eine bestimmte Seite im Seitenpanel. Props: `page`, `pageTitle`, `pageIcon`. - -Hier ist ein vollständiges Beispiel einer Headless-Front-Komponente, die `Command` verwendet, um eine Aktion aus dem Befehlsmenü auszuführen: - -```tsx src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -Und ein Beispiel, das `CommandModal` verwendet, um vor der Ausführung um Bestätigung zu bitten: - -```tsx src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Zugriff auf den Laufzeitkontext - -Verwenden Sie innerhalb Ihrer Komponente SDK-Hooks, um auf den aktuellen Benutzer, den Datensatz und die Komponenteninstanz zuzugreifen: - -```tsx src/front-components/record-info.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - useUserId, - useRecordId, - useFrontComponentId, -} from 'twenty-sdk/front-component'; - -const RecordInfo = () => { - const userId = useUserId(); - const recordId = useRecordId(); - const componentId = useFrontComponentId(); - - return ( -
-

User: {userId}

-

Record: {recordId ?? 'No record context'}

-

Component: {componentId}

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', - name: 'record-info', - component: RecordInfo, -}); -``` - -Verfügbare Hooks: - -| Hook | Gibt zurück | Beschreibung | -| --------------------------------------------- | -------------------- | --------------------------------------------------------------------------- | -| `useUserId()` | `string` oder `null` | Die ID des aktuellen Benutzers | -| `useRecordId()` | `string` oder `null` | Die ID des aktuellen Datensatzes (wenn auf einer Datensatzseite platziert) | -| `useFrontComponentId()` | `string` | Die ID dieser Komponenteninstanz | -| `useFrontComponentExecutionContext(selector)` | variiert | Zugriff auf den vollständigen Ausführungskontext mit einer Selektorfunktion | - -#### Host-Kommunikations-API - -Frontend-Komponenten können Navigation, Modals und Benachrichtigungen mittels Funktionen aus `twenty-sdk` auslösen: - -| Funktion | Beschreibung | -| ----------------------------------------------- | ----------------------------------------- | -| `navigate(to, params?, queryParams?, options?)` | Zu einer Seite in der App navigieren | -| `openSidePanelPage(params)` | Ein Seitenpanel öffnen | -| `closeSidePanel()` | Seitenpanel schließen | -| `openCommandConfirmationModal(params)` | Einen Bestätigungsdialog anzeigen | -| `enqueueSnackbar(params)` | Eine Toast-Benachrichtigung anzeigen | -| `unmountFrontComponent()` | Die Komponente entfernen | -| `updateProgress(progress)` | Einen Fortschrittsindikator aktualisieren | - -Hier ist ein Beispiel, das die Host-API verwendet, um nach Abschluss einer Aktion eine Snackbar anzuzeigen und das Seitenpanel zu schließen: - -```tsx src/front-components/archive-record.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId } from 'twenty-sdk/front-component'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -#### Befehlsoptionen - -Das Hinzufügen eines `command`-Felds zu `defineFrontComponent` registriert die Komponente im Befehlsmenü (Cmd+K). Wenn `isPinned` `true` ist, erscheint sie außerdem als Schnellaktionsschaltfläche oben rechts auf der Seite. - -| Feld | Erforderlich | Beschreibung | -| --------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `universalIdentifier` | Ja | Stabile eindeutige ID für den Befehl | -| `label` | Ja | Vollständiges Label, das im Befehlsmenü (Cmd+K) angezeigt wird | -| `shortLabel` | Nein | Kürzeres Label, das auf der angehefteten Schnellaktionsschaltfläche angezeigt wird | -| `icon` | Nein | Neben dem Label angezeigter Icon-Name (z. B. 'IconBolt', 'IconSend') | -| `isPinned` | Nein | Bei `true` wird der Befehl als Schnellaktionsschaltfläche oben rechts auf der Seite angezeigt | -| `availabilityType` | Nein | Steuert, wo der Befehl erscheint: 'GLOBAL' (immer verfügbar), 'RECORD_SELECTION' (nur wenn Datensätze ausgewählt sind) oder 'FALLBACK' (wird angezeigt, wenn keine anderen Befehle passen) | -| `availabilityObjectUniversalIdentifier` | Nein | Beschränken Sie den Befehl auf Seiten eines bestimmten Objekttyps (z. B. nur bei Company-Datensätzen) | -| `conditionalAvailabilityExpression` | Nein | Ein boolescher Ausdruck, um dynamisch zu steuern, ob der Befehl sichtbar ist (siehe unten) | - -#### Bedingte Verfügbarkeitsausdrücke - -Mit dem Feld `conditionalAvailabilityExpression` können Sie basierend auf dem aktuellen Seitenkontext steuern, wann ein Befehl sichtbar ist. Importieren Sie typisierte Variablen und Operatoren aus `twenty-sdk`, um Ausdrücke zu erstellen: - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - pageType, - numberOfSelectedRecords, - objectPermissions, - everyEquals, - isDefined, -} from 'twenty-sdk/front-component'; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'bulk-action', - component: BulkAction, - command: { - universalIdentifier: '...', - label: 'Bulk Update', - availabilityType: 'RECORD_SELECTION', - conditionalAvailabilityExpression: everyEquals( - objectPermissions, - 'canUpdateObjectRecords', - true, - ), - }, -}); -``` - -**Kontextvariablen** — sie repräsentieren den aktuellen Zustand der Seite: - -| Variable | Typ | Beschreibung | -| ------------------------------ | --------- | --------------------------------------------------------------- | -| `pageType` | `string` | Aktueller Seitentyp (z. B. 'RecordIndexPage', 'RecordShowPage') | -| `isInSidePanel` | `boolean` | Ob die Komponente in einem Seitenpanel gerendert wird | -| `numberOfSelectedRecords` | `number` | Anzahl der aktuell ausgewählten Datensätze | -| `isSelectAll` | `boolean` | Ob „Alle auswählen“ aktiv ist | -| `selectedRecords` | `array` | Die ausgewählten Datensatzobjekte | -| `favoriteRecordIds` | `array` | IDs der favorisierten Datensätze | -| `objectPermissions` | `object` | Berechtigungen für den aktuellen Objekttyp | -| `targetObjectReadPermissions` | `object` | Leseberechtigungen für das Zielobjekt | -| `targetObjectWritePermissions` | `object` | Schreibberechtigungen für das Zielobjekt | -| `featureFlags` | `object` | Aktive Feature-Flags | -| `objectMetadataItem` | `object` | Metadaten des aktuellen Objekttyps | -| `hasAnySoftDeleteFilterOnView` | `boolean` | Ob die aktuelle Ansicht einen Soft-Delete-Filter hat | - -**Operatoren** — Variablen zu booleschen Ausdrücken kombinieren: - -| Operator | Beschreibung | -| ----------------------------------- | ------------------------------------------------------------------------------------------- | -| `isDefined(value)` | `true`, wenn der Wert nicht null/undefined ist | -| `isNonEmptyString(value)` | `true`, wenn der Wert eine nicht leere Zeichenfolge ist | -| `includes(array, value)` | `true`, wenn das Array den Wert enthält | -| `includesEvery(array, prop, value)` | `true`, wenn die Eigenschaft jedes Elements den Wert enthält | -| `every(array, prop)` | `true`, wenn die Eigenschaft bei jedem Element truthy ist | -| `everyDefined(array, prop)` | `true`, wenn die Eigenschaft bei jedem Element definiert ist | -| `everyEquals(array, prop, value)` | `true`, wenn die Eigenschaft bei jedem Element dem Wert entspricht | -| `some(array, prop)` | `true`, wenn die Eigenschaft bei mindestens einem Element truthy ist | -| `someDefined(array, prop)` | `true`, wenn die Eigenschaft bei mindestens einem Element definiert ist | -| `someEquals(array, prop, value)` | `true`, wenn die Eigenschaft bei mindestens einem Element dem Wert entspricht | -| `someNonEmptyString(array, prop)` | `true`, wenn die Eigenschaft bei mindestens einem Element eine nicht leere Zeichenfolge ist | -| `none(array, prop)` | `true`, wenn die Eigenschaft bei jedem Element falsy ist | -| `noneDefined(array, prop)` | `true`, wenn die Eigenschaft bei jedem Element undefined ist | -| `noneEquals(array, prop, value)` | `true`, wenn die Eigenschaft bei keinem Element dem Wert entspricht | - -#### Öffentliche Assets - -Frontend-Komponenten können mit `getPublicAssetUrl` auf Dateien aus dem `public/`-Verzeichnis der App zugreifen: - -```tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -const Logo = () => Logo; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'logo', - component: Logo, -}); -``` - -Details finden Sie im Abschnitt [Öffentliche Assets](#accessing-public-assets-with-getpublicasseturl). - -#### Styling - -Frontend-Komponenten unterstützen mehrere Styling-Ansätze. Sie können verwenden: - -* **Inline-Styles** — `style={{ color: 'red' }}` -* **Twenty-UI-Komponenten** — Import aus `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar und mehr) -* **Emotion** — CSS-in-JS mit `@emotion/react` -* **Styled-components** — `styled.div`-Muster -* **Tailwind CSS** — Utility-Klassen -* **Beliebige CSS-in-JS-Bibliothek**, die mit React kompatibel ist - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Button, Tag, Status } from 'twenty-sdk/ui'; - -const StyledWidget = () => { - return ( -
-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', - name: 'styled-widget', - component: StyledWidget, -}); -``` - -
- - - -Skills definieren wiederverwendbare Anweisungen und Fähigkeiten, die KI-Agenten in Ihrem Arbeitsbereich verwenden können. Verwenden Sie `defineSkill()`, um Skills mit eingebauter Validierung zu definieren: - -```ts src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk/define'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -Hauptpunkte: -* `name` ist eine eindeutige Kennung (als Zeichenfolge) für den Skill (kebab-case empfohlen). -* `label` ist der menschenlesbare Anzeigename, der in der UI angezeigt wird. -* `content` enthält die Skill-Anweisungen — dies ist der Text, den der KI-Agent verwendet. -* `icon` (optional) legt das in der UI angezeigte Symbol fest. -* `description` (optional) liefert zusätzlichen Kontext zum Zweck des Skills. - - - - -Agenten sind KI-Assistenten, die innerhalb Ihres Workspaces leben. Verwenden Sie `defineAgent()`, um Agenten mit einem benutzerdefinierten System-Prompt zu erstellen: - -```ts src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk/define'; - -export default defineAgent({ - universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'Helps the sales team draft outreach emails and research prospects', - icon: 'IconRobot', - prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', -}); -``` - -Hauptpunkte: -* `name` ist die eindeutige Kennzeichnungs-Zeichenfolge für den Agenten (kebab-case empfohlen). -* `label` ist der in der UI angezeigte Anzeigename. -* `prompt` ist der System-Prompt, der das Verhalten des Agenten definiert. -* `description` (optional) liefert Kontext dazu, was der Agent tut. -* `icon` (optional) legt das in der UI angezeigte Symbol fest. -* `modelId` (optional) überschreibt das vom Agenten verwendete Standard-KI-Modell. - - - - -Ansichten sind gespeicherte Konfigurationen dafür, wie Datensätze eines Objekts angezeigt werden — einschließlich sichtbarer Felder, deren Reihenfolge sowie angewendeter Filter oder Gruppen. Verwenden Sie `defineView()`, um vorkonfigurierte Ansichten mit Ihrer App auszuliefern: - -```ts src/views/example-view.ts -import { defineView, ViewKey } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; - -export default defineView({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'All example items', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - icon: 'IconList', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', - fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 200, - }, - ], -}); -``` - -Hauptpunkte: -* `objectUniversalIdentifier` gibt an, auf welches Objekt diese Ansicht angewendet wird. -* `key` bestimmt den Ansichtstyp (z. B. `ViewKey.INDEX` für die Hauptlistenansicht). -* `fields` steuert, welche Spalten erscheinen und in welcher Reihenfolge. Jedes Feld referenziert einen `fieldMetadataUniversalIdentifier`. -* Für erweiterte Konfigurationen können Sie außerdem `filters`, `filterGroups`, `groups` und `fieldGroups` definieren. -* `position` steuert die Reihenfolge, wenn mehrere Ansichten für dasselbe Objekt existieren. - - - - -Navigationsmenüeinträge fügen der Workspace-Seitenleiste benutzerdefinierte Einträge hinzu. Verwenden Sie `defineNavigationMenuItem()`, um auf Ansichten, externe URLs oder Objekte zu verlinken: - -```ts src/navigation-menu-items/example-navigation-menu-item.ts -import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; -import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; - -export default defineNavigationMenuItem({ - universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', - name: 'example-navigation-menu-item', - icon: 'IconList', - color: 'blue', - position: 0, - type: NavigationMenuItemType.VIEW, - viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, -}); -``` - -Hauptpunkte: -* `type` bestimmt, worauf der Menüeintrag verweist: `NavigationMenuItemType.VIEW` für eine gespeicherte Ansicht oder `NavigationMenuItemType.LINK` für eine externe URL. -* Für Ansichtslinks setzen Sie `viewUniversalIdentifier`. Für externe Links setzen Sie `link`. -* `position` steuert die Reihenfolge in der Seitenleiste. -* `icon` und `color` (optional) passen das Erscheinungsbild an. - - - - -Seitenlayouts ermöglichen es Ihnen, das Aussehen einer Datensatzdetailseite anzupassen — welche Tabs erscheinen, welche Widgets sich in jedem Tab befinden und wie sie angeordnet sind. Verwenden Sie `definePageLayout()`, um benutzerdefinierte Layouts mit Ihrer App auszuliefern: - -```ts src/page-layouts/example-record-page-layout.ts -import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; - -export default definePageLayout({ - universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', - name: 'Example Record Page', - type: 'RECORD_PAGE', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - tabs: [ - { - universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', - title: 'Hello World', - position: 50, - icon: 'IconWorld', - layoutMode: PageLayoutTabLayoutMode.CANVAS, - widgets: [ - { - universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', - title: 'Hello World', - type: 'FRONT_COMPONENT', - configuration: { - configurationType: 'FRONT_COMPONENT', - frontComponentUniversalIdentifier: - HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, - }, - }, - ], - }, - ], -}); -``` - -Hauptpunkte: -* `type` ist typischerweise `'RECORD_PAGE'`, um die Detailansicht eines bestimmten Objekts anzupassen. -* `objectUniversalIdentifier` gibt an, auf welches Objekt dieses Layout angewendet wird. -* Jeder `tab` definiert einen Abschnitt der Seite mit `title`, `position` und `layoutMode` (`CANVAS` für ein freies Layout). -* Jedes `widget` innerhalb eines Tabs kann eine Frontend-Komponente, eine Relationenliste oder andere eingebaute Widget-Typen rendern. -* `position` auf Tabs steuert deren Reihenfolge. Verwenden Sie höhere Werte (z. B. 50), um benutzerdefinierte Tabs hinter den integrierten zu platzieren. - - -
- -## Öffentliche Assets (Ordner `public/`) - -Der Ordner `public/` im Stammverzeichnis Ihrer App enthält statische Dateien — Bilder, Icons, Schriftarten oder sonstige Assets, die Ihre App zur Laufzeit benötigt. Diese Dateien werden automatisch in Builds aufgenommen, während des Dev-Modus synchronisiert und auf den Server hochgeladen. - -Für Dateien im Verzeichnis `public/` gilt: - -* **Öffentlich zugänglich** — nach der Synchronisierung mit dem Server werden Assets unter einer öffentlichen URL bereitgestellt. Zum Zugriff ist keine Authentifizierung erforderlich. -* **In Frontend-Komponenten verfügbar** — verwenden Sie Asset-URLs, um Bilder, Icons oder andere Medien in Ihren React-Komponenten anzuzeigen. -* **In Logikfunktionen verfügbar** — referenzieren Sie Asset-URLs in E-Mails, API-Antworten oder in beliebiger serverseitiger Logik. -* **Für Marketplace-Metadaten verwendet** — die Felder `logoUrl` und `screenshots` in `defineApplication()` referenzieren Dateien aus diesem Ordner (z. B. `public/logo.png`). Diese werden im Marketplace angezeigt, wenn Ihre App veröffentlicht wird. -* **Im Dev-Modus automatisch synchronisiert** — wenn Sie in `public/` eine Datei hinzufügen, aktualisieren oder löschen, wird sie automatisch mit dem Server synchronisiert. Kein Neustart erforderlich. -* **In Builds enthalten** — `yarn twenty build` bündelt alle öffentlichen Assets in der Distributionsausgabe. - -### Zugriff auf öffentliche Assets mit `getPublicAssetUrl` - -Verwenden Sie den Helper `getPublicAssetUrl` aus `twenty-sdk`, um die vollständige URL einer Datei in Ihrem `public/`-Verzeichnis zu erhalten. Dies funktioniert sowohl in Logikfunktionen als auch in Frontend-Komponenten. - -**In einer Logikfunktion:** - -```ts src/logic-functions/send-invoice.ts -import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; - -const handler = async (): Promise => { - const logoUrl = getPublicAssetUrl('logo.png'); - const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); - - // Fetch the file content (no auth required — public endpoint) - const response = await fetch(invoiceUrl); - const buffer = await response.arrayBuffer(); - - return { logoUrl, size: buffer.byteLength }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'a1b2c3d4-...', - name: 'send-invoice', - description: 'Sends an invoice with the app logo', - timeoutSeconds: 10, - handler, -}); -``` - -**In einer Frontend-Komponente:** - -```tsx src/front-components/company-card.tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -export default defineFrontComponent(() => { - const logoUrl = getPublicAssetUrl('logo.png'); - - return App logo; -}); -``` - -Das Argument `path` ist relativ zum `public/`-Ordner Ihrer App. Sowohl `getPublicAssetUrl('logo.png')` als auch `getPublicAssetUrl('public/logo.png')` ergeben dieselbe URL — das Präfix `public/` wird, falls vorhanden, automatisch entfernt. - -## Verwendung von npm-Paketen - -Sie können in Ihrer App beliebige npm-Pakete installieren und verwenden. Sowohl Logikfunktionen als auch Frontend-Komponenten werden mit [esbuild](https://esbuild.github.io/) gebündelt, das alle Abhängigkeiten in die Ausgabe einbettet — zur Laufzeit sind keine `node_modules` erforderlich. - -### Ein Paket installieren - -```bash filename="Terminal" -yarn add axios -``` - -Importieren Sie es anschließend in Ihrem Code: - -```ts src/logic-functions/fetch-data.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import axios from 'axios'; - -const handler = async (): Promise => { - const { data } = await axios.get('https://api.example.com/data'); - - return { data }; -}; - -export default defineLogicFunction({ - universalIdentifier: '...', - name: 'fetch-data', - description: 'Fetches data from an external API', - timeoutSeconds: 10, - handler, -}); -``` - -Dasselbe funktioniert für Frontend-Komponenten: - -```tsx src/front-components/chart.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { format } from 'date-fns'; - -const DateWidget = () => { - return

Today is {format(new Date(), 'MMMM do, yyyy')}

; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Wie das Bundling funktioniert - -Der Build-Schritt verwendet esbuild, um pro Logikfunktion und pro Frontend-Komponente eine einzelne, in sich geschlossene Datei zu erzeugen. Alle importierten Pakete werden in das Bundle eingebettet. - -**Logikfunktionen** laufen in einer Node.js-Umgebung. Eingebaute Node.js-Module (`fs`, `path`, `crypto`, `http` usw.) stehen zur Verfügung und müssen nicht installiert werden. - -**Frontend-Komponenten** laufen in einem Web Worker. Eingebaute Node.js-Module sind **nicht** verfügbar — nur Browser-APIs und npm-Pakete, die in einer Browserumgebung funktionieren. - -In beiden Umgebungen stehen `twenty-client-sdk/core` und `twenty-client-sdk/metadata` als vorab bereitgestellte Module zur Verfügung — sie werden nicht gebündelt, sondern zur Laufzeit vom Server aufgelöst. - -## Entitäten mit `yarn twenty add` erstellen - -Anstatt Entitätsdateien manuell zu erstellen, können Sie den interaktiven Scaffolder verwenden: - -```bash filename="Terminal" -yarn twenty add -``` - -Dies fordert Sie auf, einen Entitätstyp auszuwählen, und führt Sie durch die erforderlichen Felder. Er erzeugt eine einsatzbereite Datei mit einem stabilen `universalIdentifier` und dem korrekten `defineEntity()`-Aufruf. - -Sie können den Entitätstyp auch direkt übergeben, um die erste Eingabeaufforderung zu überspringen: - -```bash filename="Terminal" -yarn twenty add object -yarn twenty add logicFunction -yarn twenty add frontComponent -``` - -### Verfügbare Entitätstypen - -| Entitätstyp | Befehl | Generierte Datei | -| ---------------------- | ------------------------------------ | ------------------------------------------------------- | -| Objekt | `yarn twenty add object` | `src/objects/\.ts` | -| Feld | `yarn twenty add field` | `src/fields/\.ts` | -| Logikfunktion | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | -| Frontend-Komponente | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | -| Rolle | `yarn twenty add role` | `src/roles/\.ts` | -| Skill | `yarn twenty add skill` | `src/skills/\.ts` | -| Agent | `yarn twenty add agent` | `src/agents/\.ts` | -| Ansicht | `yarn twenty add view` | `src/views/\.ts` | -| Navigationsmenüeintrag | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | -| Seitenlayout | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | - -### Was der Scaffolder generiert - -Jeder Entitätstyp hat seine eigene Vorlage. Zum Beispiel fragt `yarn twenty add object` nach: - -1. **Name (Singular)** — z. B. `invoice` -2. **Name (Plural)** — z. B. `invoices` -3. **Label (Singular)** — automatisch aus dem Namen befüllt (z. B. `Invoice`) -4. **Label (Plural)** — automatisch befüllt (z. B. `Invoices`) -5. **Ansicht und Navigationseintrag erstellen?** — wenn Sie mit Ja antworten, erzeugt der Scaffolder außerdem eine passende Ansicht und einen Sidebar-Link für das neue Objekt. - -Andere Entitätstypen haben einfachere Eingabeaufforderungen — die meisten fragen nur nach einem Namen. - -Der Entitätstyp `field` ist detaillierter: Er fragt nach Feldname, Label, Typ (aus einer Liste aller verfügbaren Feldtypen wie `TEXT`, `NUMBER`, `SELECT`, `RELATION` usw.) sowie dem `universalIdentifier` des Zielobjekts. - -### Benutzerdefinierter Ausgabepfad - -Verwenden Sie den Schalter `--path`, um die generierte Datei an einem benutzerdefinierten Ort abzulegen: - -```bash filename="Terminal" -yarn twenty add logicFunction --path src/custom-folder -``` - -## Typisierte API-Clients (twenty-client-sdk) - -Das Paket `twenty-client-sdk` stellt zwei typisierte GraphQL-Clients bereit, um aus Ihren Logikfunktionen und Frontend-Komponenten mit der Twenty-API zu interagieren. - -| Client | Importieren | Endpunkt | Generiert? | -| ------------------- | ---------------------------- | --------------------------------------------------------- | ------------------------------------ | -| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — Arbeitsbereichsdaten (Datensätze, Objekte) | Ja, zur Entwicklungs-/Build-Zeit | -| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — Arbeitsbereichskonfiguration, Datei-Uploads | Nein, wird vorgefertigt ausgeliefert | - - - - -Der `CoreApiClient` ist der Haupt-Client zum Abfragen und Ändern von Arbeitsbereichsdaten. Er wird während `yarn twenty dev` oder `yarn twenty build` **aus Ihrem Arbeitsbereichsschema generiert** und ist daher vollständig typisiert, passend zu Ihren Objekten und Feldern. - -```ts -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const client = new CoreApiClient(); - -// Query records -const { companies } = await client.query({ - companies: { - edges: { - node: { - id: true, - name: true, - domainName: { - primaryLinkLabel: true, - primaryLinkUrl: true, - }, - }, - }, - }, -}); - -// Create a record -const { createCompany } = await client.mutation({ - createCompany: { - __args: { - data: { - name: 'Acme Corp', - }, - }, - id: true, - name: true, - }, -}); -``` - -Der Client verwendet eine Selection-Set-Syntax: Übergeben Sie `true`, um ein Feld einzuschließen, verwenden Sie `__args` für Argumente, und verschachteln Sie Objekte für Relationen. Sie erhalten vollständige Autovervollständigung und Typprüfung basierend auf Ihrem Arbeitsbereichsschema. - - -**Der CoreApiClient wird zur Entwicklungs-/Build-Zeit generiert.** Wenn Sie ihn verwenden, ohne zuvor `yarn twenty dev` oder `yarn twenty build` ausgeführt zu haben, wird ein Fehler ausgelöst. Die Generierung erfolgt automatisch — die CLI inspiziert das GraphQL-Schema Ihres Arbeitsbereichs und erzeugt mit `@genql/cli` einen typisierten Client. - - -#### Verwendung von CoreSchema für Typannotationen - -`CoreSchema` stellt TypeScript-Typen bereit, die Ihren Arbeitsbereichsobjekten entsprechen — nützlich zum Typisieren von Komponentenzustand oder Funktionsparametern: - -```ts -import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; -import { useState } from 'react'; - -const [company, setCompany] = useState< - Pick | undefined ->(undefined); - -const client = new CoreApiClient(); -const result = await client.query({ - company: { - __args: { filter: { position: { eq: 1 } } }, - id: true, - name: true, - }, -}); -setCompany(result.company); -``` - - - - -`MetadataApiClient` ist im SDK bereits vorgefertigt enthalten (keine Generierung erforderlich). Er fragt den Endpunkt `/metadata` nach Arbeitsbereichskonfiguration, Anwendungen und Datei-Uploads ab. - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const metadataClient = new MetadataApiClient(); - -// List first 10 objects in the workspace -const { objects } = await metadataClient.query({ - objects: { - edges: { - node: { - id: true, - nameSingular: true, - namePlural: true, - labelSingular: true, - isCustom: true, - }, - }, - __args: { - filter: {}, - paging: { first: 10 }, - }, - }, -}); -``` - -#### Dateien hochladen - -Der `MetadataApiClient` enthält eine Methode `uploadFile`, um Dateien an Felder des Typs Datei anzuhängen: - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -| Parameter | Typ | Beschreibung | -| ---------------------------------- | -------- | --------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | Der Rohinhalt der Datei | -| `filename` | `string` | Der Name der Datei (wird für Speicherung und Anzeige verwendet) | -| `contentType` | `string` | MIME-Typ (standardmäßig `application/octet-stream`, wenn weggelassen) | -| `fieldMetadataUniversalIdentifier` | `string` | Der `universalIdentifier` des Dateityp-Felds in Ihrem Objekt | - -Hauptpunkte: -* Sie verwendet den `universalIdentifier` des Feldes (nicht dessen arbeitsbereichsspezifische ID), sodass Ihr Upload-Code in jedem Arbeitsbereich funktioniert, in dem Ihre App installiert ist. -* Die zurückgegebene `url` ist eine signierte URL, mit der Sie auf die hochgeladene Datei zugreifen können. - - - - - - Wenn Ihr Code auf Twenty ausgeführt wird (Logikfunktionen oder Frontend-Komponenten), injiziert die Plattform Anmeldedaten als Umgebungsvariablen: - - * `TWENTY_API_URL` — Basis-URL der Twenty-API - * `TWENTY_APP_ACCESS_TOKEN` — Kurzlebiger Schlüssel, der auf die Standard-Funktionsrolle Ihrer Anwendung begrenzt ist - - Sie müssen diese **nicht** an die Clients übergeben — sie lesen automatisch aus `process.env`. Die Berechtigungen des API-Schlüssels werden durch die Rolle bestimmt, auf die in `defaultRoleUniversalIdentifier` in Ihrer `application-config.ts` verwiesen wird. - - -## Ihre App testen - -Das SDK stellt programmgesteuerte APIs bereit, mit denen Sie Ihre App aus Testcode heraus bauen, bereitstellen, installieren und deinstallieren können. In Kombination mit [Vitest](https://vitest.dev/) und den typisierten API-Clients können Sie Integrationstests schreiben, die prüfen, dass Ihre App End-to-End gegen einen echten Twenty-Server funktioniert. - -### Einrichtung - -Die erzeugte App enthält bereits Vitest. Wenn Sie es manuell einrichten, installieren Sie die Abhängigkeiten: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Erstellen Sie eine `vitest.config.ts` im Stammverzeichnis Ihrer App: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Erstellen Sie eine Setup-Datei, die vor dem Testlauf überprüft, dass der Server erreichbar ist: - -```ts src/__tests__/setup-test.ts -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { beforeAll } from 'vitest'; - -const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; -const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### Programmgesteuerte SDK-APIs - -Der Subpfad `twenty-sdk/cli` exportiert Funktionen, die Sie direkt aus Testcode aufrufen können: - -| Funktion | Beschreibung | -| -------------- | ----------------------------------------------------- | -| `appBuild` | Die App bauen und optional ein Tarball packen | -| `appDeploy` | Ein Tarball auf den Server hochladen | -| `appInstall` | Die App im aktiven Arbeitsbereich installieren | -| `appUninstall` | Die App aus dem aktiven Arbeitsbereich deinstallieren | - -Jede Funktion gibt ein Ergebnisobjekt mit `success: boolean` und entweder `data` oder `error` zurück. - -### Einen Integrationstest schreiben - -Hier ist ein vollständiges Beispiel, das die App baut, bereitstellt und installiert und anschließend prüft, dass sie im Arbeitsbereich erscheint: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Tests ausführen - -Stellen Sie sicher, dass Ihr lokaler Twenty-Server läuft, und führen Sie dann Folgendes aus: - -```bash filename="Terminal" -yarn test -``` - -Oder im Watch-Modus während der Entwicklung: - -```bash filename="Terminal" -yarn test:watch -``` - -### Typprüfung - -Sie können die Typprüfung Ihrer App auch ohne Tests ausführen: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Dies führt `tsc --noEmit` aus und meldet etwaige Typfehler. - -## CLI-Referenz - -Zusätzlich zu `dev`, `build`, `add` und `typecheck` bietet die CLI Befehle zum Ausführen von Funktionen, Anzeigen von Logs und Verwalten von App-Installationen. - -### Funktionen ausführen (`yarn twenty exec`) - -Eine Logikfunktion manuell ausführen, ohne sie über HTTP, Cron oder ein Datenbankereignis auszulösen: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Funktionsprotokolle ansehen (`yarn twenty logs`) - -Ausführungsprotokolle für die Logikfunktionen Ihrer App streamen: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - - -Dies unterscheidet sich von `yarn twenty server logs`, das die Docker-Container-Logs anzeigt. `yarn twenty logs` zeigt die Funktionsausführungsprotokolle Ihrer App vom Twenty-Server. - - -### Eine App deinstallieren (`yarn twenty uninstall`) - -Entfernen Sie Ihre App aus dem aktiven Arbeitsbereich: - -```bash filename="Terminal" -yarn twenty uninstall - -# Skip the confirmation prompt -yarn twenty uninstall --yes -``` - -## Remotes verwalten - -Ein **Remote** ist ein Twenty-Server, mit dem sich Ihre App verbindet. Während der Einrichtung erstellt das Scaffolding-Tool automatisch eines für Sie. Sie können jederzeit weitere Remotes hinzufügen oder zwischen ihnen wechseln. - -```bash filename="Terminal" -# Add a new remote (opens a browser for OAuth login) -yarn twenty remote add - -# Connect to a local Twenty server (auto-detects port 2020 or 3000) -yarn twenty remote add --local - -# Add a remote non-interactively (useful for CI) -yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote - -# List all configured remotes -yarn twenty remote list - -# Switch the active remote -yarn twenty remote switch -``` - -Ihre Anmeldedaten werden in `~/.twenty/config.json` gespeichert. - -## CI mit GitHub Actions - -Das Scaffolding-Tool erzeugt einen einsatzbereiten GitHub-Actions-Workflow in `.github/workflows/ci.yml`. Er führt Ihre Integrationstests automatisch bei jedem Push auf `main` und bei Pull Requests aus. - -Der Workflow: - -1. Checkt Ihren Code aus -2. Startet einen temporären Twenty-Server mit der Aktion `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` -3. Installiert Abhängigkeiten mit `yarn install --immutable` -4. Führt `yarn test` aus, wobei `TWENTY_API_URL` und `TWENTY_API_KEY` aus den Aktionsausgaben injiziert werden. - -```yaml .github/workflows/ci.yml -name: CI - -on: - push: - branches: - - main - pull_request: {} - -env: - TWENTY_VERSION: latest - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Spawn Twenty instance - id: twenty - uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main - with: - twenty-version: ${{ env.TWENTY_VERSION }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Enable Corepack - run: corepack enable - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - - name: Install dependencies - run: yarn install --immutable - - - name: Run integration tests - run: yarn test - env: - TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} - TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} -``` - -Sie müssen keine Secrets konfigurieren — die Aktion `spawn-twenty-docker-image` startet einen flüchtigen Twenty-Server direkt im Runner und gibt die Verbindungsdetails aus. Das Secret `GITHUB_TOKEN` wird automatisch von GitHub bereitgestellt. - -Um eine bestimmte Twenty-Version statt `latest` festzulegen, ändern Sie die Umgebungsvariable `TWENTY_VERSION` oben im Workflow. +* **`yarn twenty dev`** — watches your source files and live-syncs changes to a connected Twenty server. The typed API client is regenerated automatically when the schema changes. +* **`yarn twenty build`** — compiles TypeScript, bundles logic functions and front components with esbuild, and produces a manifest. +* **Pre/post-install hooks** — optional logic functions that run during installation. See [Logic Functions](/l/de/developers/extend/apps/logic-functions) for details. + +## Nächste Schritte + + + + Define objects, fields, roles, and relations. + + + Server-side functions with HTTP, cron, and event triggers. + + + Sandboxed React components inside Twenty's UI. + + + Views, navigation items, and record page layouts. + + + AI skills and agents with custom prompts. + + + CLI commands, testing, assets, remotes, and CI. + + + Deploy to a server or publish to the marketplace. + + diff --git a/packages/twenty-docs/l/de/developers/extend/apps/cli-and-testing.mdx b/packages/twenty-docs/l/de/developers/extend/apps/cli-and-testing.mdx new file mode 100644 index 00000000000..0afa03f4c67 --- /dev/null +++ b/packages/twenty-docs/l/de/developers/extend/apps/cli-and-testing.mdx @@ -0,0 +1,434 @@ +--- +title: CLI & Testing +description: CLI commands, testing setup, public assets, npm packages, remotes, and CI configuration. +icon: terminal +--- + +## Öffentliche Assets (Ordner `public/`) + +Der Ordner `public/` im Stammverzeichnis Ihrer App enthält statische Dateien — Bilder, Icons, Schriftarten oder sonstige Assets, die Ihre App zur Laufzeit benötigt. Diese Dateien werden automatisch in Builds aufgenommen, während des Dev-Modus synchronisiert und auf den Server hochgeladen. + +Für Dateien im Verzeichnis `public/` gilt: + +* **Öffentlich zugänglich** — nach der Synchronisierung mit dem Server werden Assets unter einer öffentlichen URL bereitgestellt. Zum Zugriff ist keine Authentifizierung erforderlich. +* **In Frontend-Komponenten verfügbar** — verwenden Sie Asset-URLs, um Bilder, Icons oder andere Medien in Ihren React-Komponenten anzuzeigen. +* **In Logikfunktionen verfügbar** — referenzieren Sie Asset-URLs in E-Mails, API-Antworten oder in beliebiger serverseitiger Logik. +* **Für Marketplace-Metadaten verwendet** — die Felder `logoUrl` und `screenshots` in `defineApplication()` referenzieren Dateien aus diesem Ordner (z. B. `public/logo.png`). Diese werden im Marketplace angezeigt, wenn Ihre App veröffentlicht wird. +* **Im Dev-Modus automatisch synchronisiert** — wenn Sie in `public/` eine Datei hinzufügen, aktualisieren oder löschen, wird sie automatisch mit dem Server synchronisiert. Kein Neustart erforderlich. +* **In Builds enthalten** — `yarn twenty build` bündelt alle öffentlichen Assets in der Distributionsausgabe. + +### Zugriff auf öffentliche Assets mit `getPublicAssetUrl` + +Verwenden Sie den Helper `getPublicAssetUrl` aus `twenty-sdk`, um die vollständige URL einer Datei in Ihrem `public/`-Verzeichnis zu erhalten. Dies funktioniert sowohl in Logikfunktionen als auch in Frontend-Komponenten. + +**In einer Logikfunktion:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; + +const handler = async (): Promise => { + const logoUrl = getPublicAssetUrl('logo.png'); + const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); + + // Fetch the file content (no auth required — public endpoint) + const response = await fetch(invoiceUrl); + const buffer = await response.arrayBuffer(); + + return { logoUrl, size: buffer.byteLength }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'a1b2c3d4-...', + name: 'send-invoice', + description: 'Sends an invoice with the app logo', + timeoutSeconds: 10, + handler, +}); +``` + +**In einer Frontend-Komponente:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +Das Argument `path` ist relativ zum `public/`-Ordner Ihrer App. Sowohl `getPublicAssetUrl('logo.png')` als auch `getPublicAssetUrl('public/logo.png')` ergeben dieselbe URL — das Präfix `public/` wird, falls vorhanden, automatisch entfernt. + +## Verwendung von npm-Paketen + +Sie können in Ihrer App beliebige npm-Pakete installieren und verwenden. Sowohl Logikfunktionen als auch Frontend-Komponenten werden mit [esbuild](https://esbuild.github.io/) gebündelt, das alle Abhängigkeiten in die Ausgabe einbettet — zur Laufzeit sind keine `node_modules` erforderlich. + +### Ein Paket installieren + +```bash filename="Terminal" +yarn add axios +``` + +Importieren Sie es anschließend in Ihrem Code: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +Dasselbe funktioniert für Frontend-Komponenten: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### Wie das Bundling funktioniert + +Der Build-Schritt verwendet esbuild, um pro Logikfunktion und pro Frontend-Komponente eine einzelne, in sich geschlossene Datei zu erzeugen. Alle importierten Pakete werden in das Bundle eingebettet. + +**Logikfunktionen** laufen in einer Node.js-Umgebung. Eingebaute Node.js-Module (`fs`, `path`, `crypto`, `http` usw.) stehen zur Verfügung und müssen nicht installiert werden. + +**Frontend-Komponenten** laufen in einem Web Worker. Eingebaute Node.js-Module sind **nicht** verfügbar — nur Browser-APIs und npm-Pakete, die in einer Browserumgebung funktionieren. + +In beiden Umgebungen stehen `twenty-client-sdk/core` und `twenty-client-sdk/metadata` als vorab bereitgestellte Module zur Verfügung — sie werden nicht gebündelt, sondern zur Laufzeit vom Server aufgelöst. + +## Ihre App testen + +Das SDK stellt programmgesteuerte APIs bereit, mit denen Sie Ihre App aus Testcode heraus bauen, bereitstellen, installieren und deinstallieren können. In Kombination mit [Vitest](https://vitest.dev/) und den typisierten API-Clients können Sie Integrationstests schreiben, die prüfen, dass Ihre App End-to-End gegen einen echten Twenty-Server funktioniert. + +### Einrichtung + +Die erzeugte App enthält bereits Vitest. Wenn Sie es manuell einrichten, installieren Sie die Abhängigkeiten: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Erstellen Sie eine `vitest.config.ts` im Stammverzeichnis Ihrer App: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Erstellen Sie eine Setup-Datei, die vor dem Testlauf überprüft, dass der Server erreichbar ist: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### Programmgesteuerte SDK-APIs + +Der Subpfad `twenty-sdk/cli` exportiert Funktionen, die Sie direkt aus Testcode aufrufen können: + +| Funktion | Beschreibung | +| -------------- | ----------------------------------------------------- | +| `appBuild` | Die App bauen und optional ein Tarball packen | +| `appDeploy` | Ein Tarball auf den Server hochladen | +| `appInstall` | Die App im aktiven Arbeitsbereich installieren | +| `appUninstall` | Die App aus dem aktiven Arbeitsbereich deinstallieren | + +Jede Funktion gibt ein Ergebnisobjekt mit `success: boolean` und entweder `data` oder `error` zurück. + +### Einen Integrationstest schreiben + +Hier ist ein vollständiges Beispiel, das die App baut, bereitstellt und installiert und anschließend prüft, dass sie im Arbeitsbereich erscheint: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Tests ausführen + +Stellen Sie sicher, dass Ihr lokaler Twenty-Server läuft, und führen Sie dann Folgendes aus: + +```bash filename="Terminal" +yarn test +``` + +Oder im Watch-Modus während der Entwicklung: + +```bash filename="Terminal" +yarn test:watch +``` + +### Typprüfung + +Sie können die Typprüfung Ihrer App auch ohne Tests ausführen: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +Dies führt `tsc --noEmit` aus und meldet etwaige Typfehler. + +## CLI-Referenz + +Zusätzlich zu `dev`, `build`, `add` und `typecheck` bietet die CLI Befehle zum Ausführen von Funktionen, Anzeigen von Logs und Verwalten von App-Installationen. + +### Funktionen ausführen (`yarn twenty exec`) + +Eine Logikfunktion manuell ausführen, ohne sie über HTTP, Cron oder ein Datenbankereignis auszulösen: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute the post-install function +yarn twenty exec --postInstall +``` + +### Funktionsprotokolle ansehen (`yarn twenty logs`) + +Ausführungsprotokolle für die Logikfunktionen Ihrer App streamen: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +Dies unterscheidet sich von `yarn twenty server logs`, das die Docker-Container-Logs anzeigt. `yarn twenty logs` zeigt die Funktionsausführungsprotokolle Ihrer App vom Twenty-Server. + + +### Eine App deinstallieren (`yarn twenty uninstall`) + +Entfernen Sie Ihre App aus dem aktiven Arbeitsbereich: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` + +## Remotes verwalten + +Ein **Remote** ist ein Twenty-Server, mit dem sich Ihre App verbindet. Während der Einrichtung erstellt das Scaffolding-Tool automatisch eines für Sie. Sie können jederzeit weitere Remotes hinzufügen oder zwischen ihnen wechseln. + +```bash filename="Terminal" +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add + +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local + +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote + +# List all configured remotes +yarn twenty remote list + +# Switch the active remote +yarn twenty remote switch +``` + +Ihre Anmeldedaten werden in `~/.twenty/config.json` gespeichert. + +## CI mit GitHub Actions + +Das Scaffolding-Tool erzeugt einen einsatzbereiten GitHub-Actions-Workflow in `.github/workflows/ci.yml`. Er führt Ihre Integrationstests automatisch bei jedem Push auf `main` und bei Pull Requests aus. + +Der Workflow: + +1. Checkt Ihren Code aus +2. Startet einen temporären Twenty-Server mit der Aktion `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` +3. Installiert Abhängigkeiten mit `yarn install --immutable` +4. Führt `yarn test` aus, wobei `TWENTY_API_URL` und `TWENTY_API_KEY` aus den Aktionsausgaben injiziert werden. + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +Sie müssen keine Secrets konfigurieren — die Aktion `spawn-twenty-docker-image` startet einen flüchtigen Twenty-Server direkt im Runner und gibt die Verbindungsdetails aus. Das Secret `GITHUB_TOKEN` wird automatisch von GitHub bereitgestellt. + +Um eine bestimmte Twenty-Version statt `latest` festzulegen, ändern Sie die Umgebungsvariable `TWENTY_VERSION` oben im Workflow. diff --git a/packages/twenty-docs/l/de/developers/extend/apps/data-model.mdx b/packages/twenty-docs/l/de/developers/extend/apps/data-model.mdx new file mode 100644 index 00000000000..122b8a0decb --- /dev/null +++ b/packages/twenty-docs/l/de/developers/extend/apps/data-model.mdx @@ -0,0 +1,494 @@ +--- +title: Datenmodell +description: Define objects, fields, roles, and application metadata with the Twenty SDK. +icon: database +--- + +The `twenty-sdk` package provides `defineEntity` functions to declare your app's data model. Sie müssen `export default defineEntity({...})` verwenden, damit das SDK Ihre Entitäten erkennt. Diese Funktionen validieren Ihre Konfiguration zur Build-Zeit und bieten IDE-Autovervollständigung sowie Typsicherheit. + + + **Die Dateiorganisation liegt bei Ihnen.** + Die Entitätserkennung ist AST-basiert — das SDK findet Aufrufe von `export default defineEntity(...)`, unabhängig davon, wo sich die Datei befindet. Das Gruppieren von Dateien nach Typ (z. B. `logic-functions/`, `roles/`) ist lediglich eine Konvention, keine Voraussetzung. + + + + + +Rollen kapseln Berechtigungen für die Objekte und Aktionen Ihres Workspaces. + +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export default defineRole({ + universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', + label: 'My new role', + description: 'A role that can be used in your workspace', + canReadAllObjectRecords: false, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + canReadObjectRecords: true, + canUpdateObjectRecords: true, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + }, + ], + fieldPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + fieldUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, + canReadFieldValue: false, + canUpdateFieldValue: false, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Jede App muss genau einen Aufruf von `defineApplication` haben, der Folgendes beschreibt: + +* **Identität**: Bezeichner, Anzeigename und Beschreibung. +* **Berechtigungen**: welche Rolle ihre Funktionen und Frontend-Komponenten verwenden. +* **(Optional) Variablen**: Schlüssel–Wert-Paare, die Ihren Funktionen als Umgebungsvariablen zur Verfügung gestellt werden. +* **(Optional) Pre-/Post-Installationsfunktionen**: Logikfunktionen, die vor oder nach der Installation ausgeführt werden. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk/define'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +Notizen: +* `universalIdentifier`-Felder sind deterministische IDs, die Ihnen gehören. Erzeugen Sie sie einmal und halten Sie sie über Synchronisierungen hinweg stabil. +* `applicationVariables` werden zu Umgebungsvariablen für Ihre Funktionen und Frontend-Komponenten (z. B. ist `DEFAULT_RECIPIENT_NAME` als `process.env.DEFAULT_RECIPIENT_NAME` verfügbar). +* `defaultRoleUniversalIdentifier` muss auf eine mit `defineRole()` definierte Rolle verweisen (siehe oben). +* Pre- und Post-Installationsfunktionen werden während des Manifest-Builds automatisch erkannt — Sie müssen sie in `defineApplication()` nicht referenzieren. + +#### Marktplatz-Metadaten + +Wenn Sie planen, [Ihre App zu veröffentlichen](/l/de/developers/extend/apps/publishing), steuern diese optionalen Felder, wie Ihre App im Marktplatz erscheint: + +| Feld | Beschreibung | +| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- | +| `author` | Name des Autors oder des Unternehmens | +| `category` | App-Kategorie für die Filterung im Marktplatz | +| `logoUrl` | Pfad zu Ihrem App-Logo (z. B. `public/logo.png`) | +| `screenshots` | Array von Screenshot-Pfaden (z. B. `public/screenshot-1.png`) | +| `aboutDescription` | Längere Markdown-Beschreibung für den Tab "Info". Wenn weggelassen, verwendet der Marketplace die `README.md` des Pakets von npm | +| `websiteUrl` | Link zu Ihrer Website | +| `termsUrl` | Link zu den Nutzungsbedingungen | +| `emailSupport` | Support-E-Mail-Adresse | +| `issueReportUrl` | Link zum Issue-Tracker | + +#### Rollen und Berechtigungen + +Das Feld `defaultRoleUniversalIdentifier` in `application-config.ts` legt die Standardrolle fest, die von den Logikfunktionen und Frontend-Komponenten Ihrer App verwendet wird. Details finden Sie oben unter `defineRole`. + +* Das zur Laufzeit als `TWENTY_APP_ACCESS_TOKEN` injizierte Token wird aus dieser Rolle abgeleitet. +* Der typisierte Client ist auf die dieser Rolle gewährten Berechtigungen beschränkt. +* Befolgen Sie das Least-Privilege-Prinzip: Erstellen Sie eine dedizierte Rolle nur mit den Berechtigungen, die Ihre Funktionen benötigen. + +##### Standard-Funktionsrolle + +Wenn Sie eine neue App erzeugen, erstellt die CLI eine Standard-Rolldatei: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk/define'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + label: 'Default function role', + description: 'Default role for function Twenty client', + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [], + fieldPermissions: [], + permissionFlags: [], +}); +``` + +Der `universalIdentifier` dieser Rolle wird in `application-config.ts` als `defaultRoleUniversalIdentifier` referenziert: + +* **\*.role.ts** definiert, was die Rolle darf. +* **application-config.ts** verweist auf diese Rolle, sodass Ihre Funktionen deren Berechtigungen erben. + +Notizen: +* Beginnen Sie mit der vorab erstellten Rolle und schränken Sie sie schrittweise gemäß dem Least-Privilege-Prinzip ein. +* Ersetzen Sie `objectPermissions` und `fieldPermissions` durch die Objekte und Felder, die Ihre Funktionen tatsächlich benötigen. +* `permissionFlags` steuern den Zugriff auf Funktionen auf Plattformebene. Halten Sie sie minimal. +* Ein funktionierendes Beispiel finden Sie unter: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + + +Benutzerdefinierte Objekte beschreiben sowohl Schema als auch Verhalten für Datensätze in Ihrem Workspace. Verwenden Sie `defineObject()`, um Objekte mit eingebauter Validierung zu definieren: + +```ts postCard.object.ts +import { defineObject, FieldType } from 'twenty-sdk/define'; + +enum PostCardStatus { + DRAFT = 'DRAFT', + SENT = 'SENT', + DELIVERED = 'DELIVERED', + RETURNED = 'RETURNED', +} + +export default defineObject({ + universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', + nameSingular: 'postCard', + namePlural: 'postCards', + labelSingular: 'Post Card', + labelPlural: 'Post Cards', + description: 'A post card object', + icon: 'IconMail', + fields: [ + { + universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', + name: 'content', + type: FieldType.TEXT, + label: 'Content', + description: "Postcard's content", + icon: 'IconAbc', + }, + { + universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', + name: 'recipientName', + type: FieldType.FULL_NAME, + label: 'Recipient name', + icon: 'IconUser', + }, + { + universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', + name: 'recipientAddress', + type: FieldType.ADDRESS, + label: 'Recipient address', + icon: 'IconHome', + }, + { + universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', + name: 'status', + type: FieldType.SELECT, + label: 'Status', + icon: 'IconSend', + defaultValue: `'${PostCardStatus.DRAFT}'`, + options: [ + { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, + { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, + { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, + { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, + ], + }, + { + universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', + name: 'deliveredAt', + type: FieldType.DATE_TIME, + label: 'Delivered at', + icon: 'IconCheck', + isNullable: true, + defaultValue: null, + }, + ], +}); +``` + +Hauptpunkte: + +* Verwenden Sie `defineObject()` für eingebaute Validierung und bessere IDE-Unterstützung. +* Der `universalIdentifier` muss eindeutig und über Deployments hinweg stabil sein. +* Jedes Feld benötigt `name`, `type`, `label` und einen eigenen stabilen `universalIdentifier`. +* Das Array `fields` ist optional — Sie können Objekte ohne benutzerdefinierte Felder definieren. +* Sie können mit `yarn twenty add` neue Objekte erzeugen; der Assistent führt Sie durch Benennung, Felder und Beziehungen. + + +**Basisfelder werden automatisch erstellt.** Wenn Sie ein benutzerdefiniertes Objekt definieren, fügt Twenty automatisch Standardfelder hinzu +wie `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` und `deletedAt`. +Sie müssen diese nicht in Ihrem `fields`-Array definieren — fügen Sie nur Ihre benutzerdefinierten Felder hinzu. +Sie können Standardfelder überschreiben, indem Sie in Ihrem `fields`-Array ein Feld mit demselben Namen definieren, +dies wird jedoch nicht empfohlen. + + + + + +Verwenden Sie `defineField()`, um Objekten, die Ihnen nicht gehören — etwa Standardobjekten von Twenty (Person, Company usw.) — Felder hinzuzufügen oder Objekten aus anderen Apps. Im Gegensatz zu Inline-Feldern in `defineObject()` benötigen eigenständige Felder einen `objectUniversalIdentifier`, um anzugeben, welches Objekt sie erweitern: + +```ts src/fields/company-loyalty-tier.field.ts +import { defineField, FieldType } from 'twenty-sdk/define'; + +export default defineField({ + universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', + objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object + name: 'loyaltyTier', + type: FieldType.SELECT, + label: 'Loyalty Tier', + icon: 'IconStar', + options: [ + { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, + { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, + { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, + ], +}); +``` + +Hauptpunkte: +* Der `objectUniversalIdentifier` identifiziert das Zielobjekt. Für Standardobjekte verwenden Sie `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`, die aus `twenty-sdk` exportiert werden. +* Wenn Sie Felder inline in `defineObject()` definieren, benötigen Sie `objectUniversalIdentifier` **nicht** — er wird vom übergeordneten Objekt geerbt. +* `defineField()` ist die einzige Möglichkeit, Felder zu Objekten hinzuzufügen, die Sie nicht mit `defineObject()` erstellt haben. + + + + +Relationen verbinden Objekte miteinander. In Twenty sind Relationen stets **bidirektional** — Sie definieren beide Seiten, und jede Seite referenziert die andere. + +Es gibt zwei Relationstypen: + +| Beziehungstyp | Beschreibung | Fremdschlüssel vorhanden? | +| ------------- | ----------------------------------------------------------------------- | ------------------------- | +| `MANY_TO_ONE` | Viele Datensätze dieses Objekts verweisen auf einen Datensatz des Ziels | Ja (`joinColumnName`) | +| `ONE_TO_MANY` | Ein Datensatz dieses Objekts hat viele Datensätze des Ziels | Nein (inverse Seite) | + +#### Wie Relationen funktionieren + +Jede Relation erfordert **zwei Felder**, die sich gegenseitig referenzieren: + +1. Die **MANY_TO_ONE**-Seite — befindet sich auf dem Objekt, das den Fremdschlüssel hält +2. Die **ONE_TO_MANY**-Seite — befindet sich auf dem Objekt, dem die Sammlung gehört + +Beide Felder verwenden `FieldType.RELATION` und verweisen über `relationTargetFieldMetadataUniversalIdentifier` gegenseitig aufeinander. + +#### Beispiel: Postkarte hat viele Empfänger + +Angenommen, eine `PostCard` kann an viele `PostCardRecipient`-Datensätze gesendet werden. Jeder Empfänger gehört genau zu einer Postkarte. + +**Schritt 1: Definieren Sie die ONE_TO_MANY-Seite auf PostCard** (die "eine" Seite): + +```ts src/fields/post-card-recipients-on-post-card.field.ts +import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; +// Import from the other side +import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; + +export default defineField({ + universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCardRecipients', + label: 'Post Card Recipients', + icon: 'IconUsers', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); +``` + +**Schritt 2: Definieren Sie die MANY_TO_ONE-Seite auf PostCardRecipient** (die "viele" Seite — hält den Fremdschlüssel): + +```ts src/fields/post-card-on-post-card-recipient.field.ts +import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; +// Import from the other side +import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; + +export default defineField({ + universalIdentifier: POST_CARD_FIELD_ID, + objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + icon: 'IconMail', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, +}); +``` + + +**Zyklische Importe:** Beide Relationsfelder referenzieren gegenseitig den `universalIdentifier` des jeweils anderen. Um Probleme mit zyklischen Importen zu vermeiden, exportieren Sie Ihre Feld-IDs als benannte Konstanten aus jeder Datei und importieren Sie sie in der jeweils anderen Datei. Das Build-System löst dies zur Kompilierzeit auf. + + +#### Relationen zu Standardobjekten + +Um eine Relation mit einem integrierten Twenty-Objekt (Person, Company usw.) zu erstellen, verwenden Sie `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: + +```ts src/fields/person-on-self-hosting-user.field.ts +import { + defineField, + FieldType, + RelationType, + OnDeleteAction, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; +import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; + +export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; +export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; + +export default defineField({ + universalIdentifier: PERSON_FIELD_ID, + objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'person', + label: 'Person', + description: 'Person matching with the self hosting user', + isNullable: true, + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'personId', + }, +}); +``` + +#### Eigenschaften von Relationsfeldern + +| Eigenschaft | Erforderlich | Beschreibung | +| ------------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------- | +| `type` | Ja | Muss `FieldType.RELATION` sein | +| `relationTargetObjectMetadataUniversalIdentifier` | Ja | Der `universalIdentifier` des Zielobjekts | +| `relationTargetFieldMetadataUniversalIdentifier` | Ja | Der `universalIdentifier` des entsprechenden Felds auf dem Zielobjekt | +| `universalSettings.relationType` | Ja | `RelationType.MANY_TO_ONE` oder `RelationType.ONE_TO_MANY` | +| `universalSettings.onDelete` | Nur für MANY_TO_ONE | Was passiert, wenn der referenzierte Datensatz gelöscht wird: `CASCADE`, `SET_NULL`, `RESTRICT` oder `NO_ACTION` | +| `universalSettings.joinColumnName` | Nur für MANY_TO_ONE | Datenbankspaltenname für den Fremdschlüssel (z. B. `postCardId`) | + +#### Inline-Relationsfelder in defineObject + +Sie können Relationsfelder auch direkt innerhalb von `defineObject()` definieren. In diesem Fall lassen Sie `objectUniversalIdentifier` weg — er wird vom übergeordneten Objekt geerbt: + +```ts +export default defineObject({ + universalIdentifier: '...', + nameSingular: 'postCardRecipient', + // ... + fields: [ + { + universalIdentifier: POST_CARD_FIELD_ID, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, + }, + // ... other fields + ], +}); +``` + + + +## Entitäten mit `yarn twenty add` erstellen + +Anstatt Entitätsdateien manuell zu erstellen, können Sie den interaktiven Scaffolder verwenden: + +```bash filename="Terminal" +yarn twenty add +``` + +Dies fordert Sie auf, einen Entitätstyp auszuwählen, und führt Sie durch die erforderlichen Felder. Er erzeugt eine einsatzbereite Datei mit einem stabilen `universalIdentifier` und dem korrekten `defineEntity()`-Aufruf. + +Sie können den Entitätstyp auch direkt übergeben, um die erste Eingabeaufforderung zu überspringen: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Verfügbare Entitätstypen + +| Entitätstyp | Befehl | Generierte Datei | +| ---------------------- | ------------------------------------ | ------------------------------------------------------- | +| Objekt | `yarn twenty add object` | `src/objects/\.ts` | +| Feld | `yarn twenty add field` | `src/fields/\.ts` | +| Logikfunktion | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | +| Frontend-Komponente | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | +| Rolle | `yarn twenty add role` | `src/roles/\.ts` | +| Skill | `yarn twenty add skill` | `src/skills/\.ts` | +| Agent | `yarn twenty add agent` | `src/agents/\.ts` | +| Ansicht | `yarn twenty add view` | `src/views/\.ts` | +| Navigationsmenüeintrag | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | +| Seitenlayout | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | + +### Was der Scaffolder generiert + +Jeder Entitätstyp hat seine eigene Vorlage. Zum Beispiel fragt `yarn twenty add object` nach: + +1. **Name (Singular)** — z. B. `invoice` +2. **Name (Plural)** — z. B. `invoices` +3. **Label (Singular)** — automatisch aus dem Namen befüllt (z. B. `Invoice`) +4. **Label (Plural)** — automatisch befüllt (z. B. `Invoices`) +5. **Ansicht und Navigationseintrag erstellen?** — wenn Sie mit Ja antworten, erzeugt der Scaffolder außerdem eine passende Ansicht und einen Sidebar-Link für das neue Objekt. + +Andere Entitätstypen haben einfachere Eingabeaufforderungen — die meisten fragen nur nach einem Namen. + +Der Entitätstyp `field` ist detaillierter: Er fragt nach Feldname, Label, Typ (aus einer Liste aller verfügbaren Feldtypen wie `TEXT`, `NUMBER`, `SELECT`, `RELATION` usw.) sowie dem `universalIdentifier` des Zielobjekts. + +### Benutzerdefinierter Ausgabepfad + +Verwenden Sie den Schalter `--path`, um die generierte Datei an einem benutzerdefinierten Ort abzulegen: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` diff --git a/packages/twenty-docs/l/de/developers/extend/apps/front-components.mdx b/packages/twenty-docs/l/de/developers/extend/apps/front-components.mdx new file mode 100644 index 00000000000..7b9eb333360 --- /dev/null +++ b/packages/twenty-docs/l/de/developers/extend/apps/front-components.mdx @@ -0,0 +1,419 @@ +--- +title: Frontend-Komponenten +description: Build React components that render inside Twenty's UI with sandboxed isolation. +icon: window-maximize +--- + +Frontend-Komponenten sind React-Komponenten, die direkt innerhalb der Twenty-UI gerendert werden. Sie laufen in einem **isolierten Web Worker** unter Verwendung von Remote DOM — Ihr Code wird in einer Sandbox ausgeführt, rendert jedoch nativ auf der Seite, nicht in einem iframe. + +## Wo Front-Komponenten verwendet werden können + +Front-Komponenten können an zwei Stellen innerhalb von Twenty gerendert werden: + +* **Seitenpanel** — Nicht-Headless-Front-Komponenten werden im rechten Seitenpanel geöffnet. Dies ist das Standardverhalten, wenn eine Front-Komponente über das Befehlsmenü ausgelöst wird. +* **Widgets (Dashboards und Datensatzseiten)** — Front-Komponenten können als Widgets in Seitenlayouts eingebettet werden. Beim Konfigurieren eines Dashboards oder eines Datensatzseiten-Layouts können Benutzer ein Front-Komponenten-Widget hinzufügen. + +## Einfaches Beispiel + +Der schnellste Weg, eine Frontend-Komponente in Aktion zu sehen, ist, sie als Befehl zu registrieren. Das Hinzufügen eines `command`-Felds mit `isPinned: true` lässt sie als Schnellaktionsschaltfläche oben rechts auf der Seite erscheinen — kein Seitenlayout erforderlich: + +```tsx src/front-components/hello-world.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; + +const HelloWorld = () => { + return ( +
+

Hello from my app!

+

This component renders inside Twenty.

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, +}); +``` + +Nach dem Synchronisieren mit `yarn twenty dev` (oder durch einmaliges Ausführen von `yarn twenty dev --once`) erscheint die Schnellaktion oben rechts auf der Seite: + +
+ Schnellaktionsschaltfläche oben rechts +
+ +Klicken Sie darauf, um die Komponente inline zu rendern. + +## Konfigurationsfelder + +| Feld | Erforderlich | Beschreibung | +| --------------------- | ------------ | ---------------------------------------------------------------------------------------- | +| `universalIdentifier` | Ja | Stabile eindeutige ID für diese Komponente | +| `component` | Ja | Eine React-Komponentenfunktion | +| `name` | Nein | Anzeigename | +| `description` | Nein | Beschreibung dessen, was die Komponente macht | +| `isHeadless` | Nein | Auf `true` setzen, wenn die Komponente keine sichtbare UI hat (siehe unten) | +| `command` | Nein | Die Komponente als Befehl registrieren (siehe unten [Befehlsoptionen](#command-options)) | + +## Eine Frontend-Komponente auf einer Seite platzieren + +Über Befehle hinaus können Sie eine Frontend-Komponente direkt in eine Datensatzseite einbetten, indem Sie sie als Widget in einem **Seitenlayout** hinzufügen. Details finden Sie im Abschnitt [definePageLayout](/l/de/developers/extend/apps/skills-and-agents#definepagelayout). + +## Headless vs. Nicht-Headless + +Front-Komponenten gibt es in zwei Rendering-Modi, die durch die Option `isHeadless` gesteuert werden: + +**Nicht-Headless (Standard)** — Die Komponente rendert eine sichtbare UI. Wird sie über das Befehlsmenü ausgelöst, öffnet sie sich im Seitenpanel. Dies ist das Standardverhalten, wenn `isHeadless` `false` ist oder weggelassen wird. + +**Headless (`isHeadless: true`)** — Die Komponente wird unsichtbar im Hintergrund gemountet. Sie öffnet das Seitenpanel nicht. Headless-Komponenten sind für Aktionen konzipiert, die Logik ausführen und sich anschließend selbst unmounten — zum Beispiel das Ausführen einer asynchronen Aufgabe, das Navigieren zu einer Seite oder das Anzeigen eines Bestätigungsdialogs. Sie lassen sich gut mit den unten beschriebenen SDK-Command-Komponenten kombinieren. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Da die Komponente `null` zurückgibt, überspringt Twenty das Rendern eines Containers dafür — im Layout entsteht kein Leerraum. Die Komponente hat dennoch Zugriff auf alle Hooks und die Host-Kommunikations-API. + +## SDK-Command-Komponenten + +Das Paket `twenty-sdk` stellt vier Command-Hilfskomponenten bereit, die für Headless-Front-Komponenten ausgelegt sind. Jede Komponente führt beim Mounten eine Aktion aus, behandelt Fehler durch Anzeige einer Snackbar-Benachrichtigung und unmountet die Front-Komponente nach Abschluss automatisch. + +Importieren Sie sie aus `twenty-sdk/command`: + +* **`Command`** — Führt einen asynchronen Callback über das Prop `execute` aus. +* **`CommandLink`** — Navigiert zu einem App-Pfad. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Öffnet einen Bestätigungsdialog. Bestätigt der Benutzer, wird der Callback `execute` ausgeführt. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Öffnet eine bestimmte Seite im Seitenpanel. Props: `page`, `pageTitle`, `pageIcon`. + +Hier ist ein vollständiges Beispiel einer Headless-Front-Komponente, die `Command` verwendet, um eine Aktion aus dem Befehlsmenü auszuführen: + +```tsx src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +Und ein Beispiel, das `CommandModal` verwendet, um vor der Ausführung um Bestätigung zu bitten: + +```tsx src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +## Zugriff auf den Laufzeitkontext + +Verwenden Sie innerhalb Ihrer Komponente SDK-Hooks, um auf den aktuellen Benutzer, den Datensatz und die Komponenteninstanz zuzugreifen: + +```tsx src/front-components/record-info.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk/front-component'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Verfügbare Hooks: + +| Hook | Gibt zurück | Beschreibung | +| --------------------------------------------- | -------------------- | --------------------------------------------------------------------------- | +| `useUserId()` | `string` oder `null` | Die ID des aktuellen Benutzers | +| `useRecordId()` | `string` oder `null` | Die ID des aktuellen Datensatzes (wenn auf einer Datensatzseite platziert) | +| `useFrontComponentId()` | `Zeichenkette` | Die ID dieser Komponenteninstanz | +| `useFrontComponentExecutionContext(selector)` | variiert | Zugriff auf den vollständigen Ausführungskontext mit einer Selektorfunktion | + +## Host-Kommunikations-API + +Frontend-Komponenten können Navigation, Modals und Benachrichtigungen mittels Funktionen aus `twenty-sdk` auslösen: + +| Funktion | Beschreibung | +| ----------------------------------------------- | ----------------------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Zu einer Seite in der App navigieren | +| `openSidePanelPage(params)` | Ein Seitenpanel öffnen | +| `closeSidePanel()` | Seitenpanel schließen | +| `openCommandConfirmationModal(params)` | Einen Bestätigungsdialog anzeigen | +| `enqueueSnackbar(params)` | Eine Toast-Benachrichtigung anzeigen | +| `unmountFrontComponent()` | Die Komponente entfernen | +| `updateProgress(progress)` | Einen Fortschrittsindikator aktualisieren | + +Hier ist ein Beispiel, das die Host-API verwendet, um nach Abschluss einer Aktion eine Snackbar anzuzeigen und das Seitenpanel zu schließen: + +```tsx src/front-components/archive-record.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId } from 'twenty-sdk/front-component'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + +## Befehlsoptionen + +Das Hinzufügen eines `command`-Felds zu `defineFrontComponent` registriert die Komponente im Befehlsmenü (Cmd+K). Wenn `isPinned` `true` ist, erscheint sie außerdem als Schnellaktionsschaltfläche oben rechts auf der Seite. + +| Feld | Erforderlich | Beschreibung | +| --------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `universalIdentifier` | Ja | Stabile eindeutige ID für den Befehl | +| `label` | Ja | Vollständiges Label, das im Befehlsmenü (Cmd+K) angezeigt wird | +| `shortLabel` | Nein | Kürzeres Label, das auf der angehefteten Schnellaktionsschaltfläche angezeigt wird | +| `icon` | Nein | Neben dem Label angezeigter Icon-Name (z. B. 'IconBolt', 'IconSend') | +| `isPinned` | Nein | Bei `true` wird der Befehl als Schnellaktionsschaltfläche oben rechts auf der Seite angezeigt | +| `availabilityType` | Nein | Steuert, wo der Befehl erscheint: 'GLOBAL' (immer verfügbar), 'RECORD_SELECTION' (nur wenn Datensätze ausgewählt sind) oder 'FALLBACK' (wird angezeigt, wenn keine anderen Befehle passen) | +| `availabilityObjectUniversalIdentifier` | Nein | Beschränken Sie den Befehl auf Seiten eines bestimmten Objekttyps (z. B. nur bei Company-Datensätzen) | +| `conditionalAvailabilityExpression` | Nein | Ein boolescher Ausdruck, um dynamisch zu steuern, ob der Befehl sichtbar ist (siehe unten) | + +## Bedingte Verfügbarkeitsausdrücke + +Mit dem Feld `conditionalAvailabilityExpression` können Sie basierend auf dem aktuellen Seitenkontext steuern, wann ein Befehl sichtbar ist. Importieren Sie typisierte Variablen und Operatoren aus `twenty-sdk`, um Ausdrücke zu erstellen: + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk/front-component'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Kontextvariablen** — sie repräsentieren den aktuellen Zustand der Seite: + +| Variable | Typ | Beschreibung | +| ------------------------------ | -------------- | --------------------------------------------------------------- | +| `pageType` | `Zeichenkette` | Aktueller Seitentyp (z. B. 'RecordIndexPage', 'RecordShowPage') | +| `isInSidePanel` | `boolean` | Ob die Komponente in einem Seitenpanel gerendert wird | +| `numberOfSelectedRecords` | `number` | Anzahl der aktuell ausgewählten Datensätze | +| `isSelectAll` | `boolean` | Ob „Alle auswählen“ aktiv ist | +| `selectedRecords` | `array` | Die ausgewählten Datensatzobjekte | +| `favoriteRecordIds` | `array` | IDs der favorisierten Datensätze | +| `objectPermissions` | `object` | Berechtigungen für den aktuellen Objekttyp | +| `targetObjectReadPermissions` | `object` | Leseberechtigungen für das Zielobjekt | +| `targetObjectWritePermissions` | `object` | Schreibberechtigungen für das Zielobjekt | +| `featureFlags` | `object` | Aktive Feature-Flags | +| `objectMetadataItem` | `object` | Metadaten des aktuellen Objekttyps | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Ob die aktuelle Ansicht einen Soft-Delete-Filter hat | + +**Operatoren** — Variablen zu booleschen Ausdrücken kombinieren: + +| Operator | Beschreibung | +| ----------------------------------- | ------------------------------------------------------------------------------------------- | +| `isDefined(value)` | `true`, wenn der Wert nicht null/undefined ist | +| `isNonEmptyString(value)` | `true`, wenn der Wert eine nicht leere Zeichenfolge ist | +| `includes(array, value)` | `true`, wenn das Array den Wert enthält | +| `includesEvery(array, prop, value)` | `true`, wenn die Eigenschaft jedes Elements den Wert enthält | +| `every(array, prop)` | `true`, wenn die Eigenschaft bei jedem Element truthy ist | +| `everyDefined(array, prop)` | `true`, wenn die Eigenschaft bei jedem Element definiert ist | +| `everyEquals(array, prop, value)` | `true`, wenn die Eigenschaft bei jedem Element dem Wert entspricht | +| `some(array, prop)` | `true`, wenn die Eigenschaft bei mindestens einem Element truthy ist | +| `someDefined(array, prop)` | `true`, wenn die Eigenschaft bei mindestens einem Element definiert ist | +| `someEquals(array, prop, value)` | `true`, wenn die Eigenschaft bei mindestens einem Element dem Wert entspricht | +| `someNonEmptyString(array, prop)` | `true`, wenn die Eigenschaft bei mindestens einem Element eine nicht leere Zeichenfolge ist | +| `none(array, prop)` | `true`, wenn die Eigenschaft bei jedem Element falsy ist | +| `noneDefined(array, prop)` | `true`, wenn die Eigenschaft bei jedem Element undefined ist | +| `noneEquals(array, prop, value)` | `true`, wenn die Eigenschaft bei keinem Element dem Wert entspricht | + +## Öffentliche Assets + +Frontend-Komponenten können mit `getPublicAssetUrl` auf Dateien aus dem `public/`-Verzeichnis der App zugreifen: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +Details finden Sie im Abschnitt [Öffentliche Assets](/l/de/developers/extend/apps/cli-and-testing#public-assets-public-folder). + +## Styling + +Frontend-Komponenten unterstützen mehrere Styling-Ansätze. Sie können verwenden: + +* **Inline-Styles** — `style={{ color: 'red' }}` +* **Twenty-UI-Komponenten** — Import aus `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar und mehr) +* **Emotion** — CSS-in-JS mit `@emotion/react` +* **Styled-components** — `styled.div`-Muster +* **Tailwind CSS** — Utility-Klassen +* **Beliebige CSS-in-JS-Bibliothek**, die mit React kompatibel ist + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` diff --git a/packages/twenty-docs/l/de/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/de/developers/extend/apps/getting-started.mdx index 186493d72f5..5b38b6b9e1d 100644 --- a/packages/twenty-docs/l/de/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/de/developers/extend/apps/getting-started.mdx @@ -1,12 +1,9 @@ --- title: Erste Schritte +icon: rocket description: Erstellen Sie in wenigen Minuten Ihre erste Twenty-App. --- - -Apps befinden sich derzeit in der Alpha-Phase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter. - - ## Was sind Apps? Apps ermöglichen es Ihnen, Twenty mit benutzerdefinierten Objekten, Feldern, Logikfunktionen, Frontend-Komponenten, KI-Fähigkeiten und mehr zu erweitern — alles als Code verwaltet. Anstatt alles über die UI zu konfigurieren, definieren Sie Ihr Datenmodell und Ihre Logik in TypeScript und stellen es in einem oder mehreren Workspaces bereit. diff --git a/packages/twenty-docs/l/de/developers/extend/apps/layout.mdx b/packages/twenty-docs/l/de/developers/extend/apps/layout.mdx new file mode 100644 index 00000000000..3554c6d613b --- /dev/null +++ b/packages/twenty-docs/l/de/developers/extend/apps/layout.mdx @@ -0,0 +1,131 @@ +--- +title: Layout +description: Define views, navigation menu items, and page layouts to shape how your app appears in Twenty. +icon: table-columns +--- + +Layout entities control how your app surfaces inside Twenty's UI — what lives in the sidebar, which saved views ship with the app, and how a record detail page is arranged. + +## Layout concepts + +| Concept | What it controls | Entität | +| ------------------------ | --------------------------------------------------------------------------------- | -------------------------- | +| **View** | A saved list configuration for an object — visible fields, order, filters, groups | `defineView` | +| **Navigation Menu Item** | An entry in the left sidebar that links to a view or an external URL | `defineNavigationMenuItem` | +| **Page Layout** | The tabs and widgets that make up a record's detail page | `definePageLayout` | + +Views, navigation items, and page layouts reference each other by `universalIdentifier`: + +* A **navigation menu item** of type `VIEW` points at a `defineView` identifier, so the sidebar link opens that saved view. +* A **page layout** of type `RECORD_PAGE` targets an object and can embed [front components](/l/de/developers/extend/apps/front-components) inside its tabs as widgets. + + + + +Ansichten sind gespeicherte Konfigurationen dafür, wie Datensätze eines Objekts angezeigt werden — einschließlich sichtbarer Felder, deren Reihenfolge sowie angewendeter Filter oder Gruppen. Verwenden Sie `defineView()`, um vorkonfigurierte Ansichten mit Ihrer App auszuliefern: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +Hauptpunkte: +* `objectUniversalIdentifier` gibt an, auf welches Objekt diese Ansicht angewendet wird. +* `key` bestimmt den Ansichtstyp (z. B. `ViewKey.INDEX` für die Hauptlistenansicht). +* `fields` steuert, welche Spalten erscheinen und in welcher Reihenfolge. Jedes Feld referenziert einen `fieldMetadataUniversalIdentifier`. +* Für erweiterte Konfigurationen können Sie außerdem `filters`, `filterGroups`, `groups` und `fieldGroups` definieren. +* `position` steuert die Reihenfolge, wenn mehrere Ansichten für dasselbe Objekt existieren. + + + + +Navigationsmenüeinträge fügen der Workspace-Seitenleiste benutzerdefinierte Einträge hinzu. Verwenden Sie `defineNavigationMenuItem()`, um auf Ansichten, externe URLs oder Objekte zu verlinken: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Hauptpunkte: +* `type` bestimmt, worauf der Menüeintrag verweist: `NavigationMenuItemType.VIEW` für eine gespeicherte Ansicht oder `NavigationMenuItemType.LINK` für eine externe URL. +* Für Ansichtslinks setzen Sie `viewUniversalIdentifier`. Für externe Links setzen Sie `link`. +* `position` steuert die Reihenfolge in der Seitenleiste. +* `icon` und `color` (optional) passen das Erscheinungsbild an. + + + + +Seitenlayouts ermöglichen es Ihnen, das Aussehen einer Datensatzdetailseite anzupassen — welche Tabs erscheinen, welche Widgets sich in jedem Tab befinden und wie sie angeordnet sind. Verwenden Sie `definePageLayout()`, um benutzerdefinierte Layouts mit Ihrer App auszuliefern: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Hauptpunkte: +* `type` ist typischerweise `'RECORD_PAGE'`, um die Detailansicht eines bestimmten Objekts anzupassen. +* `objectUniversalIdentifier` gibt an, auf welches Objekt dieses Layout angewendet wird. +* Jeder `tab` definiert einen Abschnitt der Seite mit `title`, `position` und `layoutMode` (`CANVAS` für ein freies Layout). +* Jedes `widget` innerhalb eines Tabs kann eine Frontend-Komponente, eine Relationenliste oder andere eingebaute Widget-Typen rendern. +* `position` auf Tabs steuert deren Reihenfolge. Verwenden Sie höhere Werte (z. B. 50), um benutzerdefinierte Tabs hinter den integrierten zu platzieren. + + + diff --git a/packages/twenty-docs/l/de/developers/extend/apps/logic-functions.mdx b/packages/twenty-docs/l/de/developers/extend/apps/logic-functions.mdx new file mode 100644 index 00000000000..2a57fda128e --- /dev/null +++ b/packages/twenty-docs/l/de/developers/extend/apps/logic-functions.mdx @@ -0,0 +1,559 @@ +--- +title: Logikfunktionen +description: Define server-side TypeScript functions with HTTP, cron, and database event triggers. +icon: bolt +--- + +Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents. + + + + +Jede Funktionsdatei verwendet `defineLogicFunction()`, um eine Konfiguration mit einem Handler und optionalen Triggern zu exportieren. + +```ts src/logic-functions/createPostCard.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; + +const handler = async (params: RoutePayload) => { + const client = new CoreApiClient(); + const name = 'name' in params.queryStringParameters + ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' + : 'Hello world'; + + const result = await client.mutation({ + createPostCard: { + __args: { data: { name } }, + id: true, + name: true, + }, + }); + return result; +}; + +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'create-new-post-card', + timeoutSeconds: 2, + handler, + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: true, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ +}); +``` + +Verfügbare Trigger-Typen: +* **httpRoute**: Stellt Ihre Funktion unter einem HTTP-Pfad und einer Methode **unter dem Endpunkt `/s/`** bereit: +> z. B. `path: '/post-card/create'` ist unter `https://your-twenty-server.com/s/post-card/create` aufrufbar +* **cron**: Führt Ihre Funktion nach Zeitplan mithilfe eines CRON-Ausdrucks aus. +* **databaseEvent**: Wird bei Lebenszyklusereignissen von Workspace-Objekten ausgeführt. Wenn die Ereignisoperation `updated` ist, können bestimmte zu überwachende Felder im Array `updatedFields` angegeben werden. Wenn das Array undefiniert oder leer ist, löst jede Aktualisierung die Funktion aus. +> z. B. `person.updated`, `*.created`, `company.*` + + +Sie können eine Funktion auch manuell über die CLI ausführen: + +```bash filename="Terminal" +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' +``` + +```bash filename="Terminal" +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + +Sie können Protokolle mit folgendem Befehl ansehen: + +```bash filename="Terminal" +yarn twenty logs +``` + + +#### Routen-Trigger-Payload + +Wenn ein Route-Trigger Ihre Logikfunktion aufruft, erhält sie ein `RoutePayload`-Objekt, das dem [AWS-HTTP-API-v2-Format](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) folgt. +Importieren Sie den Typ `RoutePayload` aus `twenty-sdk`: + +```ts +import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; + +const handler = async (event: RoutePayload) => { + const { headers, queryStringParameters, pathParameters, body } = event; + const { method, path } = event.requestContext.http; + + return { message: 'Success' }; +}; +``` + +Der Typ `RoutePayload` hat die folgende Struktur: + + | Eigenschaft | Typ | Beschreibung | Beispiel | + | ---------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record\` | HTTP-Header (nur die in `forwardedRequestHeaders` aufgelisteten) | siehe Abschnitt unten | + | `queryStringParameters` | `Record\` | Query-String-Parameter (mehrere Werte mit Kommas verbunden) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record\` | Aus dem Routenmuster extrahierte Pfadparameter | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Geparster Request-Body (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | Gibt an, ob der Body Base64-codiert ist | | + | `requestContext.http.method` | `Zeichenkette` | HTTP-Methode (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `Zeichenkette` | Rohpfad der Anfrage | | + + +#### forwardedRequestHeaders + +Standardmäßig werden HTTP-Header von eingehenden Anfragen aus Sicherheitsgründen nicht an Ihre Logikfunktion weitergegeben. +Um auf bestimmte Header zuzugreifen, listen Sie diese im Array `forwardedRequestHeaders` auf: + +```ts +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'webhook-handler', + handler, + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, +}); +``` + +Greifen Sie in Ihrem Handler wie folgt auf die weitergeleiteten Header zu: + +```ts +const handler = async (event: RoutePayload) => { + const signature = event.headers['x-webhook-signature']; + const contentType = event.headers['content-type']; + + // Validate webhook signature... + return { received: true }; +}; +``` + + +Header-Namen werden in Kleinbuchstaben normalisiert. Greifen Sie mit Schlüsseln in Kleinbuchstaben darauf zu (z. B. `event.headers['content-type']`). + + +#### Eine Funktion als Tool bereitstellen + +Logikfunktionen können als **Tools** für KI-Agenten und Workflows verfügbar gemacht werden. Wenn eine Funktion als Tool markiert ist, wird sie von den KI-Funktionen von Twenty auffindbar und kann in Workflow-Automatisierungen verwendet werden. + +Um eine Logikfunktion als Tool zu markieren, setzen Sie `isTool: true`: + +```ts src/logic-functions/enrich-company.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const handler = async (params: { companyName: string; domain?: string }) => { + const client = new CoreApiClient(); + + const result = await client.mutation({ + createTask: { + __args: { + data: { + title: `Enrich data for ${params.companyName}`, + body: `Domain: ${params.domain ?? 'unknown'}`, + }, + }, + id: true, + }, + }); + + return { taskId: result.createTask.id }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + name: 'enrich-company', + description: 'Enrich a company record with external data', + timeoutSeconds: 10, + handler, + isTool: true, +}); +``` + +Hauptpunkte: + +* Sie können `isTool` mit Triggern kombinieren — eine Funktion kann gleichzeitig sowohl ein Tool (von KI-Agenten aufrufbar) als auch durch Ereignisse ausgelöst werden. +* **`toolInputSchema`** (optional): Ein JSON-Schema-Objekt, das die Parameter beschreibt, die Ihre Funktion akzeptiert. Das Schema wird automatisch durch statische Analyse des Quellcodes ermittelt, Sie können es jedoch auch explizit festlegen: + +```ts +export default defineLogicFunction({ + ..., + toolInputSchema: { + type: 'object', + properties: { + companyName: { + type: 'string', + description: 'The name of the company to enrich', + }, + domain: { + type: 'string', + description: 'The company website domain (optional)', + }, + }, + required: ['companyName'], + }, +}); +``` + + +**Schreiben Sie eine gute `description`.** KI-Agenten verlassen sich auf das `description`-Feld der Funktion, um zu entscheiden, wann das Tool verwendet werden soll. Seien Sie konkret darin, was das Tool tut und wann es aufgerufen werden soll. + + + + + +Eine Post-Installationsfunktion ist eine Logikfunktion, die automatisch ausgeführt wird, nachdem Ihre App in einem Arbeitsbereich installiert wurde. Der Server führt sie **nach** der Synchronisierung der Metadaten der App und der Generierung des SDK-Clients aus, sodass der Arbeitsbereich vollständig einsatzbereit ist und das neue Schema bereitsteht. Typische Anwendungsfälle umfassen das Befüllen von Standarddaten, das Erstellen anfänglicher Datensätze, das Konfigurieren von Arbeitsbereichseinstellungen oder das Bereitstellen von Ressourcen bei Diensten von Drittanbietern. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + shouldRunSynchronously: false, + handler, +}); +``` + +Sie können die Post-Installationsfunktion auch jederzeit manuell über die CLI ausführen: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Hauptpunkte: +* Post-Installationsfunktionen verwenden `definePostInstallLogicFunction()` — eine spezialisierte Variante, die Trigger-Einstellungen (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) weglässt. +* Der Handler erhält ein `InstallPayload` mit `{ previousVersion?: string; newVersion: string }` — `newVersion` ist die zu installierende Version, und `previousVersion` ist die zuvor installierte Version (oder `undefined` bei einer Neuinstallation). Verwenden Sie diese Werte, um Neuinstallationen von Upgrades zu unterscheiden und versionsspezifische Migrationslogik auszuführen. +* **Wann der Hook ausgeführt wird**: standardmäßig nur bei Neuinstallationen. Übergeben Sie `shouldRunOnVersionUpgrade: true`, wenn er auch beim Upgrade der App von einer vorherigen Version ausgeführt werden soll. Wenn weggelassen, ist das Flag standardmäßig `false` und Upgrades überspringen den Hook. +* **Ausführungsmodell — standardmäßig asynchron, synchron optional**: Das Flag `shouldRunSynchronously` steuert, *wie* Post-Install ausgeführt wird. + * `shouldRunSynchronously: false` *(Standard)* — der Hook wird **in die Nachrichtenwarteschlange eingereiht** mit `retryLimit: 3` und läuft asynchron in einem Worker. Die Installationsantwort kommt zurück, sobald der Job eingereiht ist, sodass ein langsamer oder fehlschlagender Handler den Aufrufer nicht blockiert. Der Worker versucht es bis zu dreimal erneut. **Verwenden Sie dies für lang laufende Jobs** — das Befüllen großer Datensätze, Aufrufe langsamer Drittanbieter-APIs, Bereitstellung externer Ressourcen, alles, was ein vernünftiges HTTP-Antwortfenster überschreiten könnte. + * `shouldRunSynchronously: true` — der Hook wird **inline während des Installationsablaufs** ausgeführt (gleicher Executor wie bei Pre-Install). Die Installationsanforderung blockiert, bis der Handler fertig ist, und wenn er einen Fehler wirft, erhält der Installationsaufrufer einen `POST_INSTALL_ERROR`. Keine automatischen Wiederholungen. **Verwenden Sie dies für schnelle Aufgaben, die vor der Antwort abgeschlossen sein müssen** — z. B. um dem Benutzer einen Validierungsfehler auszugeben oder für eine schnelle Einrichtung, auf die der Client unmittelbar nach der Rückkehr des Installationsaufrufs angewiesen ist. Beachten Sie, dass die Metadatenmigration bereits angewendet wurde, wenn Post-Install läuft, sodass ein Fehler im Synchronmodus die Schemaänderungen **nicht** rückgängig macht — er zeigt lediglich den Fehler an. +* Stellen Sie sicher, dass Ihr Handler idempotent ist. Im asynchronen Modus kann die Warteschlange bis zu dreimal erneut versuchen; in beiden Modi kann der Hook bei Upgrades erneut laufen, wenn `shouldRunOnVersionUpgrade: true`. +* Die Umgebungsvariablen `APPLICATION_ID`, `APP_ACCESS_TOKEN` und `API_URL` sind im Handler verfügbar (wie bei jeder anderen Logikfunktion), sodass Sie die Twenty API mit einem auf Ihre App beschränkten Anwendungszugriffstoken aufrufen können. +* Pro Anwendung ist nur eine Post-Installationsfunktion zulässig. Der Manifest-Build schlägt fehl, wenn mehr als eine erkannt wird. +* Die `universalIdentifier`, `shouldRunOnVersionUpgrade` und `shouldRunSynchronously` der Funktion werden während des Builds automatisch dem Anwendungsmanifest unter dem Feld `postInstallLogicFunction` hinzugefügt — Sie müssen sie in `defineApplication()` nicht referenzieren. +* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Einrichtungsvorgänge wie Daten-Seeding zu ermöglichen. +* **Nicht im Dev-Modus ausgeführt**: Wenn eine App lokal registriert ist (über `yarn twenty dev`), überspringt der Server den Installationsablauf vollständig und synchronisiert Dateien direkt über den CLI-Watcher — daher läuft Post-Install im Dev-Modus nie, unabhängig von `shouldRunSynchronously`. Verwenden Sie `yarn twenty exec --postInstall`, um es manuell gegen einen laufenden Workspace auszulösen. + + + + +Eine Pre-Install-Funktion ist eine Logikfunktion, die automatisch während der Installation ausgeführt wird, **bevor die Metadatenmigration des Workspaces angewendet wird**. Sie hat die gleiche Payload-Struktur wie Post-Install (`InstallPayload`), ist aber früher im Installationsablauf positioniert, sodass sie Zustände vorbereiten kann, von denen die bevorstehende Migration abhängt — typische Anwendungsfälle sind das Sichern von Daten, die Validierung der Kompatibilität mit dem neuen Schema oder das Archivieren von Datensätzen, die umstrukturiert oder entfernt werden sollen. + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +Sie können die Pre-Installationsfunktion auch jederzeit manuell über die CLI ausführen: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Hauptpunkte: +* Pre-Install-Funktionen verwenden `definePreInstallLogicFunction()` — dieselbe spezialisierte Konfiguration wie bei Post-Install, nur an einen anderen Lifecycle-Slot gebunden. +* Sowohl Pre- als auch Post-Install-Handler erhalten denselben `InstallPayload`-Typ: `{ previousVersion?: string; newVersion: string }`. Importieren Sie ihn einmal und verwenden Sie ihn für beide Hooks wieder. +* **Wann der Hook ausgeführt wird**: positioniert direkt vor der Metadatenmigration des Workspaces (`synchronizeFromManifest`). Vor der Ausführung führt der Server einen rein additiven "pared-down sync" durch, der die Pre-Install-Funktion der **neuen** Version in den Workspace-Metadaten registriert — sonst wird nichts angefasst — und führt sie dann aus. Da dieser Sync nur additiv ist, sind die Objekte, Felder und Daten der vorherigen Version noch intakt, wenn Ihr Handler läuft: Sie können den Zustand vor der Migration gefahrlos lesen und sichern. +* **Ausführungsmodell**: Pre-Install wird **synchron** ausgeführt und **blockiert die Installation**. Wenn der Handler einen Fehler wirft, wird die Installation abgebrochen, bevor Schemaänderungen angewendet werden — der Workspace verbleibt in der vorherigen Version in einem konsistenten Zustand. Das ist beabsichtigt: Pre-Install ist Ihre letzte Chance, ein riskantes Upgrade abzulehnen. +* Wie bei Post-Install ist pro Anwendung nur eine Pre-Installationsfunktion zulässig. Sie wird während des Builds automatisch dem Anwendungsmanifest unter `preInstallLogicFunction` hinzugefügt. +* **Nicht im Dev-Modus ausgeführt**: wie bei Post-Install — der Installationsablauf wird für lokal registrierte Apps vollständig übersprungen, daher läuft Pre-Install unter `yarn twenty dev` nie. Verwenden Sie `yarn twenty exec --preInstall`, um es manuell auszulösen. + + + + +Beide Hooks sind Teil desselben Installationsablaufs und erhalten dasselbe `InstallPayload`. Der Unterschied besteht darin, **wann** sie relativ zur Metadatenmigration des Workspaces ausgeführt werden, und das ändert, auf welche Daten sie gefahrlos zugreifen können. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ install flow │ +│ │ +│ upload package → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +│ │ +│ old schema visible new schema visible │ +└─────────────────────────────────────────────────────────────┘ +``` + +Pre-Install ist immer **synchron** (blockiert die Installation und kann sie abbrechen). Post-Install ist **standardmäßig asynchron** — in einen Worker eingereiht mit automatischen Wiederholungen — kann aber per `shouldRunSynchronously: true` in die synchrone Ausführung wechseln. Siehe das Akkordeon zu `definePostInstallLogicFunction` oben, wann welcher Modus zu verwenden ist. + +**Verwenden Sie `post-install` für alles, wofür das neue Schema existieren muss.** Dies ist der Regelfall: + +* Standarddaten befüllen (Anlegen anfänglicher Datensätze, Standardansichten, Demo-Inhalte) für neu hinzugefügte Objekte und Felder. +* Registrieren von Webhooks bei Drittanbieter-Diensten, jetzt, da die App ihre Anmeldedaten hat. +* Aufrufen Ihrer eigenen API, um eine Einrichtung abzuschließen, die von den synchronisierten Metadaten abhängt. +* Idempotente "Stelle sicher, dass dies existiert"-Logik, die bei jedem Upgrade den Zustand abgleichen soll — kombinieren Sie dies mit `shouldRunOnVersionUpgrade: true`. + +Beispiel — nach der Installation einen Standard-`PostCard`-Datensatz anlegen: + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion }: InstallPayload): Promise => { + if (previousVersion) return; // fresh installs only + + const client = createClient(); + await client.postCard.create({ + data: { title: 'Welcome to Postcard', content: 'Your first card!' }, + }); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Seeds a welcome post card after install.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + handler, +}); +``` + +**Verwenden Sie `pre-install`, wenn eine Migration ansonsten vorhandene Daten löschen oder beschädigen würde.** Da Pre-Install gegen das vorherige Schema läuft und ein Fehlschlag das Upgrade zurückrollt, ist es der richtige Ort für alles Riskante: + +* **Sichern von Daten, die gleich gelöscht oder umstrukturiert werden** — z. B. Sie entfernen in v2 ein Feld und müssen dessen Werte vor der Migration in ein anderes Feld kopieren oder in einen Speicher exportieren. +* **Archivieren von Datensätzen, die eine neue Einschränkung ungültig machen würde** — z. B. ein Feld wird `NOT NULL` und Sie müssen zuerst Zeilen mit Null-Werten löschen oder korrigieren. +* **Kompatibilität validieren und das Upgrade ablehnen, wenn die aktuellen Daten nicht sauber migriert werden können** — werfen Sie im Handler einen Fehler, und die Installation wird ohne Änderungen abgebrochen. Das ist sicherer, als die Inkompatibilität mitten in der Migration zu entdecken. +* **Daten umbenennen oder Schlüssel neu zuweisen** vor einer Schemaänderung, bei der sonst die Zuordnung verloren ginge. + +Beispiel — Datensätze vor einer destruktiven Migration archivieren: + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { + // Only the 1.x → 2.x upgrade drops the legacy `notes` field. + if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { + return; + } + + const client = createClient(); + const legacyRecords = await client.postCard.findMany({ + where: { notes: { isNotNull: true } }, + }); + + if (legacyRecords.length === 0) return; + + // Copy legacy `notes` into the new `description` field before the migration + // drops the `notes` column. If this fails, the upgrade is aborted and the + // workspace stays on v1 with all data intact. + await Promise.all( + legacyRecords.map((record) => + client.postCard.update({ + where: { id: record.id }, + data: { description: record.notes }, + }), + ), + ); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Backs up legacy notes into description before the v2 migration.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +**Faustregel:** + +| You want to... | Verwenden | +| ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| Standarddaten befüllen, den Workspace konfigurieren, externe Ressourcen registrieren | `post-install` | +| Lang laufendes Seeding oder Drittanbieteraufrufe ausführen, die die Installationsantwort nicht blockieren sollten | `post-install` (Standard — `shouldRunSynchronously: false`, mit Worker-Wiederholungen) | +| Schnelle Einrichtung ausführen, auf die sich der Aufrufer unmittelbar nach der Rückkehr des Installationsaufrufs verlassen wird | `post-install` mit `shouldRunSynchronously: true` | +| Daten lesen oder sichern, die bei der bevorstehenden Migration verloren gingen | `pre-install` | +| Ein Upgrade ablehnen, das vorhandene Daten beschädigen würde | `pre-install` (`throw` im Handler) | +| Bei jedem Upgrade einen Abgleich ausführen | `post-install` mit `shouldRunOnVersionUpgrade: true` | +| Einmalige Einrichtung nur bei der ersten Installation durchführen | `post-install` mit `shouldRunOnVersionUpgrade: false` (Standard) | + + +Im Zweifel auf **Post-Install** setzen. Greifen Sie nur zu Pre-Install, wenn die Migration selbst destruktiv ist und Sie den vorherigen Zustand abfangen müssen, bevor er verloren geht. + + + + + +## Typisierte API-Clients (twenty-client-sdk) + +Das Paket `twenty-client-sdk` stellt zwei typisierte GraphQL-Clients bereit, um aus Ihren Logikfunktionen und Frontend-Komponenten mit der Twenty-API zu interagieren. + +| Client | Importieren | Endpunkt | Generiert? | +| ------------------- | ---------------------------- | --------------------------------------------------------- | ------------------------------------ | +| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — Arbeitsbereichsdaten (Datensätze, Objekte) | Ja, zur Entwicklungs-/Build-Zeit | +| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — Arbeitsbereichskonfiguration, Datei-Uploads | Nein, wird vorgefertigt ausgeliefert | + + + + +Der `CoreApiClient` ist der Haupt-Client zum Abfragen und Ändern von Arbeitsbereichsdaten. Er wird während `yarn twenty dev` oder `yarn twenty build` **aus Ihrem Arbeitsbereichsschema generiert** und ist daher vollständig typisiert, passend zu Ihren Objekten und Feldern. + +```ts +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const client = new CoreApiClient(); + +// Query records +const { companies } = await client.query({ + companies: { + edges: { + node: { + id: true, + name: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, + }, + }, + }, +}); + +// Create a record +const { createCompany } = await client.mutation({ + createCompany: { + __args: { + data: { + name: 'Acme Corp', + }, + }, + id: true, + name: true, + }, +}); +``` + +Der Client verwendet eine Selection-Set-Syntax: Übergeben Sie `true`, um ein Feld einzuschließen, verwenden Sie `__args` für Argumente, und verschachteln Sie Objekte für Relationen. Sie erhalten vollständige Autovervollständigung und Typprüfung basierend auf Ihrem Arbeitsbereichsschema. + + +**Der CoreApiClient wird zur Entwicklungs-/Build-Zeit generiert.** Wenn Sie ihn verwenden, ohne zuvor `yarn twenty dev` oder `yarn twenty build` ausgeführt zu haben, wird ein Fehler ausgelöst. Die Generierung erfolgt automatisch — die CLI inspiziert das GraphQL-Schema Ihres Arbeitsbereichs und erzeugt mit `@genql/cli` einen typisierten Client. + + +#### Verwendung von CoreSchema für Typannotationen + +`CoreSchema` stellt TypeScript-Typen bereit, die Ihren Arbeitsbereichsobjekten entsprechen — nützlich zum Typisieren von Komponentenzustand oder Funktionsparametern: + +```ts +import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; +import { useState } from 'react'; + +const [company, setCompany] = useState< + Pick | undefined +>(undefined); + +const client = new CoreApiClient(); +const result = await client.query({ + company: { + __args: { filter: { position: { eq: 1 } } }, + id: true, + name: true, + }, +}); +setCompany(result.company); +``` + + + + +`MetadataApiClient` ist im SDK bereits vorgefertigt enthalten (keine Generierung erforderlich). Er fragt den Endpunkt `/metadata` nach Arbeitsbereichskonfiguration, Anwendungen und Datei-Uploads ab. + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; + +const metadataClient = new MetadataApiClient(); + +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, + }, +}); +``` + +#### Dateien hochladen + +Der `MetadataApiClient` enthält eine Methode `uploadFile`, um Dateien an Felder des Typs Datei anzuhängen: + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import * as fs from 'fs'; + +const metadataClient = new MetadataApiClient(); + +const fileBuffer = fs.readFileSync('./invoice.pdf'); + +const uploadedFile = await metadataClient.uploadFile( + fileBuffer, // file contents as a Buffer + 'invoice.pdf', // filename + 'application/pdf', // MIME type + '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier +); + +console.log(uploadedFile); +// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } +``` + +| Parameter | Typ | Beschreibung | +| ---------------------------------- | -------------- | --------------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | Der Rohinhalt der Datei | +| `filename` | `Zeichenkette` | Der Name der Datei (wird für Speicherung und Anzeige verwendet) | +| `contentType` | `Zeichenkette` | MIME-Typ (standardmäßig `application/octet-stream`, wenn weggelassen) | +| `fieldMetadataUniversalIdentifier` | `Zeichenkette` | Der `universalIdentifier` des Dateityp-Felds in Ihrem Objekt | + +Hauptpunkte: +* Sie verwendet den `universalIdentifier` des Feldes (nicht dessen arbeitsbereichsspezifische ID), sodass Ihr Upload-Code in jedem Arbeitsbereich funktioniert, in dem Ihre App installiert ist. +* Die zurückgegebene `url` ist eine signierte URL, mit der Sie auf die hochgeladene Datei zugreifen können. + + + + + + Wenn Ihr Code auf Twenty ausgeführt wird (Logikfunktionen oder Frontend-Komponenten), injiziert die Plattform Anmeldedaten als Umgebungsvariablen: + + * `TWENTY_API_URL` — Basis-URL der Twenty-API + * `TWENTY_APP_ACCESS_TOKEN` — Kurzlebiger Schlüssel, der auf die Standard-Funktionsrolle Ihrer Anwendung begrenzt ist + + Sie müssen diese **nicht** an die Clients übergeben — sie lesen automatisch aus `process.env`. Die Berechtigungen des API-Schlüssels werden durch die Rolle bestimmt, auf die in `defaultRoleUniversalIdentifier` in Ihrer `application-config.ts` verwiesen wird. + diff --git a/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx index c81f1d4e625..03e2972b0cc 100644 --- a/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx @@ -1,12 +1,9 @@ --- title: Veröffentlichen +icon: hochladen description: Veröffentlichen Sie Ihre Twenty-App auf dem Twenty-Marktplatz oder stellen Sie sie intern bereit. --- - - Apps befinden sich derzeit in der Alpha-Phase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter. - - ## Übersicht Sobald Ihre App [lokal gebaut und getestet](/l/de/developers/extend/apps/building) wurde, haben Sie zwei Möglichkeiten, sie zu verteilen: diff --git a/packages/twenty-docs/l/de/developers/extend/apps/skills-and-agents.mdx b/packages/twenty-docs/l/de/developers/extend/apps/skills-and-agents.mdx new file mode 100644 index 00000000000..efa10668eda --- /dev/null +++ b/packages/twenty-docs/l/de/developers/extend/apps/skills-and-agents.mdx @@ -0,0 +1,69 @@ +--- +title: Fähigkeiten & Agenten +description: Define AI skills and agents for your app. +icon: robot +--- + + + Skills and agents are currently in alpha. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter. + + +Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts. + + + + +Skills definieren wiederverwendbare Anweisungen und Fähigkeiten, die KI-Agenten in Ihrem Arbeitsbereich verwenden können. Verwenden Sie `defineSkill()`, um Skills mit eingebauter Validierung zu definieren: + +```ts src/skills/example-skill.ts +import { defineSkill } from 'twenty-sdk/define'; + +export default defineSkill({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'sales-outreach', + label: 'Sales Outreach', + description: 'Guides the AI agent through a structured sales outreach process', + icon: 'IconBrain', + content: `You are a sales outreach assistant. When reaching out to a prospect: +1. Research the company and recent news +2. Identify the prospect's role and likely pain points +3. Draft a personalized message referencing specific details +4. Keep the tone professional but conversational`, +}); +``` + +Hauptpunkte: +* `name` ist eine eindeutige Kennung (als Zeichenfolge) für den Skill (kebab-case empfohlen). +* `label` ist der menschenlesbare Anzeigename, der in der UI angezeigt wird. +* `content` enthält die Skill-Anweisungen — dies ist der Text, den der KI-Agent verwendet. +* `icon` (optional) legt das in der UI angezeigte Symbol fest. +* `description` (optional) liefert zusätzlichen Kontext zum Zweck des Skills. + + + + +Agenten sind KI-Assistenten, die innerhalb Ihres Workspaces leben. Verwenden Sie `defineAgent()`, um Agenten mit einem benutzerdefinierten System-Prompt zu erstellen: + +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk/define'; + +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +Hauptpunkte: +* `name` ist die eindeutige Kennzeichnungs-Zeichenfolge für den Agenten (kebab-case empfohlen). +* `label` ist der in der UI angezeigte Anzeigename. +* `prompt` ist der System-Prompt, der das Verhalten des Agenten definiert. +* `description` (optional) liefert Kontext dazu, was der Agent tut. +* `icon` (optional) legt das in der UI angezeigte Symbol fest. +* `modelId` (optional) überschreibt das vom Agenten verwendete Standard-KI-Modell. + + + diff --git a/packages/twenty-docs/l/de/developers/extend/oauth.mdx b/packages/twenty-docs/l/de/developers/extend/oauth.mdx new file mode 100644 index 00000000000..f9790e0ffb0 --- /dev/null +++ b/packages/twenty-docs/l/de/developers/extend/oauth.mdx @@ -0,0 +1,189 @@ +--- +title: OAuth +icon: schlüssel +description: Authorization code flow with PKCE and client credentials for server-to-server access. +--- + +Twenty implements OAuth 2.0 with authorization code + PKCE for user-facing apps and client credentials for server-to-server access. Clients are registered dynamically via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) — no manual setup in a dashboard. + +## When to Use OAuth + +| Szenario | Auth Method | +| --------------------------------------- | -------------------------------------------------------------------------------- | +| Internal scripts, automation | [API Key](/l/de/developers/extend/api#authentication) | +| External app acting on behalf of a user | **OAuth — Authorization Code** | +| Server-to-server, no user context | **OAuth — Client Credentials** | +| Twenty App with UI extensions | [Apps](/l/de/developers/extend/apps/getting-started) (OAuth is handled automatically) | + +## Register a Client + +Twenty supports **dynamic client registration** per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). No manual setup needed — register programmatically: + +```bash +POST /oauth/register +Content-Type: application/json + +{ + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"], + "grant_types": ["authorization_code"], + "token_endpoint_auth_method": "client_secret_post" +} +``` + +**Response:** + +```json +{ + "client_id": "abc123", + "client_secret": "secret456", + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"] +} +``` + + +Store the `client_secret` securely — it cannot be retrieved later. + + +## Geltungsbereiche + +| Scope | Zugriff | +| -------- | ---------------------------------------------------- | +| `api` | Full read/write access to the Core and Metadata APIs | +| `profil` | Read the authenticated user's profile information | + +Request scopes as a space-separated string: `scope=api profile` + +## Authorization Code Flow + +Use this flow when your app acts on behalf of a Twenty user. + +### 1. Redirect the user to authorize + +``` +GET /oauth/authorize? + client_id=YOUR_CLIENT_ID& + response_type=code& + redirect_uri=https://myapp.com/callback& + scope=api& + state=random_state_value& + code_challenge=CHALLENGE& + code_challenge_method=S256 +``` + +| Parameter | Erforderlich | Beschreibung | +| ----------------------- | ------------ | ------------------------------------------------------------ | +| `client_id` | Ja | Your registered client ID | +| `response_type` | Ja | Must be `code` | +| `redirect_uri` | Ja | Must match a registered redirect URI | +| `scope` | Nein | Space-separated scopes (defaults to `api`) | +| `zustand` | Empfohlen | Random string to prevent CSRF attacks | +| `code_challenge` | Empfohlen | PKCE challenge (SHA-256 hash of verifier, base64url-encoded) | +| `code_challenge_method` | Empfohlen | Must be `S256` when using PKCE | + +The user sees a consent screen and approves or denies access. + +### 2. Handle the callback + +After authorization, Twenty redirects back to your `redirect_uri`: + +``` +https://myapp.com/callback?code=AUTH_CODE&state=random_state_value +``` + +Verify that `state` matches what you sent. + +### 3. Exchange the code for tokens + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=authorization_code& +code=AUTH_CODE& +redirect_uri=https://myapp.com/callback& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +code_verifier=YOUR_PKCE_VERIFIER +``` + +**Response:** + +```json +{ + "access_token": "eyJhbG...", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "dGhpcyBpcyBh..." +} +``` + +### 4. Use the access token + +```bash +GET /rest/companies +Authorization: Bearer ACCESS_TOKEN +``` + +### 5. Refresh when expired + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=refresh_token& +refresh_token=YOUR_REFRESH_TOKEN& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET +``` + +## Client Credentials Flow + +For server-to-server integrations with no user interaction: + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=client_credentials& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +scope=api +``` + +The returned token has workspace-level access, not tied to any specific user. + +## Server Discovery + +Twenty publishes its OAuth configuration at a standard discovery endpoint: + +``` +GET /.well-known/oauth-authorization-server +``` + +This returns all endpoints, supported grant types, scopes, and capabilities — useful for building generic OAuth clients. + +## API Endpoints Summary + +| Endpunkt | Zweck | +| ----------------------------------------- | --------------------------- | +| `/.well-known/oauth-authorization-server` | Server metadata discovery | +| `/oauth/register` | Dynamic client registration | +| `/oauth/authorize` | User authorization | +| `/oauth/token` | Token exchange and refresh | + +| Umgebung | Basis-URL | +| ----------------- | ------------------------ | +| **Cloud** | `https://api.twenty.com` | +| **Selbsthosting** | `https://{your-domain}` | + +## OAuth vs API Keys + +| | API-Schlüssel | OAuth | +| -------------------------- | ----------------------- | -------------------------------------- | +| **Einrichtung** | Generate in Settings | Register a client, implement flow | +| **User context** | None (workspace-level) | Specific user's permissions | +| **Am besten geeignet für** | Scripts, internal tools | External apps, multi-user integrations | +| **Token rotation** | Manuell | Automatic via refresh tokens | +| **Scoped access** | Full API access | Granular via scopes | diff --git a/packages/twenty-docs/l/de/developers/extend/webhooks.mdx b/packages/twenty-docs/l/de/developers/extend/webhooks.mdx index 9354918d546..134be26d9ea 100644 --- a/packages/twenty-docs/l/de/developers/extend/webhooks.mdx +++ b/packages/twenty-docs/l/de/developers/extend/webhooks.mdx @@ -1,11 +1,12 @@ --- title: Webhooks -description: Erhalten Sie Benachrichtigungen in Echtzeit, wenn Ereignisse in Ihrem CRM auftreten. +icon: satellite-dish +description: Get notified when records change — HTTP POST to your endpoint on every create, update, or delete. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -Webhooks übermitteln Daten in Echtzeit an Ihre Systeme, wenn Ereignisse in Twenty auftreten — kein Polling erforderlich. Verwenden Sie sie, um externe Systeme synchron zu halten, Automatisierungen auszulösen oder Benachrichtigungen zu senden. +Twenty sends an HTTP POST to your URL whenever a record is created, updated, or deleted. All object types are covered, including custom objects. ## Webhook erstellen diff --git a/packages/twenty-docs/l/de/developers/introduction.mdx b/packages/twenty-docs/l/de/developers/introduction.mdx index d9b37c2174f..c3c873fa576 100644 --- a/packages/twenty-docs/l/de/developers/introduction.mdx +++ b/packages/twenty-docs/l/de/developers/introduction.mdx @@ -1,23 +1,28 @@ --- -title: Erste Schritte -description: Willkommen in der Twenty-Entwicklerdokumentation, Ihren Ressourcen für Erweiterungen, Selbsthosting und Beiträge zu Twenty. +title: Entwickler +description: Build apps, use the API, self-host, or contribute to the codebase. --- import { CardTitle } from "/snippets/card-title.mdx" - - Erweitern - Erstellen Sie Integrationen mit APIs, Webhooks und benutzerdefinierten Apps. + + Apps + Extend Twenty with custom objects, server-side logic, UI components, and AI agents — all as TypeScript packages. - - Selbst hosten - Stellen Sie Twenty auf Ihrer eigenen Infrastruktur bereit und verwalten Sie es. + + API + REST and GraphQL APIs, webhooks, and OAuth. - - Mitwirken - Treten Sie unserer Open-Source-Community bei und tragen Sie zu Twenty bei. + + Self-Host + Run Twenty on your own infrastructure. + + + + Contribute + Set up the monorepo locally and submit PRs. diff --git a/packages/twenty-docs/l/de/developers/self-host/capabilities/cloud-providers.mdx b/packages/twenty-docs/l/de/developers/self-host/capabilities/cloud-providers.mdx index 07e64f736d6..6de6f6b31fa 100644 --- a/packages/twenty-docs/l/de/developers/self-host/capabilities/cloud-providers.mdx +++ b/packages/twenty-docs/l/de/developers/self-host/capabilities/cloud-providers.mdx @@ -1,5 +1,6 @@ --- title: Weitere Methoden +icon: cloud --- diff --git a/packages/twenty-docs/l/de/developers/self-host/capabilities/docker-compose.mdx b/packages/twenty-docs/l/de/developers/self-host/capabilities/docker-compose.mdx index c2cba88af90..d7c630091dd 100644 --- a/packages/twenty-docs/l/de/developers/self-host/capabilities/docker-compose.mdx +++ b/packages/twenty-docs/l/de/developers/self-host/capabilities/docker-compose.mdx @@ -1,5 +1,6 @@ --- -title: 1-Klick mit Docker Compose +title: Docker Compose +icon: docker --- diff --git a/packages/twenty-docs/l/de/developers/self-host/capabilities/setup.mdx b/packages/twenty-docs/l/de/developers/self-host/capabilities/setup.mdx index 834ff87612b..a6e3a5bd717 100644 --- a/packages/twenty-docs/l/de/developers/self-host/capabilities/setup.mdx +++ b/packages/twenty-docs/l/de/developers/self-host/capabilities/setup.mdx @@ -1,5 +1,6 @@ --- title: Einrichtung +icon: gear --- # Konfigurationsverwaltung diff --git a/packages/twenty-docs/l/de/developers/self-host/capabilities/troubleshooting.mdx b/packages/twenty-docs/l/de/developers/self-host/capabilities/troubleshooting.mdx index 3f3989973f5..eee36964f1e 100644 --- a/packages/twenty-docs/l/de/developers/self-host/capabilities/troubleshooting.mdx +++ b/packages/twenty-docs/l/de/developers/self-host/capabilities/troubleshooting.mdx @@ -1,5 +1,6 @@ --- title: Fehlerbehebung +icon: wrench --- ## Fehlerbehebung diff --git a/packages/twenty-docs/l/de/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/de/developers/self-host/capabilities/upgrade-guide.mdx index 740f5556112..1d6d144b7e1 100644 --- a/packages/twenty-docs/l/de/developers/self-host/capabilities/upgrade-guide.mdx +++ b/packages/twenty-docs/l/de/developers/self-host/capabilities/upgrade-guide.mdx @@ -1,5 +1,6 @@ --- title: Upgrade-Anleitung +icon: arrow-up-right-dots --- ## Allgemeine Richtlinien @@ -16,366 +17,14 @@ Wenn Sie Docker Compose verwendet haben, befolgen Sie diese Schritte: 3. Schalten Sie Twenty mit `docker compose up -d` wieder ein. -Wenn Sie Ihre Instanz um einige Versionen aktualisieren möchten, z. B. von v0.33.0 auf v0.35.0, müssen Sie Ihre Instanz der Reihe nach upgraden, in diesem Beispiel von v0.33.0 auf v0.34.0 und dann von v0.34.0 auf v0.35.0. - **Stellen Sie sicher, dass Sie nach jeder aktualisierten Version ein nicht-korrumpiertes Backup haben.** ## Versionsspezifische Upgrade-Schritte -## v1.0 +## After v1.21 -Hallo Twenty v1.0! 🎉 +We know support sequential upgrades. You don't need to go through each version one by one. -## v0.60 +## Before v1.21 -### Leistungsverbesserungen - -Alle Interaktionen mit der Metadata-API wurden für eine bessere Leistung optimiert, insbesondere für die Manipulation von Objektmetadaten und die Erstellung von Arbeitsbereichen. - -Wir haben unsere Caching-Strategie umstrukturiert, um Cache-Treffer gegenüber Datenbankabfragen zu priorisieren, was die Leistung von Metadata-API-Operationen erheblich verbessert. - -Wenn Sie nach dem Upgrade auf Laufzeitprobleme stoßen, müssen Sie möglicherweise Ihren Cache leeren, um sicherzustellen, dass er mit den neuesten Änderungen synchronisiert ist. Führen Sie diesen Befehl in Ihrem Twenty-Server-Container aus: - -```bash -yarn command:prod cache:flush -``` - -### v0.55 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.55-Image zu verwenden. - -Sie müssen keinen Befehl mehr ausführen, das neue Image kümmert sich automatisch um alle erforderlichen Migrationen. - -### Fehler: `Benutzer hat keine Berechtigung` - -Wenn Sie nach dem Upgrade bei den meisten Anfragen auf Autorisierungsfehler stoßen, müssen Sie möglicherweise Ihren Cache leeren, um die neuesten Berechtigungen neu zu berechnen. - -Führen Sie dies in Ihrem `twenty-server`-Container aus: - -```bash -yarn command:prod cache:flush -``` - -Dieses Problem ist spezifisch für diese Twenty-Version und sollte bei zukünftigen Upgrades nicht erforderlich sein. - -### v0.54 - -Seit Version `0.53` sind keine manuellen Aktionen mehr erforderlich. - -#### Veraltung des Metadatenschemas - -Wir haben das `metadata`-Schema in das `core`-Schema integriert, um die Datenwiederherstellung aus `TypeORM` zu vereinfachen. -Wir haben den `migrate`-Befehlschritt in den `upgrade`-Befehl integriert. Wir empfehlen nicht, `migrate` manuell in einem Ihrer Server-/Arbeitsprozess-Container auszuführen. - -### Ab v0.53 - -Ab Version `0.53` wird das Upgrade programmgesteuert innerhalb des `DockerFile` durchgeführt, was bedeutet, dass Sie von nun an keinen Befehl mehr manuell ausführen müssen. - -Stellen Sie sicher, dass Sie Ihre Instanz weiterhin schrittweise aktualisieren, ohne eine Hauptversion zu überspringen (z. B. ist `0.43.3` auf `0.44.0` erlaubt, aber `0.43.1` auf `0.45.0` nicht), sonst könnte dies zu einer Desynchronisation der Arbeitsbereichsversion führen, die zu Laufzeitfehlern und fehlenden Funktionalitäten führen könnte. - -Um zu überprüfen, ob ein Arbeitsbereich korrekt migriert wurde, können Sie seine Version in der Datenbank in der Tabelle `core.workspace` überprüfen. - -Es sollte immer im Bereich Ihrer aktuellen Twenty-Instanz `major.minor`-Version liegen, Sie können Ihre Instanzversion im Admin-Panel (unter `/settings/admin-panel`, zugänglich, wenn Ihr Benutzer die Eigenschaft `canAccessFullAdminPanel` in der Datenbank auf true gesetzt hat) oder durch Ausführen von `echo $APP_VERSION` in Ihrem `twenty-server`-Container anzeigen. - -Um eine desynchronisierte Arbeitsbereichsversion zu korrigieren, müssen Sie von der entsprechenden Twenty-Version aus aktualisieren, indem Sie die zugehörige Upgrade-Anleitung der Reihe nach befolgen, bis Sie die gewünschte Version erreichen. - -#### Entfernung des `auditLog` - -Wir haben das standardmäßige auditLog-Objekt entfernt, was bedeutet, dass sich die Größe Ihres Backups nach dieser Migration möglicherweise erheblich reduziert. - -### v0.51 auf v0.52 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.52-Image zu verwenden. - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### Ich habe einen Arbeitsbereich, der in der Version zwischen `0.52.0` und `0.52.6` blockiert ist. - -Leider wurden `0.52.0` und `0.52.6` vollständig von dockerHub entfernt. -Sie müssen Ihre Arbeitsbereichsversion manuell auf `0.51.0` in der Datenbank aktualisieren und mit der Twenty-Version `0.52.11` gemäß der obigen Upgrade-Anleitung aktualisieren. - -### v0.50 bis v0.51 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.51-Image zu verwenden. - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.44.0 bis v0.50.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.50.0-Image zu verwenden. - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### Docker-compose.yml Verlagerung - -Diese Version enthält eine Mutation von `docker-compose.yml`, um dem `worker`-Dienst Zugriff auf das `server-local-data`-Volume zu geben. -Bitte aktualisieren Sie Ihre lokale `docker-compose.yml` mit der [docker-compose.yml v0.50.0](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml) - -### v0.43.0 bis v0.44.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.44.0-Image zu verwenden. - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.42.0 bis v0.43.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.43.0-Image zu verwenden. - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -In dieser Version haben wir auch auf das postgres:16-Image in docker-compose.yml umgestellt. - -#### (Option 1) Datenbankmigration - -Es ist in Ordnung, das vorhandene postgres-spilo-Image zu behalten, aber Sie müssen die Version in Ihrer docker-compose.yml auf 0.43.0 einfrieren. - -#### (Option 2) Datenbankmigration - -Wenn Sie Ihre Datenbank auf das neue postgres:16-Image migrieren möchten, befolgen Sie bitte diese Schritte: - -1. Dumpen Sie Ihre Datenbank aus dem alten postgres-spilo-Container - -``` -docker exec -it twenty-db-1 sh -pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql -exit -docker cp twenty-db-1:/home/postgres/databases_backup.sql . -``` - -Stellen Sie sicher, dass Ihre Dump-Datei nicht leer ist. - -2. Aktualisieren Sie Ihre docker-compose.yml, um das postgres:16-Image gemäß der [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) zu verwenden. - -3. Stellen Sie die Datenbank in den neuen postgres:16-Container wieder her. - -``` -docker cp databases_backup.sql twenty-db-1:/databases_backup.sql -docker exec -it twenty-db-1 sh -psql -U {IHR_POSTGRES_USER} -d {IHR_POSTGRES_DB} -f databases_backup.sql -exit -``` - -### v0.41.0 bis v0.42.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.42.0-Image zu verwenden. - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.42 -``` - -**Umgebungsvariablen** - -* Entfernt: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT` -* Hinzugefügt: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED` - -### v0.40.0 bis v0.41.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.41.0-Image zu verwenden. - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.41 -``` - -**Umgebungsvariablen** - -* Entfernt: `AUTH_MICROSOFT_TENANT_ID` - -### v0.35.0 bis v0.40.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.40.0-Image zu verwenden. - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.40 -``` - -**Umgebungsvariablen** - -* Hinzugefügt: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL` - -### v0.34.0 bis v0.35.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.35.0-Image zu verwenden. - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.35 -``` - -Der `yarn database:migrate:prod`-Befehl wendet die Migrationen auf die Datenbankstruktur (Kern- und Metadatenschemata) an -Die `yarn command:prod upgrade-0.35` kümmert sich um die Datenmigration aller Arbeitsbereiche. - -**Umgebungsvariablen** - -* Wir haben `ENABLE_DB_MIGRATIONS` durch `DISABLE_DB_MIGRATIONS` ersetzt (Standardwert ist jetzt `false`, Sie müssen wahrscheinlich nichts einstellen) - -### v0.33.0 bis v0.34.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.34.0-Image zu verwenden. - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.34 -``` - -Der `yarn database:migrate:prod`-Befehl wendet die Migrationen auf die Datenbankstruktur (Kern- und Metadatenschemata) an -Die `yarn command:prod upgrade-0.34` kümmert sich um die Datenmigration aller Arbeitsbereiche. - -**Umgebungsvariablen** - -* Entfernt: `FRONT_BASE_URL` -* Hinzugefügt: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT` - -Wir haben die Handhabung der Frontend-URL aktualisiert. -Sie können nun die Frontend-URL mit den Variablen `FRONT_DOMAIN`, `FRONT_PROTOCOL` und `FRONT_PORT` festlegen. -Wenn FRONT_DOMAIN nicht gesetzt ist, wird die Frontend-URL auf `SERVER_URL` zurückfallen. - -### v0.32.0 bis v0.33.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.33.0-Image zu verwenden. - -``` -yarn command:prod cache:flush -yarn database:migrate:prod -yarn command:prod upgrade-0.33 -``` - -Der `yarn command:prod cache:flush`-Befehl leert den Redis-Cache. -Der `yarn database:migrate:prod`-Befehl wendet die Migrationen auf die Datenbankstruktur (Kern- und Metadatenschemata) an -Die `yarn command:prod upgrade-0.33` kümmert sich um die Datenmigration aller Arbeitsbereiche. - -Ab dieser Version wurde das twenty-postgres-Image für DB veraltet und es wird stattdessen twenty-postgres-spilo verwendet. -Wenn Sie weiterhin das twenty-postgres-Image verwenden möchten, ersetzen Sie einfach `twentycrm/twenty-postgres:${TAG}` durch `twentycrm/twenty-postgres` in docker-compose.yml. - -### v0.31.0 bis v0.32.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.32.0-Image zu verwenden. - -**Schema- und Datenmigration** - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.32 -``` - -Der `yarn database:migrate:prod`-Befehl wendet die Migrationen auf die Datenbankstruktur (Kern- und Metadatenschemata) an -Die `yarn command:prod upgrade-0.32` kümmert sich um die Datenmigration aller Arbeitsbereiche. - -**Umgebungsvariablen** - -Wir haben die Handhabung der Redis-Verbindung aktualisiert. - -* Entfernt: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD` -* Hinzugefügt: `REDIS_URL` - -Aktualisieren Sie Ihre `.env`-Datei, um die neue `REDIS_URL`-Variable anstelle der einzelnen Redis-Verbindungsparameter zu verwenden. - -Wir haben auch die Handhabung der JWT-Token vereinfacht. - -* Entfernt: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET` -* Hinzugefügt: `APP_SECRET` - -Aktualisieren Sie Ihre `.env`-Datei, um die neue `APP_SECRET`-Variable anstelle der einzelnen Token-Geheimnisse zu verwenden (Sie können das gleiche Geheimnis wie zuvor verwenden oder einen neuen zufälligen String generieren). - -**Verbundenes Konto** - -Wenn Sie ein verbundenes Konto verwenden, um Ihre Google-E-Mails und -Kalender zu synchronisieren, müssen Sie die [People API](https://developers.google.com/people) in Ihrer Google Admin-Konsole aktivieren. - -### v0.30.0 bis v0.31.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.31.0-Image zu verwenden. - -**Schema- und Datenmigration**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.31 -``` - -Der `yarn database:migrate:prod`-Befehl wendet die Migrationen auf die Datenbankstruktur (Kern- und Metadatenschemata) an -Die `yarn command:prod upgrade-0.31` kümmert sich um die Datenmigration aller Arbeitsbereiche. - -### v0.24.0 bis v0.30.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.30.0-Image zu verwenden. - -**Wichtige Änderung**: -Um die Leistung zu verbessern, erfordert Twenty jetzt, dass der Redis-Cache konfiguriert wird. Wir haben unser [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) aktualisiert, um dies zu reflektieren. -Stellen Sie sicher, dass Sie Ihre Konfiguration aktualisieren und Ihre Umgebungsvariablen entsprechend anpassen: - -``` -REDIS_HOST={ihr-redis-host} -REDIS_PORT={ihr-redis-port} -CACHE_STORAGE_TYPE=redis -``` - -**Schema- und Datenmigration**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.30 -``` - -Der `yarn database:migrate:prod`-Befehl wendet die Migrationen auf die Datenbankstruktur (Kern- und Metadatenschemata) an -Die `yarn command:prod upgrade-0.30` kümmert sich um die Datenmigration aller Arbeitsbereiche. - -### v0.23.0 bis v0.24.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.24.0-Image zu verwenden. - -Führen Sie die folgenden Befehle aus: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.24 -``` - -Der `yarn database:migrate:prod`-Befehl wendet die Migrationen auf die Datenbankstruktur (Kern- und Metadatenschemata) an -Die `yarn command:prod upgrade-0.24` kümmert sich um die Datenmigration aller Arbeitsbereiche. - -### v0.22.0 bis v0.23.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.23.0-Image zu verwenden. - -Führen Sie die folgenden Befehle aus: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.23 -``` - -Der `yarn database:migrate:prod`-Befehl wendet die Migrationen auf die Datenbank an. -Die `yarn command:prod upgrade-0.23` kümmert sich um die Datenmigration, einschließlich der Übertragung von Aktivitäten auf Aufgaben/Notizen. - -### v0.21.0 bis v0.22.0 - -Aktualisieren Sie Ihre Twenty-Instanz, um das v0.22.0-Image zu verwenden. - -Führen Sie die folgenden Befehle aus: - -``` -yarn database:migrate:prod -yarn command:prod workspace:sync-metadata -f -yarn command:prod upgrade-0.22 -``` - -Der `yarn database:migrate:prod`-Befehl wendet die Migrationen auf die Datenbank an. -Der Befehl `yarn command:prod workspace:sync-metadata -f` synchronisiert die Definitionen der Standardobjekte mit den Metadaten-Tabellen und wendet erforderliche Migrationen auf bestehende Arbeitsbereiche an. -Der Befehl `yarn command:prod upgrade-0.22` führt spezifische Datenumwandlungen durch, um sich an die neuen Objekt-Standardanfrage-Instrumentierungsoptionen anzupassen. +Make sure to go through every major tagged version when upgrading (upgrade v1.6.x to v.7.y, then v.7.y to v.8.z, etc.). diff --git a/packages/twenty-docs/l/de/navigation.json b/packages/twenty-docs/l/de/navigation.json index cf433e04a3b..3da1ec283ee 100644 --- a/packages/twenty-docs/l/de/navigation.json +++ b/packages/twenty-docs/l/de/navigation.json @@ -1,24 +1,27 @@ { "tabs": { + "gettingStarted": { + "label": "Erste Schritte", + "groups": { + "welcome": { + "label": "Welcome" + }, + "coreConcepts": { + "label": "Core Concepts" + } + } + }, "userGuide": { "label": "Benutzerhandbuch", "groups": { - "discoverTwenty": { - "label": "Entdecken Sie Twenty", - "groups": { - "gettingStartedCapabilities": { - "label": "Funktionen" - }, - "gettingStartedHowTos": { - "label": "Anleitungen" - } - } + "userGuideOverview": { + "label": "Übersicht" }, "dataModel": { "label": "Datenmodell", "groups": { - "dataModelCapabilities": { - "label": "Funktionen" + "dataModelReference": { + "label": "Reference" }, "dataModelHowTos": { "label": "Anleitungen" @@ -28,8 +31,8 @@ "dataMigration": { "label": "Datenmigration", "groups": { - "dataMigrationCapabilities": { - "label": "Funktionen" + "dataMigrationReference": { + "label": "Reference" }, "dataMigrationHowTos": { "label": "Anleitungen" @@ -39,8 +42,8 @@ "calendarEmails": { "label": "Kalender & E-Mails", "groups": { - "calendarEmailsCapabilities": { - "label": "Funktionen" + "calendarEmailsReference": { + "label": "Reference" }, "calendarEmailsHowTos": { "label": "Anleitungen" @@ -50,8 +53,8 @@ "workflows": { "label": "Workflows", "groups": { - "workflowsCapabilities": { - "label": "Funktionen" + "workflowsReference": { + "label": "Reference" }, "workflowsHowTos": { "label": "Anleitungen", @@ -75,21 +78,26 @@ "ai": { "label": "KI", "groups": { - "aiCapabilities": { - "label": "Funktionen" + "aiReference": { + "label": "Reference" }, "aiHowTos": { "label": "Anleitungen" } } }, - "viewsPipelines": { - "label": "Ansichten & Pipelines", + "layout": { + "label": "Layout", "groups": { - "viewsPipelinesCapabilities": { - "label": "Funktionen" + "layoutReference": { + "label": "Reference", + "groups": { + "layoutViews": { + "label": "Ansichten" + } + } }, - "viewsPipelinesHowTos": { + "layoutHowTos": { "label": "Anleitungen" } } @@ -97,8 +105,8 @@ "dashboards": { "label": "Dashboards", "groups": { - "dashboardsCapabilities": { - "label": "Funktionen" + "dashboardsReference": { + "label": "Reference" }, "dashboardsHowTos": { "label": "Anleitungen" @@ -108,8 +116,8 @@ "permissionsAccess": { "label": "Berechtigungen & Zugriff", "groups": { - "permissionsAccessCapabilities": { - "label": "Funktionen" + "permissionsAccessReference": { + "label": "Reference" }, "permissionsAccessHowTos": { "label": "Anleitungen" @@ -119,8 +127,8 @@ "billing": { "label": "Abrechnung", "groups": { - "billingCapabilities": { - "label": "Funktionen" + "billingReference": { + "label": "Reference" }, "billingHowTos": { "label": "Anleitungen" @@ -130,8 +138,8 @@ "settings": { "label": "Einstellungen", "groups": { - "settingsCapabilities": { - "label": "Funktionen" + "settingsReference": { + "label": "Reference" }, "settingsHowTos": { "label": "Anleitungen" @@ -143,59 +151,20 @@ "developers": { "label": "Entwickler", "groups": { - "developersGroup": { - "label": "Entwickler" + "developersOverview": { + "label": "Übersicht" }, - "extend": { - "label": "Erweitern", - "groups": { - "apps": { - "label": "Apps" - } - } + "apps": { + "label": "Apps" + }, + "api": { + "label": "API" }, "selfHost": { - "label": "Selbsthosting", - "groups": { - "selfHostCapabilities": { - "label": "Funktionen" - } - } + "label": "Selbsthosting" }, "contribute": { - "label": "Mitwirken", - "groups": { - "contributeCapabilities": { - "label": "Funktionen", - "groups": { - "frontendDevelopment": { - "label": "Frontend-Entwicklung", - "groups": { - "twentyUi": { - "label": "Twenty UI", - "groups": { - "display": { - "label": "Anzeigen" - }, - "feedback": { - "label": "Rückmeldung" - }, - "input": { - "label": "Eingabe" - }, - "navigation": { - "label": "Navigation" - } - } - } - } - }, - "backendDevelopment": { - "label": "Backend-Entwicklung" - } - } - } - } + "label": "Mitwirken" } } } diff --git a/packages/twenty-docs/l/de/twenty-ui/display/app-tooltip.mdx b/packages/twenty-docs/l/de/twenty-ui/display/app-tooltip.mdx index c4c9e573607..28b24f4c439 100644 --- a/packages/twenty-docs/l/de/twenty-ui/display/app-tooltip.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/display/app-tooltip.mdx @@ -1,5 +1,6 @@ --- title: App-Tooltip +icon: nachricht --- diff --git a/packages/twenty-docs/l/de/twenty-ui/display/checkmark.mdx b/packages/twenty-docs/l/de/twenty-ui/display/checkmark.mdx index 9e95324a747..7cffeaabb3a 100644 --- a/packages/twenty-docs/l/de/twenty-ui/display/checkmark.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/display/checkmark.mdx @@ -1,5 +1,6 @@ --- title: Häkchen +icon: circle-check --- diff --git a/packages/twenty-docs/l/de/twenty-ui/display/icons.mdx b/packages/twenty-docs/l/de/twenty-ui/display/icons.mdx index ac8d8e32584..72ab277f3db 100644 --- a/packages/twenty-docs/l/de/twenty-ui/display/icons.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/display/icons.mdx @@ -1,5 +1,6 @@ --- title: Icons +icon: icons --- diff --git a/packages/twenty-docs/l/de/twenty-ui/display/soon-pill.mdx b/packages/twenty-docs/l/de/twenty-ui/display/soon-pill.mdx index f83838f894d..73d7338831e 100644 --- a/packages/twenty-docs/l/de/twenty-ui/display/soon-pill.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/display/soon-pill.mdx @@ -2,7 +2,6 @@ title: Soon Pill --- - Ein kleines Abzeichen oder "Pille", um anzuzeigen, dass etwas bald kommt. ```jsx diff --git a/packages/twenty-docs/l/de/twenty-ui/display/tag.mdx b/packages/twenty-docs/l/de/twenty-ui/display/tag.mdx index 746a53de667..766c7fc8a89 100644 --- a/packages/twenty-docs/l/de/twenty-ui/display/tag.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/display/tag.mdx @@ -1,8 +1,8 @@ --- title: '"Tag"' +icon: '"tag"' --- - Komponente zur visuellen Kategorisierung oder Kennzeichnung von Inhalten. diff --git a/packages/twenty-docs/l/de/twenty-ui/input/buttons.mdx b/packages/twenty-docs/l/de/twenty-ui/input/buttons.mdx index e61edb0cf15..32380b33e43 100644 --- a/packages/twenty-docs/l/de/twenty-ui/input/buttons.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/input/buttons.mdx @@ -1,5 +1,6 @@ --- title: Schaltflächen +icon: hand-pointer --- diff --git a/packages/twenty-docs/l/de/twenty-ui/input/checkbox.mdx b/packages/twenty-docs/l/de/twenty-ui/input/checkbox.mdx index bc8d1efa479..2fa6cbdcadc 100644 --- a/packages/twenty-docs/l/de/twenty-ui/input/checkbox.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/input/checkbox.mdx @@ -1,5 +1,6 @@ --- title: Kontrollkästchen +icon: square-check --- diff --git a/packages/twenty-docs/l/de/twenty-ui/input/color-scheme.mdx b/packages/twenty-docs/l/de/twenty-ui/input/color-scheme.mdx index 1d9eafe940e..204325f3098 100644 --- a/packages/twenty-docs/l/de/twenty-ui/input/color-scheme.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/input/color-scheme.mdx @@ -1,5 +1,6 @@ --- title: Farbschema +icon: palette --- diff --git a/packages/twenty-docs/l/de/twenty-ui/input/radio.mdx b/packages/twenty-docs/l/de/twenty-ui/input/radio.mdx index fef02a3df18..51dffe8ccee 100644 --- a/packages/twenty-docs/l/de/twenty-ui/input/radio.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/input/radio.mdx @@ -1,5 +1,6 @@ --- title: Radio +icon: circle-dot --- diff --git a/packages/twenty-docs/l/de/twenty-ui/input/toggle.mdx b/packages/twenty-docs/l/de/twenty-ui/input/toggle.mdx index 797af2fb437..87e928a9742 100644 --- a/packages/twenty-docs/l/de/twenty-ui/input/toggle.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/input/toggle.mdx @@ -1,8 +1,8 @@ --- title: Umschalten +icon: toggle-on --- - diff --git a/packages/twenty-docs/l/de/twenty-ui/introduction.mdx b/packages/twenty-docs/l/de/twenty-ui/introduction.mdx index eb0d65af75c..7c6d41bc6ea 100644 --- a/packages/twenty-docs/l/de/twenty-ui/introduction.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/introduction.mdx @@ -1,5 +1,6 @@ --- title: Übersicht +icon: palette description: Komponentenbibliothek für Twenty CRM --- diff --git a/packages/twenty-docs/l/de/twenty-ui/navigation.mdx b/packages/twenty-docs/l/de/twenty-ui/navigation.mdx index d8724e9cc73..b25ac1ecacf 100644 --- a/packages/twenty-docs/l/de/twenty-ui/navigation.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/navigation.mdx @@ -1,5 +1,6 @@ --- title: Navigation +icon: compass --- diff --git a/packages/twenty-docs/l/de/twenty-ui/navigation/links.mdx b/packages/twenty-docs/l/de/twenty-ui/navigation/links.mdx index 2161f130b8e..6290843f8b0 100644 --- a/packages/twenty-docs/l/de/twenty-ui/navigation/links.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/navigation/links.mdx @@ -1,5 +1,6 @@ --- title: Links +icon: link --- diff --git a/packages/twenty-docs/l/de/twenty-ui/navigation/menu-item.mdx b/packages/twenty-docs/l/de/twenty-ui/navigation/menu-item.mdx index 6ded5de890b..0aafc242fd1 100644 --- a/packages/twenty-docs/l/de/twenty-ui/navigation/menu-item.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/navigation/menu-item.mdx @@ -1,8 +1,8 @@ --- title: Menüpunkt +icon: bars --- - Ein vielseitiger Menüpunkt, der in einem Menü oder einer Navigationsliste verwendet werden kann. diff --git a/packages/twenty-docs/l/de/twenty-ui/navigation/navigation-bar.mdx b/packages/twenty-docs/l/de/twenty-ui/navigation/navigation-bar.mdx index 90004f5a087..ccd3968dd10 100644 --- a/packages/twenty-docs/l/de/twenty-ui/navigation/navigation-bar.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/navigation/navigation-bar.mdx @@ -1,8 +1,8 @@ --- title: Navigationsleiste +icon: bars --- - Rendert eine Navigationsleiste, die mehrere `NavigationBarItem`-Komponenten enthält. diff --git a/packages/twenty-docs/l/de/twenty-ui/progress-bar.mdx b/packages/twenty-docs/l/de/twenty-ui/progress-bar.mdx index 200963d86c9..9883de524e3 100644 --- a/packages/twenty-docs/l/de/twenty-ui/progress-bar.mdx +++ b/packages/twenty-docs/l/de/twenty-ui/progress-bar.mdx @@ -2,7 +2,6 @@ title: Rückmeldung --- - Zeigt den Fortschritt oder Countdown an und bewegt sich von rechts nach links. diff --git a/packages/twenty-docs/l/de/user-guide/billing/overview.mdx b/packages/twenty-docs/l/de/user-guide/billing/overview.mdx index b451eb9d20f..0b0feb8ca4d 100644 --- a/packages/twenty-docs/l/de/user-guide/billing/overview.mdx +++ b/packages/twenty-docs/l/de/user-guide/billing/overview.mdx @@ -3,7 +3,6 @@ title: Abrechnung description: Verstehen Sie die Preisgestaltung von Twenty und verwalten Sie Ihr Abonnement. --- - Twenty bietet flexible Preispläne, die den Bedürfnissen Ihres Teams entsprechen. Verwalten Sie Ihr Abonnement, verfolgen Sie Workflow-Guthaben und greifen Sie auf Rechnungen zu – alles über **Einstellungen → Abrechnung**. ## Was enthält dieser Abschnitt diff --git a/packages/twenty-docs/l/de/user-guide/calendar-emails/overview.mdx b/packages/twenty-docs/l/de/user-guide/calendar-emails/overview.mdx index 21c14cacab1..aae610b1c99 100644 --- a/packages/twenty-docs/l/de/user-guide/calendar-emails/overview.mdx +++ b/packages/twenty-docs/l/de/user-guide/calendar-emails/overview.mdx @@ -3,7 +3,6 @@ title: Kalender & E-Mails description: Verbinden Sie Ihre E-Mail- und Kalenderkonten mit Twenty. --- - ## Verbindungsoptionen ### Google-Konto (Gmail & Google Kalender) diff --git a/packages/twenty-docs/l/de/user-guide/dashboards/overview.mdx b/packages/twenty-docs/l/de/user-guide/dashboards/overview.mdx index 39363b95435..f6312364fc5 100644 --- a/packages/twenty-docs/l/de/user-guide/dashboards/overview.mdx +++ b/packages/twenty-docs/l/de/user-guide/dashboards/overview.mdx @@ -3,7 +3,6 @@ title: Dashboards description: Erfahren Sie die Grundlagen von Reporting und Dashboards in Twenty. --- - Dashboards befinden sich derzeit in der Beta-Phase. Aktivieren Sie sie unter **Einstellungen → Updates → Early Access**. diff --git a/packages/twenty-docs/l/de/user-guide/data-migration/overview.mdx b/packages/twenty-docs/l/de/user-guide/data-migration/overview.mdx index 1e849b526ca..cf9b8816c3f 100644 --- a/packages/twenty-docs/l/de/user-guide/data-migration/overview.mdx +++ b/packages/twenty-docs/l/de/user-guide/data-migration/overview.mdx @@ -5,7 +5,6 @@ description: Importieren und exportieren Sie Ihre CRM-Daten über CSV-Dateien od import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## Importmethoden Twenty unterstützt zwei Hauptmethoden zum Importieren von Daten: diff --git a/packages/twenty-docs/l/de/user-guide/data-model/overview.mdx b/packages/twenty-docs/l/de/user-guide/data-model/overview.mdx index 3c99e6dc634..6137708b672 100644 --- a/packages/twenty-docs/l/de/user-guide/data-model/overview.mdx +++ b/packages/twenty-docs/l/de/user-guide/data-model/overview.mdx @@ -3,7 +3,6 @@ title: Datenmodell description: Erfahren Sie, was ein Datenmodell ist und wie Sie eines entwerfen, das zu Ihrem Unternehmen passt. --- - ## Was ist ein Datenmodell? Ein Datenmodell ist die Struktur, die definiert, wie Informationen in Ihrem CRM organisiert sind. Betrachten Sie es als den **Bauplan** Ihrer Kundendaten — Sie entwerfen ihn einmal und füllen ihn dann mit Ihren tatsächlichen Daten. diff --git a/packages/twenty-docs/l/de/user-guide/introduction.mdx b/packages/twenty-docs/l/de/user-guide/introduction.mdx index 30c131f25db..dd9967fbcdd 100644 --- a/packages/twenty-docs/l/de/user-guide/introduction.mdx +++ b/packages/twenty-docs/l/de/user-guide/introduction.mdx @@ -1,16 +1,11 @@ --- -title: Entdecken Sie Twenty +title: Benutzerhandbuch description: Willkommen im Twenty-Benutzerhandbuch, Ihre Anlaufstelle für erweiterte Konfigurationen und bewährte Verfahren. --- import { CardTitle } from "/snippets/card-title.mdx" - - Entdecken Sie Twenty - Erfahren Sie, was Twenty ist und wie es Ihrem Unternehmen helfen kann. - - Datenmodell Passen Sie Ihr Datenmodell an Ihre Geschäftsprozesse an. @@ -36,9 +31,9 @@ import { CardTitle } from "/snippets/card-title.mdx" Verstärken Sie Ihr Team mit KI-Agenten. - - Ansichten & Pipelines - Organisieren Sie Ihre Daten mit umsetzbaren Ansichten und Pipelines. + + Layout + Navigation, views, and record page customization. diff --git a/packages/twenty-docs/l/de/user-guide/layout/capabilities/navigation.mdx b/packages/twenty-docs/l/de/user-guide/layout/capabilities/navigation.mdx new file mode 100644 index 00000000000..bea88c2fe56 --- /dev/null +++ b/packages/twenty-docs/l/de/user-guide/layout/capabilities/navigation.mdx @@ -0,0 +1,32 @@ +--- +title: Navigation +description: Customize the left sidebar to match how your team works. +--- + +The left sidebar is your primary way to move around Twenty. It's fully customizable — you can reorganize it to match your workflow without touching any settings page. + +## Reordering items + +Drag and drop any item in the sidebar to change its position. The order is saved per user, so each team member can arrange their own sidebar. + +## Ordner + +Group related items into folders. For example, you might create a "Sales" folder containing your pipeline views, a "Support" folder for tickets, or an "Operations" folder for internal objects. + +To create a folder, right-click in the sidebar or use the `+` button. + +## Hiding objects + +Objects you don't use can be hidden from the sidebar. They're not deleted — they're just out of the way. You can show them again anytime from Settings > Data Model. + +## Favoriten + +Pin views, records, or searches to the Favorites section at the top of the sidebar for one-click access. Favorites are personal — each user manages their own. + +## Custom links + +Add links to external tools directly in the sidebar. Useful for linking to your wiki, dashboards in other tools, or any URL your team uses regularly. + +## Command menu + +Press `Cmd+K` (or `Ctrl+K`) to open the command menu — a quick-access search bar for jumping to any record, view, or action without navigating the sidebar. diff --git a/packages/twenty-docs/l/de/user-guide/layout/capabilities/record-pages.mdx b/packages/twenty-docs/l/de/user-guide/layout/capabilities/record-pages.mdx new file mode 100644 index 00000000000..34f1e611dfc --- /dev/null +++ b/packages/twenty-docs/l/de/user-guide/layout/capabilities/record-pages.mdx @@ -0,0 +1,51 @@ +--- +title: Datensatzseiten +description: Passen Sie das Layout einzelner Datensatz-Detailseiten mit Tabs und Widgets an. +--- + +Wenn Sie in Twenty einen Datensatz öffnen, besteht die Detailseite aus **Tabs** und **Widgets**. Beide lassen sich je Objekttyp vollständig anpassen. + +## Registerkarten + +Jede Datensatzseite kann mehrere Tabs haben — ähnlich wie Tabs in einem Browser. Verwenden Sie sie, um verschiedene Aspekte eines Datensatzes zu organisieren. Zum Beispiel könnte ein Unternehmensdatensatz Tabs für Übersicht, Kommunikation, Aufgaben und Dateien haben. + +Sie können: + +* Tabs hinzufügen und entfernen +* Tabs umbenennen +* Tabs durch Ziehen neu anordnen +* Festlegen, welcher Tab standardmäßig angezeigt wird + +## Widgets + +Widgets sind die Bausteine in jedem Tab. Zu den verfügbaren Widget-Typen gehören: + +| Widget | Was angezeigt wird | +| ------------------------- | -------------------------------------------------------- | +| **Felder** | Datensatzfelder, gruppiert oder einzeln | +| **Verknüpfte Datensätze** | Tabelle der über eine Beziehung verknüpften Datensätze | +| **E-Mails** | E-Mail-Verlauf aus verbundenen Konten | +| **Kalender** | Kalenderereignisse, die mit dem Datensatz verknüpft sind | +| **Zeitleiste** | Aktivitäts- und Ereignisverlauf | +| **Aufgaben** | Zugehörige Aufgaben | +| **Notizen** | Rich-Text-Notizen | +| **Dateien** | Dateianhänge | +| **Diagramme** | Visuelle Daten aus verknüpften Datensätzen | +| **iFrame** | Eingebettete externe Inhalte | +| **Rich-Text** | Statische Inhalte oder Beschreibungen | + +## Datensatzseite anpassen + +1. Öffnen Sie einen beliebigen Datensatz +2. Drücken Sie `Cmd+K` und suchen Sie nach "Layout der Datensatzseite bearbeiten" +3. Sie befinden sich jetzt im Anpassungsmodus: + * **Widgets hinzufügen** aus der Widget-Auswahl + * **Widgets ziehen**, um sie im Raster neu zu positionieren + * **Widgets in der Größe ändern**, indem Sie ihre Ränder ziehen + * **Felder konfigurieren**, die in jedem Widget angezeigt werden + * **Tabs verwalten** — hinzufügen, entfernen, umbenennen, neu anordnen +4. Speichern Sie Ihre Änderungen — sie gelten für alle Datensätze dieses Objekttyps + +## Feldsichtbarkeit + +In einem Felder-Widget können Sie steuern, welche Felder sichtbar sind und in welcher Reihenfolge. Damit können Sie fokussierte Layouts erstellen — beispielsweise, indem Sie auf dem Tab "Übersicht" nur die wichtigsten Felder anzeigen und detaillierte Felder in einen separaten Tab legen. diff --git a/packages/twenty-docs/l/de/user-guide/layout/overview.mdx b/packages/twenty-docs/l/de/user-guide/layout/overview.mdx new file mode 100644 index 00000000000..756882c278b --- /dev/null +++ b/packages/twenty-docs/l/de/user-guide/layout/overview.mdx @@ -0,0 +1,45 @@ +--- +title: Layout +description: Customize how you navigate, browse, and view records in Twenty. +--- + +Twenty's layout is customizable at three levels: how you navigate the app, how you browse lists of records, and what you see when you open an individual record. + +## Navigation + +The left sidebar is fully customizable. Sie können: + +* **Reorder items** by dragging and dropping +* **Create folders** to group related objects and views +* **Hide objects** you don't use +* **Add custom links** to external tools +* **Pin favorites** for quick access to views, records, or searches + +[Navigation reference →](/l/de/user-guide/layout/capabilities/navigation) + +## Ansichten + +Views control how lists of records are displayed. Twenty supports three view types: + +| Ansicht | Best for | +| ------------ | ---------------------------------------------------------------------- | +| **Table** | Working with many records at once — spreadsheet-style rows and columns | +| **Kanban** | Pipeline tracking — drag-and-drop cards organized by stage | +| **Calendar** | Time-based planning — records plotted by a date field | + +Each view saves its own filters, sorting, field visibility, and grouping configuration. Views can be shared with the workspace or kept private. + +[Table views →](/l/de/user-guide/views-pipelines/capabilities/table-views) · [Kanban views →](/l/de/user-guide/views-pipelines/capabilities/kanban-views) · [Calendar view →](/l/de/user-guide/views-pipelines/capabilities/calendar-view) + +## Record pages + +When you open a record, the detail page is built from configurable tabs and widgets. Sie können: + +* **Add, remove, and reorder tabs** on any record type +* **Configure widgets** — fields, related records, emails, timeline, calendar, tasks, notes, files, charts, iframes, and more +* **Drag and resize widgets** on a grid layout +* **Control field visibility** per widget + +Enter layout customization mode from the command menu (`Cmd+K` → "Edit record page layout"). + +[Record pages reference →](/l/de/user-guide/layout/capabilities/record-pages) diff --git a/packages/twenty-docs/l/de/user-guide/permissions-access/overview.mdx b/packages/twenty-docs/l/de/user-guide/permissions-access/overview.mdx index 8dd57d7406f..d6ad8c59c96 100644 --- a/packages/twenty-docs/l/de/user-guide/permissions-access/overview.mdx +++ b/packages/twenty-docs/l/de/user-guide/permissions-access/overview.mdx @@ -3,7 +3,6 @@ title: Berechtigungen & Zugriff description: Verwalten Sie Rollen, Berechtigungen und die Zugriffskontrolle in Ihrem Arbeitsbereich. --- - Das Berechtigungssystem von Twenty ermöglicht es Ihnen, zu steuern, wer in Ihrem Arbeitsbereich auf Daten zugreifen und diese ändern darf. Erstellen Sie Rollen, weisen Sie Berechtigungen zu und konfigurieren Sie SSO für sicheren Zugriff. ## Was Sie in diesem Abschnitt finden diff --git a/packages/twenty-docs/l/de/user-guide/settings/overview.mdx b/packages/twenty-docs/l/de/user-guide/settings/overview.mdx index 050de24bb99..dd9cf10ac2b 100644 --- a/packages/twenty-docs/l/de/user-guide/settings/overview.mdx +++ b/packages/twenty-docs/l/de/user-guide/settings/overview.mdx @@ -3,7 +3,6 @@ title: Einstellungen description: Richten Sie Ihren Twenty-Arbeitsbereich mit wesentlichen Einstellungen ein. --- - ## Ersteinrichtung Wenn Sie Ihren Arbeitsbereich zum ersten Mal erstellen, gibt es mehrere wichtige Einstellungen, die Sie konfigurieren sollten. diff --git a/packages/twenty-docs/l/de/user-guide/views-pipelines/overview.mdx b/packages/twenty-docs/l/de/user-guide/views-pipelines/overview.mdx index 9406c7902f3..816e5892984 100644 --- a/packages/twenty-docs/l/de/user-guide/views-pipelines/overview.mdx +++ b/packages/twenty-docs/l/de/user-guide/views-pipelines/overview.mdx @@ -5,7 +5,6 @@ description: Erfahren Sie, wie Sie in Twenty Ansichten erstellen und verwalten. import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## Ansichten verstehen Ansichten sind gespeicherte Konfigurationen, die festlegen, wie Ihre Daten angezeigt werden. Jede Ansicht kann Folgendes haben: diff --git a/packages/twenty-docs/l/de/user-guide/workflows/overview.mdx b/packages/twenty-docs/l/de/user-guide/workflows/overview.mdx index 832ada91a99..d24d395c41f 100644 --- a/packages/twenty-docs/l/de/user-guide/workflows/overview.mdx +++ b/packages/twenty-docs/l/de/user-guide/workflows/overview.mdx @@ -3,7 +3,6 @@ title: Workflows description: Erfahren Sie, wie Sie Automatisierungen in Twenty erstellen. --- - ## Warum Workflows wichtig sind Twenty wurde entwickelt, um den Nutzern maximale Flexibilität zu bieten. Anstatt Sie dazu zu zwingen, Ihre Geschäftsprozesse an starre, vorgefertigte Funktionen anzupassen, ermöglichen Ihnen Workflows, Automatisierungen zu erstellen, mit denen Sie Ihr CRM so gestalten, dass es Ihre individuellen geschäftlichen Anwendungsfälle optimal unterstützt. diff --git a/packages/twenty-docs/l/it/developers/contribute/capabilities/backend-development/server-commands.mdx b/packages/twenty-docs/l/it/developers/contribute/capabilities/backend-development/server-commands.mdx index 9c4a81476ae..fd2cfb1b113 100644 --- a/packages/twenty-docs/l/it/developers/contribute/capabilities/backend-development/server-commands.mdx +++ b/packages/twenty-docs/l/it/developers/contribute/capabilities/backend-development/server-commands.mdx @@ -1,5 +1,6 @@ --- title: Comandi Backend +icon: terminal --- ## Comandi utili diff --git a/packages/twenty-docs/l/it/developers/contribute/capabilities/bug-and-requests.mdx b/packages/twenty-docs/l/it/developers/contribute/capabilities/bug-and-requests.mdx index f8b03e702df..d5700c57fa3 100644 --- a/packages/twenty-docs/l/it/developers/contribute/capabilities/bug-and-requests.mdx +++ b/packages/twenty-docs/l/it/developers/contribute/capabilities/bug-and-requests.mdx @@ -1,5 +1,6 @@ --- title: Bug, richieste e Pull Request +icon: bug info: Segnala problemi, richiedi funzionalità e contribuisci al codice --- diff --git a/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/best-practices-front.mdx b/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/best-practices-front.mdx index ebb1e5a0551..971ee54ebdb 100644 --- a/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/best-practices-front.mdx +++ b/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/best-practices-front.mdx @@ -1,5 +1,6 @@ --- title: Migliori Pratiche +icon: star --- Questo documento descrive le migliori pratiche da seguire quando si lavora sul frontend. diff --git a/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx b/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx index 87b91aa8bc3..3fe871ab03a 100644 --- a/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx +++ b/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx @@ -1,5 +1,6 @@ --- title: Architettura delle Cartelle +icon: folder-tree info: Uno sguardo dettagliato all'architettura delle nostre cartelle --- diff --git a/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/frontend-commands.mdx b/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/frontend-commands.mdx index 77ea6146b32..f1fd6e2e63a 100644 --- a/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/frontend-commands.mdx +++ b/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/frontend-commands.mdx @@ -1,5 +1,6 @@ --- title: Comandi frontend +icon: terminal --- ## Comandi utili diff --git a/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/style-guide.mdx b/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/style-guide.mdx index b04e37721ff..8d60cb021cd 100644 --- a/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/style-guide.mdx +++ b/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/style-guide.mdx @@ -1,5 +1,6 @@ --- title: Guida di stile +icon: paintbrush --- Questo documento include le regole da seguire quando si scrive codice. diff --git a/packages/twenty-docs/l/it/developers/contribute/capabilities/local-setup.mdx b/packages/twenty-docs/l/it/developers/contribute/capabilities/local-setup.mdx index ce728198ca9..bf6c0b020d3 100644 --- a/packages/twenty-docs/l/it/developers/contribute/capabilities/local-setup.mdx +++ b/packages/twenty-docs/l/it/developers/contribute/capabilities/local-setup.mdx @@ -1,5 +1,6 @@ --- title: Configurazione Locale +icon: laptop-code description: La guida per i collaboratori (o sviluppatori curiosi) che vogliono eseguire Twenty localmente. --- diff --git a/packages/twenty-docs/l/it/developers/contribute/commands.mdx b/packages/twenty-docs/l/it/developers/contribute/commands.mdx new file mode 100644 index 00000000000..af7715182c9 --- /dev/null +++ b/packages/twenty-docs/l/it/developers/contribute/commands.mdx @@ -0,0 +1,77 @@ +--- +title: Commands +icon: terminal +description: Useful commands for developing Twenty. +--- + +Commands can be run from the repository root using `npx nx`. Use `npx nx run {project}:{command}` for explicit targeting. + +## Starting the App + +```bash +npx nx start twenty-front # Frontend dev server (http://localhost:3001) +npx nx start twenty-server # Backend server (http://localhost:3000) +npx nx run twenty-server:worker # Background worker +``` + +## Database + +```bash +npx nx database:reset twenty-server # Reset and seed database +npx nx run twenty-server:database:migrate:prod # Run migrations +npx nx run twenty-server:database:migrate:generate --name --type # Generate a migration +``` + +## Linting + +```bash +npx nx lint:diff-with-main twenty-front # Lint changed files (fastest) +npx nx lint:diff-with-main twenty-server +npx nx lint twenty-front --configuration=fix # Auto-fix +``` + +## Type Checking + +```bash +npx nx typecheck twenty-front +npx nx typecheck twenty-server +``` + +## Testing + +```bash +# Frontend +npx nx test twenty-front # Jest unit tests +npx nx storybook:build twenty-front # Build Storybook +npx nx storybook:test twenty-front # Storybook tests + +# Backend +npx nx run twenty-server:test:unit # Unit tests +npx nx run twenty-server:test:integration # Integration tests +npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset + +# Single file (fastest) +npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs +``` + +## GraphQL + +```bash +npx nx run twenty-front:graphql:generate # Regenerate types +npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema +``` + +## Traduzioni + +```bash +npx nx run twenty-front:lingui:extract # Extract strings +npx nx run twenty-front:lingui:compile # Compile translations +``` + +## Build + +```bash +npx nx build twenty-shared # Must be built first +npx nx build twenty-front +npx nx build twenty-server +``` diff --git a/packages/twenty-docs/l/it/developers/contribute/style-guide.mdx b/packages/twenty-docs/l/it/developers/contribute/style-guide.mdx new file mode 100644 index 00000000000..48fbef5e66e --- /dev/null +++ b/packages/twenty-docs/l/it/developers/contribute/style-guide.mdx @@ -0,0 +1,176 @@ +--- +title: Guida di stile +icon: paintbrush +description: Code conventions and best practices for contributing to Twenty. +--- + +## React + +### Functional components only + +Always use TSX functional components with named exports. + +```tsx +// ❌ Bad +const MyComponent = () => { + return
Hello World
; +}; +export default MyComponent; + +// ✅ Good +export function MyComponent() { + return
Hello World
; +}; +``` + +### Props + +Create a type named `{ComponentName}Props`. Use destructuring. Don't use `React.FC`. + +```tsx +type MyComponentProps = { + name: string; +}; + +export const MyComponent = ({ name }: MyComponentProps) =>
Hello {name}
; +``` + +### No single-variable prop spreading + +```tsx +// ❌ Bad +const MyComponent = (props: MyComponentProps) => ; + +// ✅ Good +const MyComponent = ({ prop1, prop2 }: MyComponentProps) => ; +``` + +## Gestione dello stato + +### Jotai atoms for global state + +```tsx +import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; +import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; + +export const myAtomState = createAtomState({ + key: 'myAtomState', + defaultValue: 'default value', +}); +``` + +* Prefer atoms over prop drilling +* Don't use `useRef` for state — use `useState` or atoms +* Use atom families and selectors for lists + +### Avoid unnecessary re-renders + +* Extract `useEffect` and data fetching into sibling sidecar components +* Prefer event handlers (`handleClick`, `handleChange`) over `useEffect` +* Don't use `React.memo()` — fix the root cause instead +* Limit `useCallback` / `useMemo` usage + +```tsx +// ❌ Bad — useEffect in the same component causes re-renders +export const Page = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return
{data}
; +}; + +// ✅ Good — extract into sibling +export const PageData = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return <>; +}; +export const Page = () => { + const [data] = useAtomState(dataState); + return
{data}
; +}; +``` + +## TypeScript + +* **`type` over `interface`** — more flexible, easier to compose +* **String literals over enums** — except for GraphQL codegen enums and internal library APIs +* **No `any`** — strict TypeScript enforced +* **No type imports** — use regular imports (enforced by Oxlint `typescript/consistent-type-imports`) +* **Use [Zod](https://github.com/colinhacks/zod)** for runtime validation of untyped objects + +## JavaScript + +```tsx +// Use nullish-coalescing (??) instead of || +const value = process.env.MY_VALUE ?? 'default'; + +// Use optional chaining +onClick?.(); +``` + +## Denominazione + +* **Variables**: camelCase, descriptive (`email` not `value`, `fieldMetadata` not `fm`) +* **Constants**: SCREAMING_SNAKE_CASE +* **Types/Classes**: PascalCase +* **Files/directories**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`) +* **Event handlers**: `handleClick` (not `onClick` for the handler function) +* **Component props**: prefix with component name (`ButtonProps`) +* **Styled components**: prefix with `Styled` (`StyledTitle`) + +## Stile + +Use [Linaria](https://github.com/callstack/linaria) styled components. Use theme values — avoid hardcoded `px`, `rem`, or colors. + +```tsx +// ❌ Bad +const StyledButton = styled.button` + color: #333333; + font-size: 1rem; + margin-left: 4px; +`; + +// ✅ Good +const StyledButton = styled.button` + color: ${({ theme }) => theme.font.color.primary}; + font-size: ${({ theme }) => theme.font.size.md}; + margin-left: ${({ theme }) => theme.spacing(1)}; +`; +``` + +## Importa + +Use aliases instead of relative paths: + +```tsx +// ❌ Bad +import { Foo } from '../../../../../testing/decorators/Foo'; + +// ✅ Good +import { Foo } from '~/testing/decorators/Foo'; +import { Bar } from '@/modules/bar/components/Bar'; +``` + +## Folder Structure + +``` +front +└── modules/ # Feature modules +│ └── module1/ +│ ├── components/ +│ ├── constants/ +│ ├── contexts/ +│ ├── graphql/ (fragments, queries, mutations) +│ ├── hooks/ +│ ├── states/ (atoms, selectors) +│ ├── types/ +│ └── utils/ +└── pages/ # Route-level components +└── ui/ # Reusable UI components (display, input, feedback, ...) +``` + +* Modules can import from other modules, but `ui/` should stay dependency-free +* Use `internal/` subfolders for module-private code +* Components under 300 lines, services under 500 lines diff --git a/packages/twenty-docs/l/it/developers/extend/api.mdx b/packages/twenty-docs/l/it/developers/extend/api.mdx index 0cca51af44b..581c645be0b 100644 --- a/packages/twenty-docs/l/it/developers/extend/api.mdx +++ b/packages/twenty-docs/l/it/developers/extend/api.mdx @@ -1,147 +1,55 @@ --- title: API -description: Interroga e modifica i dati del tuo CRM in modo programmatico usando REST o GraphQL. +icon: plug +description: REST and GraphQL APIs generated from your workspace schema. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -Twenty è stato progettato per essere adatto agli sviluppatori, offrendo potenti API che si adattano al tuo modello di dati personalizzato. Forniamo quattro tipi distinti di API per soddisfare diverse esigenze di integrazione. +## Schema-per-tenant APIs -## Approccio incentrato sullo sviluppatore +There is no static API reference for Twenty. Each workspace has its own schema — when you add a custom object (say `Invoice`), it immediately gets REST and GraphQL endpoints identical to built-in objects like `Company` or `Person`. The API is generated from the schema, so endpoints use your object and field names directly — no opaque IDs. -Twenty genera API specifiche per il tuo modello di dati: +Your workspace-specific API documentation is available under **Settings → API & Webhooks** after creating an API key. It includes an interactive playground where you can execute real calls against your data. -* **Nessun ID lungo richiesto**: Utilizza direttamente i nomi degli oggetti e dei campi negli endpoint -* **Oggetti standard e personalizzati trattati allo stesso modo**: I tuoi oggetti personalizzati ricevono lo stesso trattamento API di quelli predefiniti -* **Endpoint dedicati**: Ogni oggetto e campo ottiene il proprio endpoint API -* **Documentazione personalizzata**: Generata specificamente per il modello di dati del tuo workspace +## Two APIs - -La documentazione personalizzata delle tue API è disponibile in **Impostazioni → API & Webhooks** dopo aver creato una chiave API. Poiché Twenty genera API che corrispondono al tuo modello di dati personalizzato, la documentazione è unica per il tuo workspace. - +**Core API** — `/rest/` and `/graphql/` -## I due tipi di API +CRUD on records: People, Companies, Opportunities, your custom objects. Query, filter, traverse relations. -### Core API +**Metadata API** — `/rest/metadata/` and `/metadata/` -Accessibile su `/rest/` o `/graphql/` +Schema management: create/modify/delete objects, fields, and relations. This is how you programmatically change your data model. -Lavora con i tuoi **record** reali (i dati): +Both are available as REST and GraphQL. GraphQL adds batch upserts and the ability to traverse relations in a single query. Same underlying data either way. -* Crea, leggi, aggiorna, elimina Persone, Aziende, Opportunità, ecc. -* Interroga e filtra i dati -* Gestisci le relazioni tra i record +## Base URLs -### Metadata API - -Accessibile su `/rest/metadata/` o `/metadata/` - -Gestisci il tuo **workspace e il modello di dati**: - -* Crea, modifica o elimina oggetti e campi -* Configura le impostazioni del workspace -* Definisci le relazioni tra oggetti - -## REST vs GraphQL - -Sia le API Core che le API Metadata sono disponibili nei formati REST e GraphQL: - -| Formato | Operazioni disponibili | -| ----------- | ------------------------------------------------------------------------------------- | -| **REST** | CRUD, operazioni batch, upsert | -| **GraphQL** | Stesse funzionalità + **upsert in batch**, query sulle relazioni in un'unica chiamata | - -Scegli in base alle tue esigenze — entrambi i formati accedono agli stessi dati. - -## Endpoint API - -| Ambiente | URL di base | -| ----------------- | ------------------------- | -| **Cloud** | `https://api.twenty.com/` | -| **Auto-ospitato** | `https://{your-domain}/` | +| Ambiente | URL di base | +| ----------- | ------------------------- | +| Cloud | `https://api.twenty.com/` | +| Self-Hosted | `https://{your-domain}/` | ## Autenticazione -Ogni richiesta API richiede una chiave API nell'intestazione: - ``` Authorization: Bearer YOUR_API_KEY ``` -### Crea una chiave API - -1. Vai a **Impostazioni → APIs & Webhooks** -2. Fai clic su **+ Crea chiave** -3. Configura: - * **Nome**: Nome descrittivo per la chiave - * **Data di scadenza**: Quando la chiave scade -4. Fai clic su **Salva** -5. **Copia subito** — la chiave viene mostrata una sola volta +Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access. - -La tua chiave API concede l'accesso a dati sensibili. Non condividerla con servizi non affidabili. Se compromessa, disabilitala immediatamente e generane una nuova. - +For OAuth-based access (external apps acting on behalf of users), see [OAuth](/l/it/developers/extend/oauth). -### Assegna un ruolo a una chiave API +## Batch operations -Per una maggiore sicurezza, assegna un ruolo specifico per limitare l'accesso: +Both REST and GraphQL support batching up to 60 records per request — create, update, or delete. GraphQL also supports batch upsert (create-or-update in one call) using plural names like `CreateCompanies`. -1. Vai a **Impostazioni → Ruoli** -2. Fai clic sul ruolo da assegnare -3. Apri la scheda **Assegnazione** -4. In **Chiavi API**, fai clic su **+ Assegna alla chiave API** -5. Seleziona la chiave API +## Rate limits -La chiave erediterà le autorizzazioni di quel ruolo. Vedi [Autorizzazioni](/l/it/user-guide/permissions-access/capabilities/permissions) per i dettagli. - -### Gestisci chiavi API - -**Rigenera**: Impostazioni → APIs & Webhooks → Fai clic sulla chiave → **Rigenera** - -**Elimina**: Impostazioni → APIs & Webhooks → Fai clic sulla chiave → **Elimina** - -## Playground API - -Testa le tue API direttamente nel browser con il nostro playground integrato — disponibile sia per **REST** sia per **GraphQL**. - -### Accedi al Playground - -1. Vai a **Impostazioni → APIs & Webhooks** -2. Crea una chiave API (obbligatorio) -3. Fai clic su **REST API** o **GraphQL API** per aprire il playground - -### Cosa ottieni - -* **Documentazione interattiva**: Generata per il tuo specifico modello di dati -* **Test in tempo reale**: Esegui chiamate API reali sul tuo workspace -* **Esploratore dello schema**: Sfoglia gli oggetti, i campi e le relazioni disponibili -* **Generatore di richieste**: Crea query con completamento automatico - -Il playground riflette i tuoi oggetti e campi personalizzati, quindi la documentazione è sempre accurata per il tuo workspace. - -## Operazioni Batch - -Sia REST che GraphQL supportano operazioni batch: - -* **Dimensione batch**: Fino a 60 record per richiesta -* **Operazioni**: Creare, aggiornare, eliminare più record - -**Funzionalità esclusive di GraphQL:** - -* **Upsert in batch**: Crea o aggiorna in un'unica chiamata -* Usa nomi oggetto al plurale (ad es. `CreateCompanies` invece di `CreateCompany`) - -## Limiti di frequenza delle API - -Le richieste API sono limitate per garantire la stabilità della piattaforma: - -| Limite | Valore | -| -------------------- | ---------------------- | -| **Richieste** | 100 chiamate al minuto | -| **Dimensione batch** | 60 record per chiamata | - - -Usa le operazioni batch per massimizzare il throughput — elabora fino a 60 record in una singola chiamata API invece di effettuare richieste individuali. - +| Limite | Valore | +| ---------- | ---------------------- | +| Requests | 100 per minute | +| Batch size | 60 record per chiamata | diff --git a/packages/twenty-docs/l/it/developers/extend/apps/building.mdx b/packages/twenty-docs/l/it/developers/extend/apps/building.mdx index b18e6e760d9..e90da36c4f7 100644 --- a/packages/twenty-docs/l/it/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/it/developers/extend/apps/building.mdx @@ -1,2062 +1,104 @@ --- -title: Creazione di app -description: Definisci oggetti, funzioni logiche, componenti front-end e molto altro con il Twenty SDK. +title: Architettura +description: How Twenty apps work — sandboxing, lifecycle, and the building blocks. +icon: sitemap --- - - Le app sono attualmente in fase alfa. La funzionalità funziona ma è ancora in evoluzione. - +Twenty apps are TypeScript packages that extend your workspace with custom objects, logic, UI components, and AI capabilities. They run on the Twenty platform with full sandboxing and permission controls. -Il pacchetto `twenty-sdk` fornisce blocchi costruttivi tipizzati per creare la tua app. Questa pagina copre tutti i tipi di entità e i client API disponibili nell'SDK. +## How apps work -## Funzioni DefineEntity +An app is a collection of **entities** declared using `defineEntity()` functions from the `twenty-sdk` package. The SDK detects these declarations via AST analysis at build time and produces a **manifest** — a complete description of what your app adds to a workspace. -L'SDK fornisce funzioni per definire le entità della tua app. Devi usare `export default defineEntity({...})` affinché l'SDK rilevi le tue entità. Queste funzioni convalidano la configurazione in fase di build e offrono il completamento automatico nell'IDE e la sicurezza dei tipi. - - - **L'organizzazione dei file dipende da te.** - Il rilevamento delle entità è basato sull'AST — l'SDK trova le chiamate a `export default defineEntity(...)` indipendentemente da dove si trova il file. Raggruppare i file per tipo (ad es., `logic-functions/`, `roles/`) è solo una convenzione, non un requisito. - - - - - -I ruoli incapsulano i permessi sugli oggetti e sulle azioni del tuo spazio di lavoro. - -```ts restricted-company-role.ts -import { - defineRole, - PermissionFlag, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -export default defineRole({ - universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', - label: 'My new role', - description: 'A role that can be used in your workspace', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - fieldUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); ``` - - - - -Ogni app deve avere esattamente una chiamata a `defineApplication` che descrive: - -* **Identità**: identificatori, nome visualizzato e descrizione. -* **Autorizzazioni**: quale ruolo usano le sue funzioni e i componenti front-end. -* **Variabili (opzionali)**: coppie chiave–valore esposte alle funzioni come variabili d'ambiente. -* **(Opzionali) Funzioni di pre-installazione/post-installazione**: funzioni logiche che vengono eseguite prima o dopo l'installazione. - -```ts src/application-config.ts -import { defineApplication } from 'twenty-sdk/define'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Note: -* I campi `universalIdentifier` sono ID deterministici che possiedi. Generali una volta e mantienili stabili tra una sincronizzazione e l'altra. -* `applicationVariables` diventano variabili d'ambiente per le tue funzioni e i componenti front-end (ad esempio, `DEFAULT_RECIPIENT_NAME` è disponibile come `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` deve fare riferimento a un ruolo definito con `defineRole()` (vedi sopra). -* Le funzioni di pre-installazione e post-installazione vengono rilevate automaticamente durante il build del manifesto — non è necessario farvi riferimento in `defineApplication()`. - -#### Metadati del marketplace - -Se prevedi di [pubblicare la tua app](/l/it/developers/extend/apps/publishing), questi campi opzionali controllano come appare nel marketplace: - -| Campo | Descrizione | -| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | -| `autore` | Nome dell'autore o dell'azienda | -| `categoria` | Categoria dell'app per il filtraggio nel marketplace | -| `logoUrl` | Percorso al logo della tua app (ad es., `public/logo.png`) | -| `screenshots` | Array di percorsi degli screenshot (ad es., `public/screenshot-1.png`) | -| `aboutDescription` | Descrizione markdown più lunga per la scheda "Informazioni". Se omesso, il marketplace utilizza il `README.md` del pacchetto da npm | -| `websiteUrl` | Link al tuo sito web | -| `termsUrl` | Link ai Termini di servizio | -| `emailSupport` | Indirizzo email di supporto | -| `issueReportUrl` | Link al sistema di tracciamento dei problemi | - -#### Ruoli e permessi - -Il `defaultRoleUniversalIdentifier` in `application-config.ts` indica il ruolo predefinito utilizzato dalle funzioni logiche e dai componenti front-end della tua app. Vedi `defineRole` sopra per i dettagli. - -* Il token di runtime iniettato come `TWENTY_APP_ACCESS_TOKEN` è derivato da questo ruolo. -* Il client tipizzato è limitato ai permessi concessi a quel ruolo. -* Segui il principio del privilegio minimo: crea un ruolo dedicato con solo i permessi necessari alle tue funzioni. - -##### Ruolo funzione predefinito - -Quando esegui lo scaffolding di una nuova app, la CLI crea un file di ruolo predefinito: - -```ts src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk/define'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: true, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [], - fieldPermissions: [], - permissionFlags: [], -}); -``` - -L'`universalIdentifier` di questo ruolo viene referenziato in `application-config.ts` come `defaultRoleUniversalIdentifier`: - -* **\*.role.ts** definisce ciò che il ruolo può fare. -* **application-config.ts** punta a quel ruolo in modo che le tue funzioni ne ereditino i permessi. - -Note: -* Parti dal ruolo generato dallo scaffolder, quindi restringilo progressivamente seguendo il principio del privilegio minimo. -* Sostituisci `objectPermissions` e `fieldPermissions` con gli oggetti e i campi di cui le tue funzioni hanno realmente bisogno. -* `permissionFlags` controllano l'accesso alle funzionalità a livello di piattaforma. Mantienili al minimo. -* Vedi un esempio funzionante: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - - - - -Gli oggetti personalizzati descrivono sia lo schema sia il comportamento per i record nel tuo spazio di lavoro. Usa `defineObject()` per definire oggetti con convalida integrata: - -```ts postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk/define'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Punti chiave: - -* Usa `defineObject()` per una convalida integrata e un migliore supporto IDE. -* Il `universalIdentifier` deve essere univoco e stabile tra i deployment. -* Ogni campo richiede un `name`, `type`, `label` e il proprio `universalIdentifier` stabile. -* L'array `fields` è facoltativo: puoi definire oggetti senza campi personalizzati. -* Puoi generare nuovi oggetti con `yarn twenty add`, che ti guida nella denominazione, nei campi e nelle relazioni. - - -**I campi base vengono creati automaticamente.** Quando definisci un oggetto personalizzato, Twenty aggiunge automaticamente i campi standard -come `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` e `deletedAt`. -Non è necessario definirli nel tuo array `fields` — aggiungi solo i tuoi campi personalizzati. -Puoi sovrascrivere i campi predefiniti definendo un campo con lo stesso nome nel tuo array `fields`, -ma non è consigliato. - - - - - -Usa `defineField()` per aggiungere campi a oggetti che non possiedi — come gli oggetti standard di Twenty (Person, Company, ecc.) o oggetti di altre app. A differenza dei campi inline in `defineObject()`, i campi autonomi richiedono un `objectUniversalIdentifier` per specificare quale oggetto estendono: - -```ts src/fields/company-loyalty-tier.field.ts -import { defineField, FieldType } from 'twenty-sdk/define'; - -export default defineField({ - universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', - objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object - name: 'loyaltyTier', - type: FieldType.SELECT, - label: 'Loyalty Tier', - icon: 'IconStar', - options: [ - { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, - { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, - { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, - ], -}); -``` - -Punti chiave: -* `objectUniversalIdentifier` identifica l'oggetto di destinazione. Per gli oggetti standard, usa `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` esportati da `twenty-sdk`. -* Quando definisci campi inline in `defineObject()`, **non** hai bisogno di `objectUniversalIdentifier` — viene ereditato dall'oggetto padre. -* `defineField()` è l'unico modo per aggiungere campi a oggetti che non hai creato con `defineObject()`. - - - - -Le relazioni collegano gli oggetti tra loro. In Twenty, le relazioni sono sempre **bidirezionali** — definisci entrambi i lati e ciascun lato fa riferimento all'altro. - -Esistono due tipi di relazione: - -| Tipo di relazione | Descrizione | Ha una chiave esterna? | -| ----------------- | --------------------------------------------------------------------- | ---------------------- | -| `MANY_TO_ONE` | Molti record di questo oggetto puntano a un record della destinazione | Sì (`joinColumnName`) | -| `ONE_TO_MANY` | Un record di questo oggetto ha molti record della destinazione | No (lato inverso) | - -#### Come funzionano le relazioni - -Ogni relazione richiede **due campi** che fanno riferimento l'uno all'altro: - -1. Il lato **MANY_TO_ONE** — risiede sull'oggetto che detiene la chiave esterna -2. Il lato **ONE_TO_MANY** — risiede sull'oggetto che possiede la collezione - -Entrambi i campi usano `FieldType.RELATION` e si riferiscono reciprocamente tramite `relationTargetFieldMetadataUniversalIdentifier`. - -#### Esempio: Post Card ha molti destinatari - -Supponiamo che un `PostCard` possa essere inviato a molti record `PostCardRecipient`. Ogni destinatario appartiene esattamente a una sola cartolina. - -**Passaggio 1: definisci il lato ONE_TO_MANY su PostCard** (il lato "uno"): - -```ts src/fields/post-card-recipients-on-post-card.field.ts -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; -// Import from the other side -import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; - -export default defineField({ - universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCardRecipients', - label: 'Post Card Recipients', - icon: 'IconUsers', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); -``` - -**Passaggio 2: definisci il lato MANY_TO_ONE su PostCardRecipient** (il lato "molti" — contiene la chiave esterna): - -```ts src/fields/post-card-on-post-card-recipient.field.ts -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; -// Import from the other side -import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; - -export default defineField({ - universalIdentifier: POST_CARD_FIELD_ID, - objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - icon: 'IconMail', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, -}); +your-app/ +├── src/ +│ ├── application-config.ts ← defineApplication (required, one per app) +│ ├── roles/ ← defineRole +│ ├── objects/ ← defineObject +│ ├── fields/ ← defineField +│ ├── logic-functions/ ← defineLogicFunction +│ ├── front-components/ ← defineFrontComponent +│ ├── skills/ ← defineSkill +│ ├── agents/ ← defineAgent +│ ├── views/ ← defineView +│ ├── navigation-menu-items/ ← defineNavigationMenuItem +│ └── page-layouts/ ← definePageLayout +├── public/ ← Static assets (images, icons) +└── package.json ``` -**Importazioni circolari:** Entrambi i campi di relazione fanno riferimento all'`universalIdentifier` dell'altro. Per evitare problemi di importazioni circolari, esporta gli ID dei campi come costanti denominate da ciascun file e importale nell'altro file. Il sistema di build le risolve in fase di compilazione. + **File organization is up to you.** Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. The folder structure above is a convention, not a requirement. -#### Relazioni con gli oggetti standard - -Per creare una relazione con un oggetto Twenty integrato (Person, Company, ecc.), usa `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: - -```ts src/fields/person-on-self-hosting-user.field.ts -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; -import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; - -export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; -export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; - -export default defineField({ - universalIdentifier: PERSON_FIELD_ID, - objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - description: 'Person matching with the self hosting user', - isNullable: true, - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'personId', - }, -}); -``` - -#### Proprietà dei campi di relazione - -| Proprietà | Obbligatorio | Descrizione | -| ------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------- | -| `tipo` | Sì | Deve essere `FieldType.RELATION` | -| `relationTargetObjectMetadataUniversalIdentifier` | Sì | L'`universalIdentifier` dell'oggetto di destinazione | -| `relationTargetFieldMetadataUniversalIdentifier` | Sì | L'`universalIdentifier` del campo corrispondente sull'oggetto di destinazione | -| `universalSettings.relationType` | Sì | `RelationType.MANY_TO_ONE` or `RelationType.ONE_TO_MANY` | -| `universalSettings.onDelete` | Solo MANY_TO_ONE | Cosa accade quando il record referenziato viene eliminato: `CASCADE`, `SET_NULL`, `RESTRICT` o `NO_ACTION` | -| `universalSettings.joinColumnName` | Solo MANY_TO_ONE | Nome della colonna del database per la chiave esterna (ad es., `postCardId`) | - -#### Campi di relazione inline in defineObject - -Puoi anche definire i campi di relazione direttamente all'interno di `defineObject()`. In tal caso, ometti `objectUniversalIdentifier` — viene ereditato dall'oggetto padre: - -```ts -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCardRecipient', - // ... - fields: [ - { - universalIdentifier: POST_CARD_FIELD_ID, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, - }, - // ... other fields - ], -}); -``` - - - -Ogni file di funzione usa `defineLogicFunction()` per esportare una configurazione con un handler e trigger opzionali. - -```ts src/logic-functions/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - httpRouteTriggerSettings: { - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: true, - }, - /*databaseEventTriggerSettings: { - eventName: 'people.created', - },*/ - /*cronTriggerSettings: { - pattern: '0 0 1 1 *', - },*/ -}); -``` - -Tipi di trigger disponibili: -* **httpRoute**: Espone la tua funzione su un percorso e metodo HTTP **sotto l'endpoint `/s/`**: -> ad es. `path: '/post-card/create'` è invocabile su `https://your-twenty-server.com/s/post-card/create` -* **cron**: Esegue la tua funzione secondo una pianificazione utilizzando un'espressione CRON. -* **databaseEvent**: Viene eseguito sugli eventi del ciclo di vita degli oggetti dello spazio di lavoro. Quando l'operazione dell'evento è `updated`, è possibile specificare campi specifici da monitorare nell'array `updatedFields`. Se lasciato non definito o vuoto, qualsiasi aggiornamento attiverà la funzione. -> ad es. `person.updated`, `*.created`, `company.*` - - -Puoi anche eseguire manualmente una funzione utilizzando la CLI: - -```bash filename="Terminal" -yarn twenty exec -n create-new-post-card -p '{"key": "value"}' -``` - -```bash filename="Terminal" -yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -Puoi osservare i log con: - -```bash filename="Terminal" -yarn twenty logs -``` - - -#### Payload del trigger di route - -Quando un trigger di tipo route invoca la tua funzione logica, questa riceve un oggetto `RoutePayload` che segue il [formato AWS HTTP API v2](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). -Importa il tipo `RoutePayload` da `twenty-sdk`: - -```ts -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; - -const handler = async (event: RoutePayload) => { - const { headers, queryStringParameters, pathParameters, body } = event; - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -Il tipo `RoutePayload` ha la seguente struttura: - - | Proprietà | Tipo | Descrizione | Esempio | - | ---------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------- | - | `headers` | `Record\` | Intestazioni HTTP (solo quelle elencate in `forwardedRequestHeaders`) | vedi la sezione sotto | - | `queryStringParameters` | `Record\` | Parametri della query string (valori multipli uniti da virgole) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | - | `pathParameters` | `Record\` | Parametri di percorso estratti dal pattern della route | `/users/:id`, `/users/123` -> `{ id: '123' }` | - | `body` | `object \| null` | Corpo della richiesta analizzato (JSON) | `{ id: 1 }` -> `{ id: 1 }` | - | `isBase64Encoded` | `boolean` | Indica se il corpo è codificato in base64 | | - | `requestContext.http.method` | `string` | Metodo HTTP (GET, POST, PUT, PATCH, DELETE) | | - | `requestContext.http.path` | `string` | Percorso della richiesta non elaborato | | - - -#### forwardedRequestHeaders - -Per impostazione predefinita, le intestazioni HTTP delle richieste in ingresso **non** vengono passate alla tua funzione logica per motivi di sicurezza. -Per accedere a intestazioni specifiche, elencale nell'array `forwardedRequestHeaders`: - -```ts -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - httpRouteTriggerSettings: { - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, -}); -``` - -Nel tuo handler, accedi alle intestazioni inoltrate in questo modo: - -```ts -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - -I nomi delle intestazioni vengono normalizzati in minuscolo. Accedile usando chiavi in minuscolo (ad es., `event.headers['content-type']`). - - -#### Esporre una funzione come strumento - -Le funzioni logiche possono essere esposte come **strumenti** per gli agenti di IA e i flussi di lavoro. Quando una funzione è contrassegnata come strumento, diventa individuabile dalle funzionalità di IA di Twenty e può essere utilizzata nelle automazioni dei flussi di lavoro. - -Per contrassegnare una funzione logica come strumento, imposta `isTool: true`: - -```ts src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, -}); -``` - -Punti chiave: - -* Puoi combinare `isTool` con i trigger — una funzione può essere sia uno strumento (invocabile dagli agenti IA) sia attivata da eventi allo stesso tempo. -* **`toolInputSchema`** (opzionale): un oggetto JSON Schema che descrive i parametri accettati dalla funzione. Lo schema viene calcolato automaticamente dall'analisi statica del codice sorgente, ma puoi impostarlo esplicitamente: - -```ts -export default defineLogicFunction({ - ..., - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - - -**Scrivi una buona `description`.** Gli agenti IA fanno affidamento sul campo `description` della funzione per decidere quando usare lo strumento. Sii specifico su cosa fa lo strumento e quando dovrebbe essere invocato. - - - - - -Una funzione post-installazione è una funzione logica che viene eseguita automaticamente dopo che la tua app è stata installata in uno spazio di lavoro. Il server la esegue **dopo** che i metadati dell'app sono stati sincronizzati e il client SDK è stato generato, così lo spazio di lavoro è completamente pronto per l'uso e il nuovo schema è attivo. I casi d'uso tipici includono il popolamento di dati predefiniti, la creazione di record iniziali, la configurazione delle impostazioni dello spazio di lavoro o il provisioning di risorse su servizi di terze parti. - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - shouldRunSynchronously: false, - handler, -}); -``` - -Puoi anche eseguire manualmente la funzione di post-installazione in qualsiasi momento utilizzando la CLI: - -```bash filename="Terminal" -yarn twenty exec --postInstall -``` - -Punti chiave: -* Le funzioni di post-installazione utilizzano `definePostInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* L'handler riceve un `InstallPayload` con `{ previousVersion?: string; newVersion: string }` — `newVersion` è la versione in fase di installazione e `previousVersion` è la versione installata in precedenza (oppure `undefined` in caso di nuova installazione). Usa questi valori per distinguere le nuove installazioni dagli aggiornamenti e per eseguire logiche di migrazione specifiche per versione. -* **Quando viene eseguito l'hook**: solo sulle nuove installazioni, per impostazione predefinita. Passa `shouldRunOnVersionUpgrade: true` se vuoi che venga eseguito anche quando l'app viene aggiornata da una versione precedente. Se omesso, il flag è `false` per impostazione predefinita e gli aggiornamenti saltano l'hook. -* **Modello di esecuzione — asincrono per impostazione predefinita, sincrono su richiesta**: il flag `shouldRunSynchronously` controlla *come* viene eseguito il post-install. - * `shouldRunSynchronously: false` *(default)* — l'hook viene **messo in coda nella coda dei messaggi** con `retryLimit: 3` ed eseguito in modo asincrono in un worker. La risposta di installazione viene restituita non appena il job è messo in coda, quindi un handler lento o in errore non blocca il chiamante. Il worker riproverà fino a tre volte. **Usalo per job di lunga durata** — popolamento di dataset di grandi dimensioni, chiamate a API di terze parti lente, provisioning di risorse esterne, qualsiasi cosa che possa superare una finestra di risposta HTTP ragionevole. - * `shouldRunSynchronously: true` — l'hook viene eseguito **inline durante il flusso di installazione** (stesso executor del pre-install). La richiesta di installazione rimane bloccata finché l'handler non termina e, se genera un'eccezione, il chiamante dell'installazione riceve un `POST_INSTALL_ERROR`. Nessun tentativo automatico. **Usalo per attività rapide che devono completarsi prima della risposta** — ad esempio, emettere un errore di validazione all'utente, oppure un setup rapido di cui il client avrà bisogno immediatamente dopo il ritorno della chiamata di installazione. Tieni presente che la migrazione dei metadati è già stata applicata quando viene eseguito il post-install, quindi un errore in modalità sincrona **non** annulla le modifiche allo schema — si limita a far emergere l'errore. -* Assicurati che il tuo handler sia idempotente. In modalità asincrona la coda può riprovare fino a tre volte; in entrambe le modalità l'hook può essere eseguito di nuovo durante gli aggiornamenti quando `shouldRunOnVersionUpgrade: true`. -* Le variabili d'ambiente `APPLICATION_ID`, `APP_ACCESS_TOKEN` e `API_URL` sono disponibili all'interno dell'handler (come in qualsiasi altra funzione logica), quindi puoi chiamare le API di Twenty con un token di accesso applicativo con ambito sulla tua app. -* È consentita una sola funzione di post-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una. -* I campi `universalIdentifier`, `shouldRunOnVersionUpgrade` e `shouldRunSynchronously` della funzione vengono associati automaticamente al manifest dell'applicazione nel campo `postInstallLogicFunction` durante la build — non è necessario referenziarli in `defineApplication()`. -* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di configurazione più lunghe, come il popolamento dei dati. -* **Non eseguito in modalità dev**: quando un'app è registrata in locale (tramite `yarn twenty dev`), il server salta completamente il flusso di installazione e sincronizza i file direttamente tramite il watcher della CLI — quindi il post-install non viene mai eseguito in modalità dev, indipendentemente da `shouldRunSynchronously`. Usa `yarn twenty exec --postInstall` per attivarlo manualmente su un workspace in esecuzione. - - - - -Una funzione di pre-install è una funzione logica che viene eseguita automaticamente durante l'installazione, **prima che venga applicata la migrazione dei metadati del workspace**. Condivide la stessa struttura di payload del post-install (`InstallPayload`), ma è posizionata prima nel flusso di installazione così da poter preparare lo stato da cui dipenderà la migrazione imminente — usi tipici includono il backup dei dati, la validazione della compatibilità con il nuovo schema o l'archiviazione di record che stanno per essere ristrutturati o eliminati. - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -Puoi anche eseguire manualmente la funzione di pre-installazione in qualsiasi momento utilizzando la CLI: - -```bash filename="Terminal" -yarn twenty exec --preInstall -``` - -Punti chiave: -* Le funzioni di pre-install usano `definePreInstallLogicFunction()` — stessa configurazione specialistica del post-install, solo agganciata a uno slot di ciclo di vita diverso. -* Sia gli handler di pre- sia quelli di post-install ricevono lo stesso tipo `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Importalo una volta e riutilizzalo per entrambi gli hook. -* **Quando viene eseguito l'hook**: posizionato appena prima della migrazione dei metadati del workspace (`synchronizeFromManifest`). Prima dell'esecuzione, il server esegue una "sincronizzazione ridotta" puramente additiva che registra nei metadati del workspace la funzione di pre-install della versione **nuova** — nient'altro viene toccato — e poi la esegue. Poiché questa sincronizzazione è solo additiva, gli oggetti, i campi e i dati della versione precedente restano intatti quando il tuo handler viene eseguito: puoi leggere ed eseguire in sicurezza il backup dello stato pre-migrazione. -* **Modello di esecuzione**: il pre-install è eseguito **in modo sincrono** e **blocca l'installazione**. Se l'handler genera un'eccezione, l'installazione viene interrotta prima che vengano applicate modifiche allo schema — il workspace rimane sulla versione precedente in uno stato coerente. Questo è intenzionale: il pre-install è la tua ultima possibilità per rifiutare un aggiornamento rischioso. -* Come per il post-install, è consentita una sola funzione di pre-installazione per applicazione. Viene collegata automaticamente al manifest dell'applicazione nel campo `preInstallLogicFunction` durante la build. -* **Non eseguito in modalità dev**: come per il post-install — il flusso di installazione viene completamente saltato per le app registrate localmente, quindi il pre-install non viene mai eseguito con `yarn twenty dev`. Usa `yarn twenty exec --preInstall` per attivarlo manualmente. - - - - -Entrambi gli hook fanno parte dello stesso flusso di installazione e ricevono lo stesso `InstallPayload`. La differenza è **quando** vengono eseguiti rispetto alla migrazione dei metadati del workspace, e questo modifica quali dati possono gestire in sicurezza. +## Entity types + +| Entità | Scopo | Documentazione | +| ------------------------ | ----------------------------------------- | ------------------------------------------------------------ | +| **Application** | App identity, permissions, variables | [Data Model](/l/it/developers/extend/apps/data-model) | +| **Role** | Permission sets for objects and fields | [Data Model](/l/it/developers/extend/apps/data-model) | +| **Oggetto** | Custom data tables with fields | [Data Model](/l/it/developers/extend/apps/data-model) | +| **Campo** | Extend existing objects, define relations | [Data Model](/l/it/developers/extend/apps/data-model) | +| **Logic Function** | Server-side TypeScript with triggers | [Funzioni logiche](/l/it/developers/extend/apps/logic-functions) | +| **Front Component** | Sandboxed React UI in Twenty's page | [Front Components](/l/it/developers/extend/apps/front-components) | +| **Skill** | Reusable AI agent instructions | [Skills & Agents](/l/it/developers/extend/apps/skills-and-agents) | +| **Agent** | AI assistants with custom prompts | [Skills & Agents](/l/it/developers/extend/apps/skills-and-agents) | +| **View** | Pre-configured record list views | [Layout](/l/it/developers/extend/apps/layout) | +| **Navigation Menu Item** | Custom sidebar entries | [Layout](/l/it/developers/extend/apps/layout) | +| **Page Layout** | Custom record page tabs and widgets | [Layout](/l/it/developers/extend/apps/layout) | + +## Sandboxing + +* **Logic functions** run in isolated Node.js processes on the server. They only access data through the typed API client, scoped to the app's role permissions. +* **Front components** run in Web Workers using Remote DOM — sandboxed from the main page but rendering native DOM elements (not iframes). They communicate with Twenty via a message-passing host API. +* **Permissions** are enforced at the API level. The runtime token (`TWENTY_APP_ACCESS_TOKEN`) is derived from the role defined in `defineApplication()`. + +## App lifecycle ``` -┌─────────────────────────────────────────────────────────────┐ -│ install flow │ -│ │ -│ upload package → [pre-install] → metadata migration → │ -│ generate SDK → [post-install] │ -│ │ -│ old schema visible new schema visible │ -└─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────┐ +│ Development │ +│ npx create-twenty-app → yarn twenty dev (live sync) │ +├─────────────────────────────────────────────────────────┤ +│ Build & Deploy │ +│ yarn twenty build → yarn twenty deploy │ +├─────────────────────────────────────────────────────────┤ +│ Install flow │ +│ upload → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +├─────────────────────────────────────────────────────────┤ +│ Publish │ +│ npm publish → appears in Twenty marketplace │ +└─────────────────────────────────────────────────────────┘ ``` -Il pre-install è sempre **sincrono** (blocca l'installazione e può interromperla). Il post-install è **asincrono per impostazione predefinita** — messo in coda su un worker con retry automatici — ma può optare per l'esecuzione sincrona con `shouldRunSynchronously: true`. Vedi l'accordion `definePostInstallLogicFunction` sopra per quando usare ciascuna modalità. - -**Usa `post-install` per tutto ciò che richiede l'esistenza del nuovo schema.** Questo è il caso più comune: - -* Popolamento di dati predefiniti (creazione di record iniziali, viste predefinite, contenuti demo) su oggetti e campi appena aggiunti. -* Registrazione di webhook con servizi di terze parti ora che l'app ha le proprie credenziali. -* Chiamare la tua API per completare il setup che dipende dai metadati sincronizzati. -* Logica idempotente di "ensure this exists" che dovrebbe riconciliare lo stato a ogni aggiornamento — da combinare con `shouldRunOnVersionUpgrade: true`. - -Esempio — eseguire il seeding di un record `PostCard` predefinito dopo l'installazione: - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion }: InstallPayload): Promise => { - if (previousVersion) return; // fresh installs only - - const client = createClient(); - await client.postCard.create({ - data: { title: 'Welcome to Postcard', content: 'Your first card!' }, - }); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Seeds a welcome post card after install.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - handler, -}); -``` - -**Usa `pre-install` quando una migrazione altrimenti distruggerebbe o corromperebbe i dati esistenti.** Poiché il pre-install viene eseguito contro lo schema *precedente* e un suo fallimento annulla l'aggiornamento, è il posto giusto per qualsiasi operazione rischiosa: - -* **Eseguire il backup dei dati che stanno per essere eliminati o ristrutturati** — ad esempio, stai rimuovendo un campo nella v2 e devi copiarne i valori in un altro campo o esportarli su uno storage prima che venga eseguita la migrazione. -* **Archiviare i record che un nuovo vincolo renderebbe non validi** — ad esempio, un campo sta diventando `NOT NULL` e devi prima eliminare o correggere le righe con valori nulli. -* **Validare la compatibilità e rifiutare l'aggiornamento se i dati attuali non possono essere migrati correttamente** — genera un'eccezione dall'handler e l'installazione si interrompe senza applicare modifiche. Questo è più sicuro che scoprire l'incompatibilità a migrazione in corso. -* **Rinominare o rigenerare le chiavi dei dati** prima di una modifica dello schema che farebbe perdere l'associazione. - -Esempio — archiviare i record prima di una migrazione distruttiva: - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { - // Only the 1.x → 2.x upgrade drops the legacy `notes` field. - if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { - return; - } - - const client = createClient(); - const legacyRecords = await client.postCard.findMany({ - where: { notes: { isNotNull: true } }, - }); - - if (legacyRecords.length === 0) return; - - // Copy legacy `notes` into the new `description` field before the migration - // drops the `notes` column. If this fails, the upgrade is aborted and the - // workspace stays on v1 with all data intact. - await Promise.all( - legacyRecords.map((record) => - client.postCard.update({ - where: { id: record.id }, - data: { description: record.notes }, - }), - ), - ); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Backs up legacy notes into description before the v2 migration.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -**Regola generale:** - -| Vuoi… | Usa | -| ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -| Popolare dati predefiniti, configurare il workspace, registrare risorse esterne | `post-install` | -| Eseguire seeding di lunga durata o chiamate a terze parti che non dovrebbero bloccare la risposta dell'installazione | `post-install` (predefinito — `shouldRunSynchronously: false`, con retry del worker) | -| Eseguire un setup rapido di cui il chiamante farà affidamento immediatamente dopo il ritorno della chiamata di installazione | `post-install` con `shouldRunSynchronously: true` | -| Leggere o eseguire il backup dei dati che la prossima migrazione perderebbe | `pre-install` | -| Rifiutare un aggiornamento che corromperebbe i dati esistenti | `pre-install` (genera un'eccezione dall'handler) | -| Eseguire la riconciliazione a ogni aggiornamento | `post-install` con `shouldRunOnVersionUpgrade: true` | -| Eseguire un setup una tantum solo alla prima installazione | `post-install` con `shouldRunOnVersionUpgrade: false` (predefinito) | - - -In caso di dubbio, usa **post-install**. Ricorri al pre-install solo quando la migrazione stessa è distruttiva e devi intercettare lo stato precedente prima che vada perso. - - - - - -I componenti front-end sono componenti React che vengono renderizzati direttamente all'interno della UI di Twenty. Vengono eseguiti in un **Web Worker** isolato utilizzando Remote DOM — il tuo codice è in sandbox ma viene renderizzato in modo nativo nella pagina, non in un iframe. - -#### Dove possono essere utilizzati i componenti front. - -I componenti front possono essere renderizzati in due posizioni all'interno di Twenty: - -* **Pannello laterale** — I componenti front non headless si aprono nel pannello laterale destro. Questo è il comportamento predefinito quando un componente front viene avviato dal menu comandi. -* **Widget (dashboard e pagine dei record)** — I componenti front possono essere incorporati come widget all'interno dei layout di pagina. Quando si configura una dashboard o il layout di una pagina record, gli utenti possono aggiungere un widget del componente front. - -#### Esempio di base - -Il modo più rapido per vedere in azione un componente front-end è registrarlo come **comando**. Aggiungere un campo `command` con `isPinned: true` lo fa apparire come pulsante di azione rapida nell'angolo in alto a destra della pagina — nessun layout di pagina necessario: - -```tsx src/front-components/hello-world.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; - -const HelloWorld = () => { - return ( -
-

Hello from my app!

-

This component renders inside Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', - name: 'hello-world', - description: 'A simple front component', - component: HelloWorld, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', - shortLabel: 'Hello', - label: 'Hello World', - icon: 'IconBolt', - isPinned: true, - availabilityType: 'GLOBAL', - }, -}); -``` - -Dopo la sincronizzazione con `yarn twenty dev` (o eseguendo una volta sola `yarn twenty dev --once`), l'azione rapida appare nell'angolo in alto a destra della pagina: - -
- Pulsante di azione rapida nell'angolo in alto a destra -
- -Fai clic per renderizzare il componente in linea. - -{/* TODO: add screenshot of the rendered front component */} - -#### Campi di configurazione - -| Campo | Obbligatorio | Descrizione | -| --------------------- | ------------ | ---------------------------------------------------------------------------------------- | -| `universalIdentifier` | Sì | ID univoco stabile per questo componente | -| `component` | Sì | Una funzione di componente React | -| `name` | No | Nome visualizzato | -| `descrizione` | No | Descrizione di ciò che fa il componente | -| `isHeadless` | No | Imposta su `true` se il componente non ha una UI visibile (vedi sotto) | -| `comando` | No | Registra il componente come comando (vedi [opzioni del comando](#command-options) sotto) | - -#### Posizionare un componente front-end su una pagina - -Oltre ai comandi, puoi incorporare un componente front-end direttamente in una pagina di record aggiungendolo come widget in un **layout di pagina**. Vedi la sezione [definePageLayout](#definepagelayout) per i dettagli. - -#### Headless vs non headless - -I componenti front prevedono due modalità di rendering controllate dall'opzione `isHeadless`: - -**Non headless (predefinito)** — Il componente renderizza un'interfaccia utente visibile. Quando viene avviato dal menu comandi, si apre nel pannello laterale. Questo è il comportamento predefinito quando `isHeadless` è `false` o omesso. - -**Headless (`isHeadless: true`)** — Il componente viene montato in modo invisibile in background. Non apre il pannello laterale. I componenti headless sono pensati per azioni che eseguono una logica e poi si smontano — ad esempio, eseguire un'attività asincrona, navigare a una pagina o mostrare una finestra modale di conferma. Si abbinano naturalmente ai componenti Command dell'SDK descritti di seguito. - -```tsx src/front-components/sync-tracker.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; -import { useEffect } from 'react'; - -const SyncTracker = () => { - const recordId = useRecordId(); - - useEffect(() => { - enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); - }, [recordId]); - - return null; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'sync-tracker', - description: 'Tracks record views silently', - isHeadless: true, - component: SyncTracker, -}); -``` - -Poiché il componente restituisce `null`, Twenty evita di renderizzare un contenitore per esso — non appare alcuno spazio vuoto nel layout. Il componente ha comunque accesso a tutti gli hook e all'API di comunicazione con l'host. - -#### Componenti Command dell'SDK - -Il pacchetto `twenty-sdk` fornisce quattro componenti di supporto Command progettati per i componenti front headless. Ogni componente esegue un'azione al montaggio, gestisce gli errori mostrando una notifica snackbar e smonta automaticamente il componente front al termine. - -Importali da `twenty-sdk/command`: - -* **`Command`** — Esegue una callback asincrona tramite la prop `execute`. -* **`CommandLink`** — Naviga verso un percorso dell'app. Props: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — Apre una finestra modale di conferma. Se l'utente conferma, esegue la callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — Apre una specifica pagina del pannello laterale. Props: `page`, `pageTitle`, `pageIcon`. - -Ecco un esempio completo di componente front headless che usa `Command` per eseguire un'azione dal menu comandi: - -```tsx src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -E un esempio che usa `CommandModal` per chiedere conferma prima di eseguire: - -```tsx src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Accesso al contesto di runtime - -All'interno del tuo componente, usa gli hook dell'SDK per accedere all'utente corrente, al record e all'istanza del componente: - -```tsx src/front-components/record-info.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - useUserId, - useRecordId, - useFrontComponentId, -} from 'twenty-sdk/front-component'; - -const RecordInfo = () => { - const userId = useUserId(); - const recordId = useRecordId(); - const componentId = useFrontComponentId(); - - return ( -
-

User: {userId}

-

Record: {recordId ?? 'No record context'}

-

Component: {componentId}

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', - name: 'record-info', - component: RecordInfo, -}); -``` - -Hook disponibili: - -| Hook | Restituisce | Descrizione | -| --------------------------------------------- | ----------------- | --------------------------------------------------------------------- | -| `useUserId()` | `string` o `null` | L'ID dell'utente corrente | -| `useRecordId()` | `string` o `null` | L'ID del record corrente (quando posizionato su una pagina di record) | -| `useFrontComponentId()` | `string` | L'ID di questa istanza di componente | -| `useFrontComponentExecutionContext(selector)` | varia | Accedi all'intero contesto di esecuzione con una funzione selettore | - -#### API di comunicazione con l'host - -I componenti front-end possono attivare navigazione, modali e notifiche utilizzando funzioni da `twenty-sdk`: - -| Funzione | Descrizione | -| ----------------------------------------------- | ------------------------------------- | -| `navigate(to, params?, queryParams?, options?)` | Naviga a una pagina dell'app | -| `openSidePanelPage(params)` | Apri un pannello laterale | -| `closeSidePanel()` | Chiudi il pannello laterale | -| `openCommandConfirmationModal(params)` | Mostra una finestra di conferma | -| `enqueueSnackbar(params)` | Mostra una notifica toast | -| `unmountFrontComponent()` | Smonta il componente | -| `updateProgress(progress)` | Aggiorna un indicatore di avanzamento | - -Ecco un esempio che usa l'API host per mostrare una snackbar e chiudere il pannello laterale dopo il completamento di un'azione: - -```tsx src/front-components/archive-record.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId } from 'twenty-sdk/front-component'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -#### Opzioni del comando - -Aggiungere un campo `command` a `defineFrontComponent` registra il componente nel menu comandi (Cmd+K). Se `isPinned` è `true`, compare anche come pulsante di azione rapida nell'angolo in alto a destra della pagina. - -| Campo | Obbligatorio | Descrizione | -| --------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `universalIdentifier` | Sì | ID univoco stabile per il comando | -| `etichetta` | Sì | Etichetta completa mostrata nel menu comandi (Cmd+K) | -| `shortLabel` | No | Etichetta breve visualizzata sul pulsante di azione rapida fissato | -| `icona` | No | Nome dell'icona visualizzato accanto all'etichetta (ad es. `'IconBolt'`, `'IconSend'`) | -| `isPinned` | No | Quando `true`, mostra il comando come pulsante di azione rapida nell'angolo in alto a destra della pagina | -| `availabilityType` | No | Controlla dove compare il comando: `'GLOBAL'` (sempre disponibile), `'RECORD_SELECTION'` (solo quando sono selezionati dei record) o `'FALLBACK'` (mostrato quando nessun altro comando corrisponde) | -| `availabilityObjectUniversalIdentifier` | No | Limita il comando alle pagine di uno specifico tipo di oggetto (ad es. solo sui record Company) | -| `conditionalAvailabilityExpression` | No | Un'espressione booleana per controllare dinamicamente se il comando è visibile (vedi sotto) | - -#### Espressioni di disponibilità condizionale - -Il campo `conditionalAvailabilityExpression` consente di controllare quando un comando è visibile in base al contesto della pagina corrente. Importa variabili tipizzate e operatori da `twenty-sdk` per costruire espressioni: - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - pageType, - numberOfSelectedRecords, - objectPermissions, - everyEquals, - isDefined, -} from 'twenty-sdk/front-component'; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'bulk-action', - component: BulkAction, - command: { - universalIdentifier: '...', - label: 'Bulk Update', - availabilityType: 'RECORD_SELECTION', - conditionalAvailabilityExpression: everyEquals( - objectPermissions, - 'canUpdateObjectRecords', - true, - ), - }, -}); -``` - -**Variabili di contesto** — rappresentano lo stato corrente della pagina: - -| Variabile | Tipo | Descrizione | -| ------------------------------ | --------- | ------------------------------------------------------------------------ | -| `pageType` | `string` | Tipo di pagina corrente (ad es. `'RecordIndexPage'`, `'RecordShowPage'`) | -| `isInSidePanel` | `boolean` | Indica se il componente è renderizzato in un pannello laterale | -| `numberOfSelectedRecords` | `numero` | Numero di record attualmente selezionati | -| `isSelectAll` | `boolean` | Indica se "seleziona tutto" è attivo | -| `selectedRecords` | `array` | Gli oggetti dei record selezionati | -| `favoriteRecordIds` | `array` | ID dei record aggiunti ai preferiti | -| `objectPermissions` | `oggetto` | Autorizzazioni per il tipo di oggetto corrente | -| `targetObjectReadPermissions` | `oggetto` | Autorizzazioni di lettura per l'oggetto di destinazione | -| `targetObjectWritePermissions` | `oggetto` | Autorizzazioni di scrittura per l'oggetto di destinazione | -| `featureFlags` | `oggetto` | Flag delle funzionalità attivi | -| `objectMetadataItem` | `oggetto` | Metadati del tipo di oggetto corrente | -| `hasAnySoftDeleteFilterOnView` | `boolean` | Indica se la vista corrente ha un filtro di soft-delete | - -**Operatori** — combinano variabili in espressioni booleane: - -| Operatore | Descrizione | -| ----------------------------------- | -------------------------------------------------------------------- | -| `isDefined(value)` | `true` se il valore non è null/undefined | -| `isNonEmptyString(value)` | `true` se il valore è una stringa non vuota | -| `includes(array, value)` | `true` se l'array contiene il valore | -| `includesEvery(array, prop, value)` | `true` se la proprietà di ogni elemento include il valore | -| `every(array, prop)` | `true` se la proprietà è truthy su ogni elemento | -| `everyDefined(array, prop)` | `true` se la proprietà è definita su ogni elemento | -| `everyEquals(array, prop, value)` | `true` se la proprietà è uguale al valore su ogni elemento | -| `some(array, prop)` | `true` se la proprietà è truthy su almeno un elemento | -| `someDefined(array, prop)` | `true` se la proprietà è definita su almeno un elemento | -| `someEquals(array, prop, value)` | `true` se la proprietà è uguale al valore su almeno un elemento | -| `someNonEmptyString(array, prop)` | `true` se la proprietà è una stringa non vuota su almeno un elemento | -| `none(array, prop)` | `true` se la proprietà è falsy su ogni elemento | -| `noneDefined(array, prop)` | `true` se la proprietà è undefined su ogni elemento | -| `noneEquals(array, prop, value)` | `true` se la proprietà non è uguale al valore su alcun elemento | - -#### Asset pubblici - -I componenti front-end possono accedere ai file dalla directory `public/` dell'app utilizzando `getPublicAssetUrl`: - -```tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -const Logo = () => Logo; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'logo', - component: Logo, -}); -``` - -Vedi la [sezione sugli asset pubblici](#accessing-public-assets-with-getpublicasseturl) per i dettagli. - -#### Stile - -I componenti front-end supportano diversi approcci di styling. Puoi usare: - -* **Stili inline** — `style={{ color: 'red' }}` -* **Componenti Twenty UI** — importali da `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar e altro) -* **Emotion** — CSS-in-JS con `@emotion/react` -* **Styled-components** — pattern `styled.div` -* **Tailwind CSS** — classi di utilità -* **Qualsiasi libreria CSS-in-JS** compatibile con React - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Button, Tag, Status } from 'twenty-sdk/ui'; - -const StyledWidget = () => { - return ( -
-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', - name: 'styled-widget', - component: StyledWidget, -}); -``` - -
- - - -Le skill definiscono istruzioni e capacità riutilizzabili che gli agenti IA possono utilizzare all'interno del tuo spazio di lavoro. Usa `defineSkill()` per definire skill con convalida integrata: - -```ts src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk/define'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -Punti chiave: -* `name` è una stringa identificativa univoca per la skill (kebab-case consigliato). -* `label` è il nome di visualizzazione leggibile mostrato nell'UI. -* `content` contiene le istruzioni della skill — questo è il testo che l'agente IA utilizza. -* `icon` (opzionale) imposta l'icona visualizzata nell'UI. -* `description` (opzionale) fornisce contesto aggiuntivo sullo scopo della skill. - - - - -Gli agenti sono assistenti IA che vivono all'interno del tuo spazio di lavoro. Usa `defineAgent()` per creare agenti con un prompt di sistema personalizzato: - -```ts src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk/define'; - -export default defineAgent({ - universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'Helps the sales team draft outreach emails and research prospects', - icon: 'IconRobot', - prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', -}); -``` - -Punti chiave: -* `name` è la stringa identificativa univoca dell'agente (kebab-case consigliato). -* `label` è il nome visualizzato nell'UI. -* `prompt` è il prompt di sistema che definisce il comportamento dell'agente. -* `description` (opzionale) fornisce contesto su ciò che fa l'agente. -* `icon` (opzionale) imposta l'icona visualizzata nell'UI. -* `modelId` (opzionale) sostituisce il modello di IA predefinito utilizzato dall'agente. - - - - -Le viste sono configurazioni salvate di come vengono visualizzati i record di un oggetto — inclusi quali campi sono visibili, il loro ordine e gli eventuali filtri o raggruppamenti applicati. Usa `defineView()` per fornire viste preconfigurate con la tua app: - -```ts src/views/example-view.ts -import { defineView, ViewKey } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; - -export default defineView({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'All example items', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - icon: 'IconList', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', - fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 200, - }, - ], -}); -``` - -Punti chiave: -* `objectUniversalIdentifier` specifica a quale oggetto si applica questa vista. -* `key` determina il tipo di vista (ad es., `ViewKey.INDEX` per la vista elenco principale). -* `fields` controlla quali colonne compaiono e il loro ordine. Ogni campo fa riferimento a un `fieldMetadataUniversalIdentifier`. -* Puoi anche definire `filters`, `filterGroups`, `groups` e `fieldGroups` per configurazioni più avanzate. -* `position` controlla l'ordinamento quando esistono più viste per lo stesso oggetto. - - - - -Le voci del menu di navigazione aggiungono elementi personalizzati alla barra laterale dello spazio di lavoro. Usa `defineNavigationMenuItem()` per collegarti a viste, URL esterni o oggetti: - -```ts src/navigation-menu-items/example-navigation-menu-item.ts -import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; -import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; - -export default defineNavigationMenuItem({ - universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', - name: 'example-navigation-menu-item', - icon: 'IconList', - color: 'blue', - position: 0, - type: NavigationMenuItemType.VIEW, - viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, -}); -``` - -Punti chiave: -* `type` determina a cosa rimanda la voce di menu: `NavigationMenuItemType.VIEW` per una vista salvata o `NavigationMenuItemType.LINK` per un URL esterno. -* Per i link a viste, imposta `viewUniversalIdentifier`. Per i link esterni, imposta `link`. -* `position` controlla l'ordinamento nella barra laterale. -* `icon` e `color` (opzionali) personalizzano l'aspetto. - - - - -I layout di pagina ti consentono di personalizzare l'aspetto di una pagina dei dettagli di un record — quali schede compaiono, quali widget sono all'interno di ciascuna scheda e come sono disposti. Usa `definePageLayout()` per fornire layout personalizzati con la tua app: - -```ts src/page-layouts/example-record-page-layout.ts -import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; - -export default definePageLayout({ - universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', - name: 'Example Record Page', - type: 'RECORD_PAGE', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - tabs: [ - { - universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', - title: 'Hello World', - position: 50, - icon: 'IconWorld', - layoutMode: PageLayoutTabLayoutMode.CANVAS, - widgets: [ - { - universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', - title: 'Hello World', - type: 'FRONT_COMPONENT', - configuration: { - configurationType: 'FRONT_COMPONENT', - frontComponentUniversalIdentifier: - HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, - }, - }, - ], - }, - ], -}); -``` - -Punti chiave: -* `type` è in genere `'RECORD_PAGE'` per personalizzare la vista dei dettagli di un oggetto specifico. -* `objectUniversalIdentifier` specifica a quale oggetto si applica questo layout. -* Ogni `tab` definisce una sezione della pagina con un `title`, `position` e `layoutMode` (`CANVAS` per il layout libero). -* Ogni `widget` all'interno di una scheda può renderizzare un componente front-end, un elenco di relazioni o altri tipi di widget integrati. -* `position` sulle schede controlla il loro ordine. Usa valori più alti (ad es., 50) per posizionare le schede personalizzate dopo quelle integrate. - - -
- -## Asset pubblici (cartella `public/`) - -La cartella `public/` alla radice della tua app contiene file statici — immagini, icone, font o qualsiasi altro asset di cui la tua app ha bisogno a runtime. Questi file sono inclusi automaticamente nelle build, sincronizzati durante la modalità di sviluppo e caricati sul server. - -I file posizionati in `public/` sono: - -* **Pubblicamente accessibili** — una volta sincronizzati sul server, gli asset sono serviti a un URL pubblico. Non è necessaria alcuna autenticazione per accedervi. -* **Disponibili nei componenti front-end** — usa gli URL degli asset per visualizzare immagini, icone o qualsiasi media all'interno dei tuoi componenti React. -* **Disponibili nelle funzioni logiche** — fai riferimento agli URL degli asset nelle email, nelle risposte API o in qualsiasi logica lato server. -* **Usati per i metadati del marketplace** — i campi `logoUrl` e `screenshots` in `defineApplication()` fanno riferimento a file di questa cartella (ad es., `public/logo.png`). Questi vengono visualizzati nel marketplace quando la tua app viene pubblicata. -* **Sincronizzati automaticamente in modalità dev** — quando aggiungi, aggiorni o elimini un file in `public/`, viene sincronizzato automaticamente con il server. Nessun riavvio necessario. -* **Inclusi nelle build** — `yarn twenty build` raggruppa tutti gli asset pubblici nell'output di distribuzione. - -### Accedere agli asset pubblici con `getPublicAssetUrl` - -Usa l'helper `getPublicAssetUrl` da `twenty-sdk` per ottenere l'URL completo di un file nella tua directory `public/`. Funziona sia nelle funzioni logiche che nei componenti front-end. - -**In una funzione logica:** - -```ts src/logic-functions/send-invoice.ts -import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; - -const handler = async (): Promise => { - const logoUrl = getPublicAssetUrl('logo.png'); - const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); - - // Fetch the file content (no auth required — public endpoint) - const response = await fetch(invoiceUrl); - const buffer = await response.arrayBuffer(); - - return { logoUrl, size: buffer.byteLength }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'a1b2c3d4-...', - name: 'send-invoice', - description: 'Sends an invoice with the app logo', - timeoutSeconds: 10, - handler, -}); -``` - -**In un componente front-end:** - -```tsx src/front-components/company-card.tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -export default defineFrontComponent(() => { - const logoUrl = getPublicAssetUrl('logo.png'); - - return App logo; -}); -``` - -L'argomento `path` è relativo alla cartella `public/` della tua app. Sia `getPublicAssetUrl('logo.png')` sia `getPublicAssetUrl('public/logo.png')` risolvono allo stesso URL — il prefisso `public/` viene rimosso automaticamente se presente. - -## Uso dei pacchetti npm - -Puoi installare e usare qualsiasi pacchetto npm nella tua app. Sia le funzioni logiche sia i componenti front-end vengono impacchettati con [esbuild](https://esbuild.github.io/), che incorpora tutte le dipendenze nell'output — non sono necessari i `node_modules` a runtime. - -### Installazione di un pacchetto - -```bash filename="Terminal" -yarn add axios -``` - -Quindi importalo nel tuo codice: - -```ts src/logic-functions/fetch-data.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import axios from 'axios'; - -const handler = async (): Promise => { - const { data } = await axios.get('https://api.example.com/data'); - - return { data }; -}; - -export default defineLogicFunction({ - universalIdentifier: '...', - name: 'fetch-data', - description: 'Fetches data from an external API', - timeoutSeconds: 10, - handler, -}); -``` - -Lo stesso vale per i componenti front-end: - -```tsx src/front-components/chart.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { format } from 'date-fns'; - -const DateWidget = () => { - return

Today is {format(new Date(), 'MMMM do, yyyy')}

; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Come funziona il bundling - -La fase di build usa esbuild per produrre un singolo file autonomo per ogni funzione logica e per ogni componente front-end. Tutti i pacchetti importati sono incorporati nel bundle. - -**Le funzioni logiche** vengono eseguite in un ambiente Node.js. I moduli integrati di Node (`fs`, `path`, `crypto`, `http`, ecc.) sono disponibili e non necessitano di essere installati. - -**I componenti front-end** vengono eseguiti in un Web Worker. I moduli integrati di Node non sono disponibili — solo le API del browser e i pacchetti npm che funzionano in un ambiente browser. - -Entrambi gli ambienti hanno `twenty-client-sdk/core` e `twenty-client-sdk/metadata` disponibili come moduli preforniti — questi non vengono inclusi nel bundle ma vengono risolti a runtime dal server. - -## Creazione di entità con lo scaffolding tramite `yarn twenty add` - -Invece di creare manualmente i file delle entità, puoi usare lo scaffolder interattivo: - -```bash filename="Terminal" -yarn twenty add -``` - -Questo ti chiede di scegliere un tipo di entità e ti guida attraverso i campi richiesti. Genera un file pronto all'uso con un `universalIdentifier` stabile e la corretta chiamata a `defineEntity()`. - -Puoi anche passare direttamente il tipo di entità per saltare il primo prompt: - -```bash filename="Terminal" -yarn twenty add object -yarn twenty add logicFunction -yarn twenty add frontComponent -``` - -### Tipi di entità disponibili - -| Tipo di entità | Comando | File generato | -| ---------------------------- | ------------------------------------ | ------------------------------------------------------- | -| Oggetto | `yarn twenty add object` | `src/objects/\.ts` | -| Campo | `yarn twenty add field` | `src/fields/\.ts` | -| Funzione logica | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | -| Componente front-end | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | -| Ruolo | `yarn twenty add role` | `src/roles/\.ts` | -| Abilità | `yarn twenty add skill` | `src/skills/\.ts` | -| Agente | `yarn twenty add agent` | `src/agents/\.ts` | -| Vista | `yarn twenty add view` | `src/views/\.ts` | -| Voce del menu di navigazione | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | -| Layout di pagina | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | - -### Cosa genera lo scaffolder - -Ogni tipo di entità ha il proprio template. Ad esempio, `yarn twenty add object` richiede: - -1. **Nome (singolare)** — ad es., `invoice` -2. **Nome (plurale)** — ad es., `invoices` -3. **Etichetta (singolare)** — compilata automaticamente dal nome (ad es., `Invoice`) -4. **Etichetta (plurale)** — compilata automaticamente (ad es., `Invoices`) -5. **Creare una vista e una voce di navigazione?** — se rispondi sì, lo scaffolder genera anche una vista corrispondente e un link nella barra laterale per il nuovo oggetto. - -Gli altri tipi di entità hanno prompt più semplici — la maggior parte chiede solo un nome. - -Il tipo di entità `field` è più dettagliato: chiede il nome del campo, l'etichetta, il tipo (da un elenco di tutti i tipi di campo disponibili come `TEXT`, `NUMBER`, `SELECT`, `RELATION`, ecc.) e l'`universalIdentifier` dell'oggetto di destinazione. - -### Percorso di output personalizzato - -Usa il flag `--path` per posizionare il file generato in una posizione personalizzata: - -```bash filename="Terminal" -yarn twenty add logicFunction --path src/custom-folder -``` - -## Client API tipizzati (twenty-client-sdk) - -Il pacchetto `twenty-client-sdk` fornisce due client GraphQL tipizzati per interagire con l'API di Twenty dalle tue funzioni logiche e dai componenti front-end. - -| Client | Importa | Endpoint | Generato? | -| ------------------- | ---------------------------- | ------------------------------------------------------------------------ | -------------------------- | -| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — dati dello spazio di lavoro (record, oggetti) | Sì, in fase di dev/build | -| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — configurazione dello spazio di lavoro, caricamenti di file | No, fornito pronto all'uso | - - - - -`CoreApiClient` è il client principale per interrogare e modificare i dati dello spazio di lavoro. Viene **generato dallo schema del tuo spazio di lavoro** durante `yarn twenty dev` o `yarn twenty build`, quindi è completamente tipizzato per corrispondere ai tuoi oggetti e campi. - -```ts -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const client = new CoreApiClient(); - -// Query records -const { companies } = await client.query({ - companies: { - edges: { - node: { - id: true, - name: true, - domainName: { - primaryLinkLabel: true, - primaryLinkUrl: true, - }, - }, - }, - }, -}); - -// Create a record -const { createCompany } = await client.mutation({ - createCompany: { - __args: { - data: { - name: 'Acme Corp', - }, - }, - id: true, - name: true, - }, -}); -``` - -Il client utilizza una sintassi a selection-set: passa `true` per includere un campo, usa `__args` per gli argomenti e annida oggetti per le relazioni. Ottieni completamento automatico e controllo dei tipi completi basati sullo schema del tuo spazio di lavoro. - - -**CoreApiClient viene generato in fase di dev/build.** Se lo usi senza eseguire prima `yarn twenty dev` o `yarn twenty build`, genera un errore. La generazione avviene automaticamente — la CLI esegue l'introspezione dello schema GraphQL del tuo spazio di lavoro e genera un client tipizzato usando `@genql/cli`. - - -#### Utilizzo di CoreSchema per le annotazioni di tipo - -`CoreSchema` fornisce tipi TypeScript corrispondenti agli oggetti del tuo spazio di lavoro — utile per tipizzare lo stato dei componenti o i parametri delle funzioni: - -```ts -import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; -import { useState } from 'react'; - -const [company, setCompany] = useState< - Pick | undefined ->(undefined); - -const client = new CoreApiClient(); -const result = await client.query({ - company: { - __args: { filter: { position: { eq: 1 } } }, - id: true, - name: true, - }, -}); -setCompany(result.company); -``` - - - - -`MetadataApiClient` è fornito pronto all'uso con l'SDK (nessuna generazione richiesta). Interroga l'endpoint `/metadata` per la configurazione dello spazio di lavoro, le applicazioni e i caricamenti di file. - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const metadataClient = new MetadataApiClient(); - -// List first 10 objects in the workspace -const { objects } = await metadataClient.query({ - objects: { - edges: { - node: { - id: true, - nameSingular: true, - namePlural: true, - labelSingular: true, - isCustom: true, - }, - }, - __args: { - filter: {}, - paging: { first: 10 }, - }, - }, -}); -``` - -#### Caricamento dei file - -`MetadataApiClient` include un metodo `uploadFile` per allegare file ai campi di tipo file: - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -| Parametro | Tipo | Descrizione | -| ---------------------------------- | -------- | ---------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | Il contenuto grezzo del file | -| `filename` | `string` | Il nome del file (utilizzato per l'archiviazione e la visualizzazione) | -| `contentType` | `string` | Tipo MIME (predefinito su `application/octet-stream` se omesso) | -| `fieldMetadataUniversalIdentifier` | `string` | L'`universalIdentifier` del campo di tipo file nel tuo oggetto | - -Punti chiave: -* Usa l'`universalIdentifier` del campo (non il suo ID specifico dello spazio di lavoro), quindi il tuo codice di upload funziona in qualsiasi spazio di lavoro in cui la tua app è installata. -* L'`url` restituito è un URL firmato che puoi usare per accedere al file caricato. - - - - - - Quando il tuo codice viene eseguito su Twenty (funzioni logiche o componenti front-end), la piattaforma inietta le credenziali come variabili d'ambiente: - - * `TWENTY_API_URL` — URL di base dell'API di Twenty - * `TWENTY_APP_ACCESS_TOKEN` — Chiave a breve durata con ambito al ruolo funzione predefinito della tua applicazione - - Non è **necessario** passarle ai client — vengono lette automaticamente da `process.env`. I permessi della chiave API sono determinati dal ruolo referenziato in `defaultRoleUniversalIdentifier` nel tuo `application-config.ts`. - - -## Testare la tua app - -L'SDK fornisce API programmatiche che ti consentono di compilare, distribuire, installare e disinstallare la tua app dal codice di test. In combinazione con [Vitest](https://vitest.dev/) e i client API tipizzati, puoi scrivere test di integrazione che verificano che la tua app funzioni end-to-end contro un server Twenty reale. - -### Impostazione - -L'app generata tramite scaffolding include già Vitest. Se la configuri manualmente, installa le dipendenze: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Crea un `vitest.config.ts` alla radice della tua app: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Crea un file di setup che verifichi che il server sia raggiungibile prima dell'esecuzione dei test: - -```ts src/__tests__/setup-test.ts -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { beforeAll } from 'vitest'; - -const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; -const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### API programmatiche dell'SDK - -Il sottopercorso `twenty-sdk/cli` esporta funzioni che puoi chiamare direttamente dal codice di test: - -| Funzione | Descrizione | -| -------------- | ----------------------------------------------- | -| `appBuild` | Compila l'app e, opzionalmente, crea un tarball | -| `appDeploy` | Carica un tarball sul server | -| `appInstall` | Installa l'app nello spazio di lavoro attivo | -| `appUninstall` | Disinstalla l'app dallo spazio di lavoro attivo | - -Ogni funzione restituisce un oggetto risultato con `success: boolean` e `data` oppure `error`. - -### Scrivere un test di integrazione - -Ecco un esempio completo che compila, distribuisce e installa l'app, quindi verifica che compaia nello spazio di lavoro: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Esecuzione dei test - -Assicurati che il tuo server Twenty locale sia in esecuzione, quindi: - -```bash filename="Terminal" -yarn test -``` - -Oppure in modalità watch durante lo sviluppo: - -```bash filename="Terminal" -yarn test:watch -``` - -### Controllo dei tipi - -Puoi anche eseguire il controllo dei tipi sulla tua app senza eseguire i test: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Questo esegue `tsc --noEmit` e riporta eventuali errori di tipo. - -## Riferimento CLI - -Oltre a `dev`, `build`, `add` e `typecheck`, la CLI fornisce comandi per eseguire funzioni, visualizzare i log e gestire le installazioni delle app. - -### Esecuzione delle funzioni (`yarn twenty exec`) - -Esegui manualmente una funzione logica senza attivarla tramite HTTP, cron o evento del database: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Visualizzazione dei log delle funzioni (`yarn twenty logs`) - -Esegui lo streaming dei log di esecuzione per le funzioni logiche della tua app: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - - -Questo è diverso da `yarn twenty server logs`, che mostra i log del container Docker. `yarn twenty logs` mostra i log di esecuzione delle funzioni della tua app dal server Twenty. - - -### Disinstallazione di un'app (`yarn twenty uninstall`) - -Rimuovi la tua app dallo spazio di lavoro attivo: - -```bash filename="Terminal" -yarn twenty uninstall - -# Skip the confirmation prompt -yarn twenty uninstall --yes -``` - -## Gestione dei remoti - -Un **remoto** è un server Twenty a cui la tua app si connette. Durante la configurazione, lo strumento di scaffolding ne crea uno automaticamente per te. Puoi aggiungere altri remoti o passare da uno all'altro in qualsiasi momento. - -```bash filename="Terminal" -# Add a new remote (opens a browser for OAuth login) -yarn twenty remote add - -# Connect to a local Twenty server (auto-detects port 2020 or 3000) -yarn twenty remote add --local - -# Add a remote non-interactively (useful for CI) -yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote - -# List all configured remotes -yarn twenty remote list - -# Switch the active remote -yarn twenty remote switch -``` - -Le tue credenziali sono archiviate in `~/.twenty/config.json`. - -## CI con GitHub Actions - -Lo strumento di scaffolding genera un workflow GitHub Actions pronto all'uso in `.github/workflows/ci.yml`. Esegue automaticamente i test di integrazione a ogni push su `main` e sulle pull request. - -Il workflow: - -1. Esegue il checkout del tuo codice -2. Avvia un server Twenty temporaneo utilizzando l'azione `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` -3. Installa le dipendenze con `yarn install --immutable` -4. Esegue `yarn test` con `TWENTY_API_URL` e `TWENTY_API_KEY` iniettati dagli output dell'azione - -```yaml .github/workflows/ci.yml -name: CI - -on: - push: - branches: - - main - pull_request: {} - -env: - TWENTY_VERSION: latest - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Spawn Twenty instance - id: twenty - uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main - with: - twenty-version: ${{ env.TWENTY_VERSION }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Enable Corepack - run: corepack enable - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - - name: Install dependencies - run: yarn install --immutable - - - name: Run integration tests - run: yarn test - env: - TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} - TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} -``` - -Non è necessario configurare alcun secret — l'azione `spawn-twenty-docker-image` avvia un server Twenty effimero direttamente nel runner e fornisce i dettagli di connessione. Il secret `GITHUB_TOKEN` è fornito automaticamente da GitHub. - -Per fissare una versione specifica di Twenty invece di `latest`, modifica la variabile d'ambiente `TWENTY_VERSION` all'inizio del workflow. +* **`yarn twenty dev`** — watches your source files and live-syncs changes to a connected Twenty server. The typed API client is regenerated automatically when the schema changes. +* **`yarn twenty build`** — compiles TypeScript, bundles logic functions and front components with esbuild, and produces a manifest. +* **Pre/post-install hooks** — optional logic functions that run during installation. See [Logic Functions](/l/it/developers/extend/apps/logic-functions) for details. + +## Prossimi passaggi + + + + Define objects, fields, roles, and relations. + + + Server-side functions with HTTP, cron, and event triggers. + + + Sandboxed React components inside Twenty's UI. + + + Views, navigation items, and record page layouts. + + + AI skills and agents with custom prompts. + + + CLI commands, testing, assets, remotes, and CI. + + + Deploy to a server or publish to the marketplace. + + diff --git a/packages/twenty-docs/l/it/developers/extend/apps/cli-and-testing.mdx b/packages/twenty-docs/l/it/developers/extend/apps/cli-and-testing.mdx new file mode 100644 index 00000000000..c3e5df373b8 --- /dev/null +++ b/packages/twenty-docs/l/it/developers/extend/apps/cli-and-testing.mdx @@ -0,0 +1,434 @@ +--- +title: CLI & Testing +description: CLI commands, testing setup, public assets, npm packages, remotes, and CI configuration. +icon: terminal +--- + +## Asset pubblici (cartella `public/`) + +La cartella `public/` alla radice della tua app contiene file statici — immagini, icone, font o qualsiasi altro asset di cui la tua app ha bisogno a runtime. Questi file sono inclusi automaticamente nelle build, sincronizzati durante la modalità di sviluppo e caricati sul server. + +I file posizionati in `public/` sono: + +* **Pubblicamente accessibili** — una volta sincronizzati sul server, gli asset sono serviti a un URL pubblico. Non è necessaria alcuna autenticazione per accedervi. +* **Disponibili nei componenti front-end** — usa gli URL degli asset per visualizzare immagini, icone o qualsiasi media all'interno dei tuoi componenti React. +* **Disponibili nelle funzioni logiche** — fai riferimento agli URL degli asset nelle email, nelle risposte API o in qualsiasi logica lato server. +* **Usati per i metadati del marketplace** — i campi `logoUrl` e `screenshots` in `defineApplication()` fanno riferimento a file di questa cartella (ad es., `public/logo.png`). Questi vengono visualizzati nel marketplace quando la tua app viene pubblicata. +* **Sincronizzati automaticamente in modalità dev** — quando aggiungi, aggiorni o elimini un file in `public/`, viene sincronizzato automaticamente con il server. Nessun riavvio necessario. +* **Inclusi nelle build** — `yarn twenty build` raggruppa tutti gli asset pubblici nell'output di distribuzione. + +### Accedere agli asset pubblici con `getPublicAssetUrl` + +Usa l'helper `getPublicAssetUrl` da `twenty-sdk` per ottenere l'URL completo di un file nella tua directory `public/`. Funziona sia nelle funzioni logiche che nei componenti front-end. + +**In una funzione logica:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; + +const handler = async (): Promise => { + const logoUrl = getPublicAssetUrl('logo.png'); + const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); + + // Fetch the file content (no auth required — public endpoint) + const response = await fetch(invoiceUrl); + const buffer = await response.arrayBuffer(); + + return { logoUrl, size: buffer.byteLength }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'a1b2c3d4-...', + name: 'send-invoice', + description: 'Sends an invoice with the app logo', + timeoutSeconds: 10, + handler, +}); +``` + +**In un componente front-end:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +L'argomento `path` è relativo alla cartella `public/` della tua app. Sia `getPublicAssetUrl('logo.png')` sia `getPublicAssetUrl('public/logo.png')` risolvono allo stesso URL — il prefisso `public/` viene rimosso automaticamente se presente. + +## Uso dei pacchetti npm + +Puoi installare e usare qualsiasi pacchetto npm nella tua app. Sia le funzioni logiche sia i componenti front-end vengono impacchettati con [esbuild](https://esbuild.github.io/), che incorpora tutte le dipendenze nell'output — non sono necessari i `node_modules` a runtime. + +### Installazione di un pacchetto + +```bash filename="Terminal" +yarn add axios +``` + +Quindi importalo nel tuo codice: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +Lo stesso vale per i componenti front-end: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### Come funziona il bundling + +La fase di build usa esbuild per produrre un singolo file autonomo per ogni funzione logica e per ogni componente front-end. Tutti i pacchetti importati sono incorporati nel bundle. + +**Le funzioni logiche** vengono eseguite in un ambiente Node.js. I moduli integrati di Node (`fs`, `path`, `crypto`, `http`, ecc.) sono disponibili e non necessitano di essere installati. + +**I componenti front-end** vengono eseguiti in un Web Worker. I moduli integrati di Node non sono disponibili — solo le API del browser e i pacchetti npm che funzionano in un ambiente browser. + +Entrambi gli ambienti hanno `twenty-client-sdk/core` e `twenty-client-sdk/metadata` disponibili come moduli preforniti — questi non vengono inclusi nel bundle ma vengono risolti a runtime dal server. + +## Testare la tua app + +L'SDK fornisce API programmatiche che ti consentono di compilare, distribuire, installare e disinstallare la tua app dal codice di test. In combinazione con [Vitest](https://vitest.dev/) e i client API tipizzati, puoi scrivere test di integrazione che verificano che la tua app funzioni end-to-end contro un server Twenty reale. + +### Impostazione + +L'app generata tramite scaffolding include già Vitest. Se la configuri manualmente, installa le dipendenze: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Crea un `vitest.config.ts` alla radice della tua app: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Crea un file di setup che verifichi che il server sia raggiungibile prima dell'esecuzione dei test: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### API programmatiche dell'SDK + +Il sottopercorso `twenty-sdk/cli` esporta funzioni che puoi chiamare direttamente dal codice di test: + +| Funzione | Descrizione | +| -------------- | ----------------------------------------------- | +| `appBuild` | Compila l'app e, opzionalmente, crea un tarball | +| `appDeploy` | Carica un tarball sul server | +| `appInstall` | Installa l'app nello spazio di lavoro attivo | +| `appUninstall` | Disinstalla l'app dallo spazio di lavoro attivo | + +Ogni funzione restituisce un oggetto risultato con `success: boolean` e `data` oppure `error`. + +### Scrivere un test di integrazione + +Ecco un esempio completo che compila, distribuisce e installa l'app, quindi verifica che compaia nello spazio di lavoro: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Esecuzione dei test + +Assicurati che il tuo server Twenty locale sia in esecuzione, quindi: + +```bash filename="Terminal" +yarn test +``` + +Oppure in modalità watch durante lo sviluppo: + +```bash filename="Terminal" +yarn test:watch +``` + +### Controllo dei tipi + +Puoi anche eseguire il controllo dei tipi sulla tua app senza eseguire i test: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +Questo esegue `tsc --noEmit` e riporta eventuali errori di tipo. + +## Riferimento CLI + +Oltre a `dev`, `build`, `add` e `typecheck`, la CLI fornisce comandi per eseguire funzioni, visualizzare i log e gestire le installazioni delle app. + +### Esecuzione delle funzioni (`yarn twenty exec`) + +Esegui manualmente una funzione logica senza attivarla tramite HTTP, cron o evento del database: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute the post-install function +yarn twenty exec --postInstall +``` + +### Visualizzazione dei log delle funzioni (`yarn twenty logs`) + +Esegui lo streaming dei log di esecuzione per le funzioni logiche della tua app: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +Questo è diverso da `yarn twenty server logs`, che mostra i log del container Docker. `yarn twenty logs` mostra i log di esecuzione delle funzioni della tua app dal server Twenty. + + +### Disinstallazione di un'app (`yarn twenty uninstall`) + +Rimuovi la tua app dallo spazio di lavoro attivo: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` + +## Gestione dei remoti + +Un **remoto** è un server Twenty a cui la tua app si connette. Durante la configurazione, lo strumento di scaffolding ne crea uno automaticamente per te. Puoi aggiungere altri remoti o passare da uno all'altro in qualsiasi momento. + +```bash filename="Terminal" +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add + +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local + +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote + +# List all configured remotes +yarn twenty remote list + +# Switch the active remote +yarn twenty remote switch +``` + +Le tue credenziali sono archiviate in `~/.twenty/config.json`. + +## CI con GitHub Actions + +Lo strumento di scaffolding genera un workflow GitHub Actions pronto all'uso in `.github/workflows/ci.yml`. Esegue automaticamente i test di integrazione a ogni push su `main` e sulle pull request. + +Il workflow: + +1. Esegue il checkout del tuo codice +2. Avvia un server Twenty temporaneo utilizzando l'azione `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` +3. Installa le dipendenze con `yarn install --immutable` +4. Esegue `yarn test` con `TWENTY_API_URL` e `TWENTY_API_KEY` iniettati dagli output dell'azione + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +Non è necessario configurare alcun secret — l'azione `spawn-twenty-docker-image` avvia un server Twenty effimero direttamente nel runner e fornisce i dettagli di connessione. Il secret `GITHUB_TOKEN` è fornito automaticamente da GitHub. + +Per fissare una versione specifica di Twenty invece di `latest`, modifica la variabile d'ambiente `TWENTY_VERSION` all'inizio del workflow. diff --git a/packages/twenty-docs/l/it/developers/extend/apps/data-model.mdx b/packages/twenty-docs/l/it/developers/extend/apps/data-model.mdx new file mode 100644 index 00000000000..bec78dc3384 --- /dev/null +++ b/packages/twenty-docs/l/it/developers/extend/apps/data-model.mdx @@ -0,0 +1,494 @@ +--- +title: Modello dati +description: Define objects, fields, roles, and application metadata with the Twenty SDK. +icon: database +--- + +The `twenty-sdk` package provides `defineEntity` functions to declare your app's data model. Devi usare `export default defineEntity({...})` affinché l'SDK rilevi le tue entità. Queste funzioni convalidano la configurazione in fase di build e offrono il completamento automatico nell'IDE e la sicurezza dei tipi. + + + **L'organizzazione dei file dipende da te.** + Il rilevamento delle entità è basato sull'AST — l'SDK trova le chiamate a `export default defineEntity(...)` indipendentemente da dove si trova il file. Raggruppare i file per tipo (ad es., `logic-functions/`, `roles/`) è solo una convenzione, non un requisito. + + + + + +I ruoli incapsulano i permessi sugli oggetti e sulle azioni del tuo spazio di lavoro. + +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export default defineRole({ + universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', + label: 'My new role', + description: 'A role that can be used in your workspace', + canReadAllObjectRecords: false, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + canReadObjectRecords: true, + canUpdateObjectRecords: true, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + }, + ], + fieldPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + fieldUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, + canReadFieldValue: false, + canUpdateFieldValue: false, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Ogni app deve avere esattamente una chiamata a `defineApplication` che descrive: + +* **Identità**: identificatori, nome visualizzato e descrizione. +* **Autorizzazioni**: quale ruolo usano le sue funzioni e i componenti front-end. +* **Variabili (opzionali)**: coppie chiave–valore esposte alle funzioni come variabili d'ambiente. +* **(Opzionali) Funzioni di pre-installazione/post-installazione**: funzioni logiche che vengono eseguite prima o dopo l'installazione. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk/define'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +Note: +* I campi `universalIdentifier` sono ID deterministici che possiedi. Generali una volta e mantienili stabili tra una sincronizzazione e l'altra. +* `applicationVariables` diventano variabili d'ambiente per le tue funzioni e i componenti front-end (ad esempio, `DEFAULT_RECIPIENT_NAME` è disponibile come `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` deve fare riferimento a un ruolo definito con `defineRole()` (vedi sopra). +* Le funzioni di pre-installazione e post-installazione vengono rilevate automaticamente durante il build del manifesto — non è necessario farvi riferimento in `defineApplication()`. + +#### Metadati del marketplace + +Se prevedi di [pubblicare la tua app](/l/it/developers/extend/apps/publishing), questi campi opzionali controllano come appare nel marketplace: + +| Campo | Descrizione | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | +| `author` | Nome dell'autore o dell'azienda | +| `category` | Categoria dell'app per il filtraggio nel marketplace | +| `logoUrl` | Percorso al logo della tua app (ad es., `public/logo.png`) | +| `screenshots` | Array di percorsi degli screenshot (ad es., `public/screenshot-1.png`) | +| `aboutDescription` | Descrizione markdown più lunga per la scheda "Informazioni". Se omesso, il marketplace utilizza il `README.md` del pacchetto da npm | +| `websiteUrl` | Link al tuo sito web | +| `termsUrl` | Link ai Termini di servizio | +| `emailSupport` | Indirizzo email di supporto | +| `issueReportUrl` | Link al sistema di tracciamento dei problemi | + +#### Ruoli e permessi + +Il `defaultRoleUniversalIdentifier` in `application-config.ts` indica il ruolo predefinito utilizzato dalle funzioni logiche e dai componenti front-end della tua app. Vedi `defineRole` sopra per i dettagli. + +* Il token di runtime iniettato come `TWENTY_APP_ACCESS_TOKEN` è derivato da questo ruolo. +* Il client tipizzato è limitato ai permessi concessi a quel ruolo. +* Segui il principio del privilegio minimo: crea un ruolo dedicato con solo i permessi necessari alle tue funzioni. + +##### Ruolo funzione predefinito + +Quando esegui lo scaffolding di una nuova app, la CLI crea un file di ruolo predefinito: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk/define'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + label: 'Default function role', + description: 'Default role for function Twenty client', + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [], + fieldPermissions: [], + permissionFlags: [], +}); +``` + +L'`universalIdentifier` di questo ruolo viene referenziato in `application-config.ts` come `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** definisce ciò che il ruolo può fare. +* **application-config.ts** punta a quel ruolo in modo che le tue funzioni ne ereditino i permessi. + +Note: +* Parti dal ruolo generato dallo scaffolder, quindi restringilo progressivamente seguendo il principio del privilegio minimo. +* Sostituisci `objectPermissions` e `fieldPermissions` con gli oggetti e i campi di cui le tue funzioni hanno realmente bisogno. +* `permissionFlags` controllano l'accesso alle funzionalità a livello di piattaforma. Mantienili al minimo. +* Vedi un esempio funzionante: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + + +Gli oggetti personalizzati descrivono sia lo schema sia il comportamento per i record nel tuo spazio di lavoro. Usa `defineObject()` per definire oggetti con convalida integrata: + +```ts postCard.object.ts +import { defineObject, FieldType } from 'twenty-sdk/define'; + +enum PostCardStatus { + DRAFT = 'DRAFT', + SENT = 'SENT', + DELIVERED = 'DELIVERED', + RETURNED = 'RETURNED', +} + +export default defineObject({ + universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', + nameSingular: 'postCard', + namePlural: 'postCards', + labelSingular: 'Post Card', + labelPlural: 'Post Cards', + description: 'A post card object', + icon: 'IconMail', + fields: [ + { + universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', + name: 'content', + type: FieldType.TEXT, + label: 'Content', + description: "Postcard's content", + icon: 'IconAbc', + }, + { + universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', + name: 'recipientName', + type: FieldType.FULL_NAME, + label: 'Recipient name', + icon: 'IconUser', + }, + { + universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', + name: 'recipientAddress', + type: FieldType.ADDRESS, + label: 'Recipient address', + icon: 'IconHome', + }, + { + universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', + name: 'status', + type: FieldType.SELECT, + label: 'Status', + icon: 'IconSend', + defaultValue: `'${PostCardStatus.DRAFT}'`, + options: [ + { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, + { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, + { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, + { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, + ], + }, + { + universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', + name: 'deliveredAt', + type: FieldType.DATE_TIME, + label: 'Delivered at', + icon: 'IconCheck', + isNullable: true, + defaultValue: null, + }, + ], +}); +``` + +Punti chiave: + +* Usa `defineObject()` per una convalida integrata e un migliore supporto IDE. +* Il `universalIdentifier` deve essere univoco e stabile tra i deployment. +* Ogni campo richiede un `name`, `type`, `label` e il proprio `universalIdentifier` stabile. +* L'array `fields` è facoltativo: puoi definire oggetti senza campi personalizzati. +* Puoi generare nuovi oggetti con `yarn twenty add`, che ti guida nella denominazione, nei campi e nelle relazioni. + + +**I campi base vengono creati automaticamente.** Quando definisci un oggetto personalizzato, Twenty aggiunge automaticamente i campi standard +come `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` e `deletedAt`. +Non è necessario definirli nel tuo array `fields` — aggiungi solo i tuoi campi personalizzati. +Puoi sovrascrivere i campi predefiniti definendo un campo con lo stesso nome nel tuo array `fields`, +ma non è consigliato. + + + + + +Usa `defineField()` per aggiungere campi a oggetti che non possiedi — come gli oggetti standard di Twenty (Person, Company, ecc.) o oggetti di altre app. A differenza dei campi inline in `defineObject()`, i campi autonomi richiedono un `objectUniversalIdentifier` per specificare quale oggetto estendono: + +```ts src/fields/company-loyalty-tier.field.ts +import { defineField, FieldType } from 'twenty-sdk/define'; + +export default defineField({ + universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', + objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object + name: 'loyaltyTier', + type: FieldType.SELECT, + label: 'Loyalty Tier', + icon: 'IconStar', + options: [ + { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, + { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, + { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, + ], +}); +``` + +Punti chiave: +* `objectUniversalIdentifier` identifica l'oggetto di destinazione. Per gli oggetti standard, usa `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` esportati da `twenty-sdk`. +* Quando definisci campi inline in `defineObject()`, **non** hai bisogno di `objectUniversalIdentifier` — viene ereditato dall'oggetto padre. +* `defineField()` è l'unico modo per aggiungere campi a oggetti che non hai creato con `defineObject()`. + + + + +Le relazioni collegano gli oggetti tra loro. In Twenty, le relazioni sono sempre **bidirezionali** — definisci entrambi i lati e ciascun lato fa riferimento all'altro. + +Esistono due tipi di relazione: + +| Tipo di relazione | Descrizione | Ha una chiave esterna? | +| ----------------- | --------------------------------------------------------------------- | ---------------------- | +| `MANY_TO_ONE` | Molti record di questo oggetto puntano a un record della destinazione | Sì (`joinColumnName`) | +| `ONE_TO_MANY` | Un record di questo oggetto ha molti record della destinazione | No (lato inverso) | + +#### Come funzionano le relazioni + +Ogni relazione richiede **due campi** che fanno riferimento l'uno all'altro: + +1. Il lato **MANY_TO_ONE** — risiede sull'oggetto che detiene la chiave esterna +2. Il lato **ONE_TO_MANY** — risiede sull'oggetto che possiede la collezione + +Entrambi i campi usano `FieldType.RELATION` e si riferiscono reciprocamente tramite `relationTargetFieldMetadataUniversalIdentifier`. + +#### Esempio: Post Card ha molti destinatari + +Supponiamo che un `PostCard` possa essere inviato a molti record `PostCardRecipient`. Ogni destinatario appartiene esattamente a una sola cartolina. + +**Passaggio 1: definisci il lato ONE_TO_MANY su PostCard** (il lato "uno"): + +```ts src/fields/post-card-recipients-on-post-card.field.ts +import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; +// Import from the other side +import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; + +export default defineField({ + universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCardRecipients', + label: 'Post Card Recipients', + icon: 'IconUsers', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); +``` + +**Passaggio 2: definisci il lato MANY_TO_ONE su PostCardRecipient** (il lato "molti" — contiene la chiave esterna): + +```ts src/fields/post-card-on-post-card-recipient.field.ts +import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; +// Import from the other side +import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; + +export default defineField({ + universalIdentifier: POST_CARD_FIELD_ID, + objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + icon: 'IconMail', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, +}); +``` + + +**Importazioni circolari:** Entrambi i campi di relazione fanno riferimento all'`universalIdentifier` dell'altro. Per evitare problemi di importazioni circolari, esporta gli ID dei campi come costanti denominate da ciascun file e importale nell'altro file. Il sistema di build le risolve in fase di compilazione. + + +#### Relazioni con gli oggetti standard + +Per creare una relazione con un oggetto Twenty integrato (Person, Company, ecc.), usa `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: + +```ts src/fields/person-on-self-hosting-user.field.ts +import { + defineField, + FieldType, + RelationType, + OnDeleteAction, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; +import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; + +export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; +export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; + +export default defineField({ + universalIdentifier: PERSON_FIELD_ID, + objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'person', + label: 'Person', + description: 'Person matching with the self hosting user', + isNullable: true, + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'personId', + }, +}); +``` + +#### Proprietà dei campi di relazione + +| Proprietà | Obbligatorio | Descrizione | +| ------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------- | +| `tipo` | Sì | Deve essere `FieldType.RELATION` | +| `relationTargetObjectMetadataUniversalIdentifier` | Sì | L'`universalIdentifier` dell'oggetto di destinazione | +| `relationTargetFieldMetadataUniversalIdentifier` | Sì | L'`universalIdentifier` del campo corrispondente sull'oggetto di destinazione | +| `universalSettings.relationType` | Sì | `RelationType.MANY_TO_ONE` o `RelationType.ONE_TO_MANY` | +| `universalSettings.onDelete` | Solo MANY_TO_ONE | Cosa accade quando il record referenziato viene eliminato: `CASCADE`, `SET_NULL`, `RESTRICT` o `NO_ACTION` | +| `universalSettings.joinColumnName` | Solo MANY_TO_ONE | Nome della colonna del database per la chiave esterna (ad es., `postCardId`) | + +#### Campi di relazione inline in defineObject + +Puoi anche definire i campi di relazione direttamente all'interno di `defineObject()`. In tal caso, ometti `objectUniversalIdentifier` — viene ereditato dall'oggetto padre: + +```ts +export default defineObject({ + universalIdentifier: '...', + nameSingular: 'postCardRecipient', + // ... + fields: [ + { + universalIdentifier: POST_CARD_FIELD_ID, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, + }, + // ... other fields + ], +}); +``` + + + +## Creazione di entità con lo scaffolding tramite `yarn twenty add` + +Invece di creare manualmente i file delle entità, puoi usare lo scaffolder interattivo: + +```bash filename="Terminal" +yarn twenty add +``` + +Questo ti chiede di scegliere un tipo di entità e ti guida attraverso i campi richiesti. Genera un file pronto all'uso con un `universalIdentifier` stabile e la corretta chiamata a `defineEntity()`. + +Puoi anche passare direttamente il tipo di entità per saltare il primo prompt: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Tipi di entità disponibili + +| Tipo di entità | Comando | File generato | +| ---------------------------- | ------------------------------------ | ------------------------------------------------------- | +| Oggetto | `yarn twenty add object` | `src/objects/\.ts` | +| Campo | `yarn twenty add field` | `src/fields/\.ts` | +| Funzione logica | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | +| Componente front-end | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | +| Ruolo | `yarn twenty add role` | `src/roles/\.ts` | +| Abilità | `yarn twenty add skill` | `src/skills/\.ts` | +| Agente | `yarn twenty add agent` | `src/agents/\.ts` | +| Vista | `yarn twenty add view` | `src/views/\.ts` | +| Voce del menu di navigazione | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | +| Layout di pagina | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | + +### Cosa genera lo scaffolder + +Ogni tipo di entità ha il proprio template. Ad esempio, `yarn twenty add object` richiede: + +1. **Nome (singolare)** — ad es., `invoice` +2. **Nome (plurale)** — ad es., `invoices` +3. **Etichetta (singolare)** — compilata automaticamente dal nome (ad es., `Invoice`) +4. **Etichetta (plurale)** — compilata automaticamente (ad es., `Invoices`) +5. **Creare una vista e una voce di navigazione?** — se rispondi sì, lo scaffolder genera anche una vista corrispondente e un link nella barra laterale per il nuovo oggetto. + +Gli altri tipi di entità hanno prompt più semplici — la maggior parte chiede solo un nome. + +Il tipo di entità `field` è più dettagliato: chiede il nome del campo, l'etichetta, il tipo (da un elenco di tutti i tipi di campo disponibili come `TEXT`, `NUMBER`, `SELECT`, `RELATION`, ecc.) e l'`universalIdentifier` dell'oggetto di destinazione. + +### Percorso di output personalizzato + +Usa il flag `--path` per posizionare il file generato in una posizione personalizzata: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` diff --git a/packages/twenty-docs/l/it/developers/extend/apps/front-components.mdx b/packages/twenty-docs/l/it/developers/extend/apps/front-components.mdx new file mode 100644 index 00000000000..89b14a3a208 --- /dev/null +++ b/packages/twenty-docs/l/it/developers/extend/apps/front-components.mdx @@ -0,0 +1,419 @@ +--- +title: Componenti front-end +description: Build React components that render inside Twenty's UI with sandboxed isolation. +icon: window-maximize +--- + +I componenti front-end sono componenti React che vengono renderizzati direttamente all'interno della UI di Twenty. Vengono eseguiti in un **Web Worker** isolato utilizzando Remote DOM — il tuo codice è in sandbox ma viene renderizzato in modo nativo nella pagina, non in un iframe. + +## Dove possono essere utilizzati i componenti front. + +I componenti front possono essere renderizzati in due posizioni all'interno di Twenty: + +* **Pannello laterale** — I componenti front non headless si aprono nel pannello laterale destro. Questo è il comportamento predefinito quando un componente front viene avviato dal menu comandi. +* **Widget (dashboard e pagine dei record)** — I componenti front possono essere incorporati come widget all'interno dei layout di pagina. Quando si configura una dashboard o il layout di una pagina record, gli utenti possono aggiungere un widget del componente front. + +## Esempio di base + +Il modo più rapido per vedere in azione un componente front-end è registrarlo come **comando**. Aggiungere un campo `command` con `isPinned: true` lo fa apparire come pulsante di azione rapida nell'angolo in alto a destra della pagina — nessun layout di pagina necessario: + +```tsx src/front-components/hello-world.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; + +const HelloWorld = () => { + return ( +
+

Hello from my app!

+

This component renders inside Twenty.

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, +}); +``` + +Dopo la sincronizzazione con `yarn twenty dev` (o eseguendo una volta sola `yarn twenty dev --once`), l'azione rapida appare nell'angolo in alto a destra della pagina: + +
+ Pulsante di azione rapida nell'angolo in alto a destra +
+ +Fai clic per renderizzare il componente in linea. + +## Campi di configurazione + +| Campo | Obbligatorio | Descrizione | +| --------------------- | ------------ | ---------------------------------------------------------------------------------------- | +| `universalIdentifier` | Sì | ID univoco stabile per questo componente | +| `component` | Sì | Una funzione di componente React | +| `name` | No | Nome visualizzato | +| `descrizione` | No | Descrizione di ciò che fa il componente | +| `isHeadless` | No | Imposta su `true` se il componente non ha una UI visibile (vedi sotto) | +| `comando` | No | Registra il componente come comando (vedi [opzioni del comando](#command-options) sotto) | + +## Posizionare un componente front-end su una pagina + +Oltre ai comandi, puoi incorporare un componente front-end direttamente in una pagina di record aggiungendolo come widget in un **layout di pagina**. Vedi la sezione [definePageLayout](/l/it/developers/extend/apps/skills-and-agents#definepagelayout) per i dettagli. + +## Headless vs non headless + +I componenti front prevedono due modalità di rendering controllate dall'opzione `isHeadless`: + +**Non headless (predefinito)** — Il componente renderizza un'interfaccia utente visibile. Quando viene avviato dal menu comandi, si apre nel pannello laterale. Questo è il comportamento predefinito quando `isHeadless` è `false` o omesso. + +**Headless (`isHeadless: true`)** — Il componente viene montato in modo invisibile in background. Non apre il pannello laterale. I componenti headless sono pensati per azioni che eseguono una logica e poi si smontano — ad esempio, eseguire un'attività asincrona, navigare a una pagina o mostrare una finestra modale di conferma. Si abbinano naturalmente ai componenti Command dell'SDK descritti di seguito. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Poiché il componente restituisce `null`, Twenty evita di renderizzare un contenitore per esso — non appare alcuno spazio vuoto nel layout. Il componente ha comunque accesso a tutti gli hook e all'API di comunicazione con l'host. + +## Componenti Command dell'SDK + +Il pacchetto `twenty-sdk` fornisce quattro componenti di supporto Command progettati per i componenti front headless. Ogni componente esegue un'azione al montaggio, gestisce gli errori mostrando una notifica snackbar e smonta automaticamente il componente front al termine. + +Importali da `twenty-sdk/command`: + +* **`Command`** — Esegue una callback asincrona tramite la prop `execute`. +* **`CommandLink`** — Naviga verso un percorso dell'app. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Apre una finestra modale di conferma. Se l'utente conferma, esegue la callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Apre una specifica pagina del pannello laterale. Props: `page`, `pageTitle`, `pageIcon`. + +Ecco un esempio completo di componente front headless che usa `Command` per eseguire un'azione dal menu comandi: + +```tsx src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +E un esempio che usa `CommandModal` per chiedere conferma prima di eseguire: + +```tsx src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +## Accesso al contesto di runtime + +All'interno del tuo componente, usa gli hook dell'SDK per accedere all'utente corrente, al record e all'istanza del componente: + +```tsx src/front-components/record-info.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk/front-component'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Hook disponibili: + +| Hook | Restituisce | Descrizione | +| --------------------------------------------- | ----------------- | --------------------------------------------------------------------- | +| `useUserId()` | `string` o `null` | L'ID dell'utente corrente | +| `useRecordId()` | `string` o `null` | L'ID del record corrente (quando posizionato su una pagina di record) | +| `useFrontComponentId()` | `string` | L'ID di questa istanza di componente | +| `useFrontComponentExecutionContext(selector)` | varia | Accedi all'intero contesto di esecuzione con una funzione selettore | + +## API di comunicazione con l'host + +I componenti front-end possono attivare navigazione, modali e notifiche utilizzando funzioni da `twenty-sdk`: + +| Funzione | Descrizione | +| ----------------------------------------------- | ------------------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Naviga a una pagina dell'app | +| `openSidePanelPage(params)` | Apri un pannello laterale | +| `closeSidePanel()` | Chiudi il pannello laterale | +| `openCommandConfirmationModal(params)` | Mostra una finestra di conferma | +| `enqueueSnackbar(params)` | Mostra una notifica toast | +| `unmountFrontComponent()` | Smonta il componente | +| `updateProgress(progress)` | Aggiorna un indicatore di avanzamento | + +Ecco un esempio che usa l'API host per mostrare una snackbar e chiudere il pannello laterale dopo il completamento di un'azione: + +```tsx src/front-components/archive-record.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId } from 'twenty-sdk/front-component'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + +## Opzioni del comando + +Aggiungere un campo `command` a `defineFrontComponent` registra il componente nel menu comandi (Cmd+K). Se `isPinned` è `true`, compare anche come pulsante di azione rapida nell'angolo in alto a destra della pagina. + +| Campo | Obbligatorio | Descrizione | +| --------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `universalIdentifier` | Sì | ID univoco stabile per il comando | +| `etichetta` | Sì | Etichetta completa mostrata nel menu comandi (Cmd+K) | +| `shortLabel` | No | Etichetta breve visualizzata sul pulsante di azione rapida fissato | +| `icona` | No | Nome dell'icona visualizzato accanto all'etichetta (ad es. `'IconBolt'`, `'IconSend'`) | +| `isPinned` | No | Quando `true`, mostra il comando come pulsante di azione rapida nell'angolo in alto a destra della pagina | +| `availabilityType` | No | Controlla dove compare il comando: `'GLOBAL'` (sempre disponibile), `'RECORD_SELECTION'` (solo quando sono selezionati dei record) o `'FALLBACK'` (mostrato quando nessun altro comando corrisponde) | +| `availabilityObjectUniversalIdentifier` | No | Limita il comando alle pagine di uno specifico tipo di oggetto (ad es. solo sui record Company) | +| `conditionalAvailabilityExpression` | No | Un'espressione booleana per controllare dinamicamente se il comando è visibile (vedi sotto) | + +## Espressioni di disponibilità condizionale + +Il campo `conditionalAvailabilityExpression` consente di controllare quando un comando è visibile in base al contesto della pagina corrente. Importa variabili tipizzate e operatori da `twenty-sdk` per costruire espressioni: + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk/front-component'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Variabili di contesto** — rappresentano lo stato corrente della pagina: + +| Variabile | Tipo | Descrizione | +| ------------------------------ | --------- | ------------------------------------------------------------------------ | +| `pageType` | `string` | Tipo di pagina corrente (ad es. `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Indica se il componente è renderizzato in un pannello laterale | +| `numberOfSelectedRecords` | `numero` | Numero di record attualmente selezionati | +| `isSelectAll` | `boolean` | Indica se "seleziona tutto" è attivo | +| `selectedRecords` | `array` | Gli oggetti dei record selezionati | +| `favoriteRecordIds` | `array` | ID dei record aggiunti ai preferiti | +| `objectPermissions` | `oggetto` | Autorizzazioni per il tipo di oggetto corrente | +| `targetObjectReadPermissions` | `oggetto` | Autorizzazioni di lettura per l'oggetto di destinazione | +| `targetObjectWritePermissions` | `oggetto` | Autorizzazioni di scrittura per l'oggetto di destinazione | +| `featureFlags` | `oggetto` | Flag delle funzionalità attivi | +| `objectMetadataItem` | `oggetto` | Metadati del tipo di oggetto corrente | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Indica se la vista corrente ha un filtro di soft-delete | + +**Operatori** — combinano variabili in espressioni booleane: + +| Operatore | Descrizione | +| ----------------------------------- | -------------------------------------------------------------------- | +| `isDefined(value)` | `true` se il valore non è null/undefined | +| `isNonEmptyString(value)` | `true` se il valore è una stringa non vuota | +| `includes(array, value)` | `true` se l'array contiene il valore | +| `includesEvery(array, prop, value)` | `true` se la proprietà di ogni elemento include il valore | +| `every(array, prop)` | `true` se la proprietà è truthy su ogni elemento | +| `everyDefined(array, prop)` | `true` se la proprietà è definita su ogni elemento | +| `everyEquals(array, prop, value)` | `true` se la proprietà è uguale al valore su ogni elemento | +| `some(array, prop)` | `true` se la proprietà è truthy su almeno un elemento | +| `someDefined(array, prop)` | `true` se la proprietà è definita su almeno un elemento | +| `someEquals(array, prop, value)` | `true` se la proprietà è uguale al valore su almeno un elemento | +| `someNonEmptyString(array, prop)` | `true` se la proprietà è una stringa non vuota su almeno un elemento | +| `none(array, prop)` | `true` se la proprietà è falsy su ogni elemento | +| `noneDefined(array, prop)` | `true` se la proprietà è undefined su ogni elemento | +| `noneEquals(array, prop, value)` | `true` se la proprietà non è uguale al valore su alcun elemento | + +## Asset pubblici + +I componenti front-end possono accedere ai file dalla directory `public/` dell'app utilizzando `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +Vedi la [sezione sugli asset pubblici](/l/it/developers/extend/apps/cli-and-testing#public-assets-public-folder) per i dettagli. + +## Stile + +I componenti front-end supportano diversi approcci di styling. Puoi usare: + +* **Stili inline** — `style={{ color: 'red' }}` +* **Componenti Twenty UI** — importali da `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar e altro) +* **Emotion** — CSS-in-JS con `@emotion/react` +* **Styled-components** — pattern `styled.div` +* **Tailwind CSS** — classi di utilità +* **Qualsiasi libreria CSS-in-JS** compatibile con React + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` diff --git a/packages/twenty-docs/l/it/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/it/developers/extend/apps/getting-started.mdx index 122fd767ce8..6a18d413cf4 100644 --- a/packages/twenty-docs/l/it/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/it/developers/extend/apps/getting-started.mdx @@ -1,12 +1,9 @@ --- title: Per iniziare +icon: rocket description: Crea la tua prima app Twenty in pochi minuti. --- - -Le app sono attualmente in fase alfa. La funzionalità funziona ma è ancora in evoluzione. - - ## Cosa sono le app? Le app ti consentono di estendere Twenty con oggetti, campi, funzioni logiche, componenti front-end, competenze IA e altro ancora — il tutto gestito come codice. Invece di configurare tutto tramite l'interfaccia utente, definisci in TypeScript il modello dati e la logica e li distribuisci in uno o più spazi di lavoro. diff --git a/packages/twenty-docs/l/it/developers/extend/apps/layout.mdx b/packages/twenty-docs/l/it/developers/extend/apps/layout.mdx new file mode 100644 index 00000000000..aa6eef513d4 --- /dev/null +++ b/packages/twenty-docs/l/it/developers/extend/apps/layout.mdx @@ -0,0 +1,131 @@ +--- +title: Disposizione +description: Define views, navigation menu items, and page layouts to shape how your app appears in Twenty. +icon: table-columns +--- + +Layout entities control how your app surfaces inside Twenty's UI — what lives in the sidebar, which saved views ship with the app, and how a record detail page is arranged. + +## Layout concepts + +| Concept | What it controls | Entità | +| ------------------------ | --------------------------------------------------------------------------------- | -------------------------- | +| **View** | A saved list configuration for an object — visible fields, order, filters, groups | `defineView` | +| **Navigation Menu Item** | An entry in the left sidebar that links to a view or an external URL | `defineNavigationMenuItem` | +| **Page Layout** | The tabs and widgets that make up a record's detail page | `definePageLayout` | + +Views, navigation items, and page layouts reference each other by `universalIdentifier`: + +* A **navigation menu item** of type `VIEW` points at a `defineView` identifier, so the sidebar link opens that saved view. +* A **page layout** of type `RECORD_PAGE` targets an object and can embed [front components](/l/it/developers/extend/apps/front-components) inside its tabs as widgets. + + + + +Le viste sono configurazioni salvate di come vengono visualizzati i record di un oggetto — inclusi quali campi sono visibili, il loro ordine e gli eventuali filtri o raggruppamenti applicati. Usa `defineView()` per fornire viste preconfigurate con la tua app: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +Punti chiave: +* `objectUniversalIdentifier` specifica a quale oggetto si applica questa vista. +* `key` determina il tipo di vista (ad es., `ViewKey.INDEX` per la vista elenco principale). +* `fields` controlla quali colonne compaiono e il loro ordine. Ogni campo fa riferimento a un `fieldMetadataUniversalIdentifier`. +* Puoi anche definire `filters`, `filterGroups`, `groups` e `fieldGroups` per configurazioni più avanzate. +* `position` controlla l'ordinamento quando esistono più viste per lo stesso oggetto. + + + + +Le voci del menu di navigazione aggiungono elementi personalizzati alla barra laterale dello spazio di lavoro. Usa `defineNavigationMenuItem()` per collegarti a viste, URL esterni o oggetti: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Punti chiave: +* `type` determina a cosa rimanda la voce di menu: `NavigationMenuItemType.VIEW` per una vista salvata o `NavigationMenuItemType.LINK` per un URL esterno. +* Per i link a viste, imposta `viewUniversalIdentifier`. Per i link esterni, imposta `link`. +* `position` controlla l'ordinamento nella barra laterale. +* `icon` e `color` (opzionali) personalizzano l'aspetto. + + + + +I layout di pagina ti consentono di personalizzare l'aspetto di una pagina dei dettagli di un record — quali schede compaiono, quali widget sono all'interno di ciascuna scheda e come sono disposti. Usa `definePageLayout()` per fornire layout personalizzati con la tua app: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Punti chiave: +* `type` è in genere `'RECORD_PAGE'` per personalizzare la vista dei dettagli di un oggetto specifico. +* `objectUniversalIdentifier` specifica a quale oggetto si applica questo layout. +* Ogni `tab` definisce una sezione della pagina con un `title`, `position` e `layoutMode` (`CANVAS` per il layout libero). +* Ogni `widget` all'interno di una scheda può renderizzare un componente front-end, un elenco di relazioni o altri tipi di widget integrati. +* `position` sulle schede controlla il loro ordine. Usa valori più alti (ad es., 50) per posizionare le schede personalizzate dopo quelle integrate. + + + diff --git a/packages/twenty-docs/l/it/developers/extend/apps/logic-functions.mdx b/packages/twenty-docs/l/it/developers/extend/apps/logic-functions.mdx new file mode 100644 index 00000000000..6bd81658593 --- /dev/null +++ b/packages/twenty-docs/l/it/developers/extend/apps/logic-functions.mdx @@ -0,0 +1,559 @@ +--- +title: Funzioni logiche +description: Define server-side TypeScript functions with HTTP, cron, and database event triggers. +icon: bolt +--- + +Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents. + + + + +Ogni file di funzione usa `defineLogicFunction()` per esportare una configurazione con un handler e trigger opzionali. + +```ts src/logic-functions/createPostCard.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; + +const handler = async (params: RoutePayload) => { + const client = new CoreApiClient(); + const name = 'name' in params.queryStringParameters + ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' + : 'Hello world'; + + const result = await client.mutation({ + createPostCard: { + __args: { data: { name } }, + id: true, + name: true, + }, + }); + return result; +}; + +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'create-new-post-card', + timeoutSeconds: 2, + handler, + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: true, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ +}); +``` + +Tipi di trigger disponibili: +* **httpRoute**: Espone la tua funzione su un percorso e metodo HTTP **sotto l'endpoint `/s/`**: +> ad es. `path: '/post-card/create'` è invocabile su `https://your-twenty-server.com/s/post-card/create` +* **cron**: Esegue la tua funzione secondo una pianificazione utilizzando un'espressione CRON. +* **databaseEvent**: Viene eseguito sugli eventi del ciclo di vita degli oggetti dello spazio di lavoro. Quando l'operazione dell'evento è `updated`, è possibile specificare campi specifici da monitorare nell'array `updatedFields`. Se lasciato non definito o vuoto, qualsiasi aggiornamento attiverà la funzione. +> ad es. `person.updated`, `*.created`, `company.*` + + +Puoi anche eseguire manualmente una funzione utilizzando la CLI: + +```bash filename="Terminal" +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' +``` + +```bash filename="Terminal" +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + +Puoi osservare i log con: + +```bash filename="Terminal" +yarn twenty logs +``` + + +#### Payload del trigger di route + +Quando un trigger di tipo route invoca la tua funzione logica, questa riceve un oggetto `RoutePayload` che segue il [formato AWS HTTP API v2](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +Importa il tipo `RoutePayload` da `twenty-sdk`: + +```ts +import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; + +const handler = async (event: RoutePayload) => { + const { headers, queryStringParameters, pathParameters, body } = event; + const { method, path } = event.requestContext.http; + + return { message: 'Success' }; +}; +``` + +Il tipo `RoutePayload` ha la seguente struttura: + + | Proprietà | Tipo | Descrizione | Esempio | + | ---------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record\` | Intestazioni HTTP (solo quelle elencate in `forwardedRequestHeaders`) | vedi la sezione sotto | + | `queryStringParameters` | `Record\` | Parametri della query string (valori multipli uniti da virgole) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record\` | Parametri di percorso estratti dal pattern della route | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Corpo della richiesta analizzato (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | Indica se il corpo è codificato in base64 | | + | `requestContext.http.method` | `string` | Metodo HTTP (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | Percorso della richiesta non elaborato | | + + +#### forwardedRequestHeaders + +Per impostazione predefinita, le intestazioni HTTP delle richieste in ingresso **non** vengono passate alla tua funzione logica per motivi di sicurezza. +Per accedere a intestazioni specifiche, elencale nell'array `forwardedRequestHeaders`: + +```ts +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'webhook-handler', + handler, + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, +}); +``` + +Nel tuo handler, accedi alle intestazioni inoltrate in questo modo: + +```ts +const handler = async (event: RoutePayload) => { + const signature = event.headers['x-webhook-signature']; + const contentType = event.headers['content-type']; + + // Validate webhook signature... + return { received: true }; +}; +``` + + +I nomi delle intestazioni vengono normalizzati in minuscolo. Accedile usando chiavi in minuscolo (ad es., `event.headers['content-type']`). + + +#### Esporre una funzione come strumento + +Le funzioni logiche possono essere esposte come **strumenti** per gli agenti di IA e i flussi di lavoro. Quando una funzione è contrassegnata come strumento, diventa individuabile dalle funzionalità di IA di Twenty e può essere utilizzata nelle automazioni dei flussi di lavoro. + +Per contrassegnare una funzione logica come strumento, imposta `isTool: true`: + +```ts src/logic-functions/enrich-company.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const handler = async (params: { companyName: string; domain?: string }) => { + const client = new CoreApiClient(); + + const result = await client.mutation({ + createTask: { + __args: { + data: { + title: `Enrich data for ${params.companyName}`, + body: `Domain: ${params.domain ?? 'unknown'}`, + }, + }, + id: true, + }, + }); + + return { taskId: result.createTask.id }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + name: 'enrich-company', + description: 'Enrich a company record with external data', + timeoutSeconds: 10, + handler, + isTool: true, +}); +``` + +Punti chiave: + +* Puoi combinare `isTool` con i trigger — una funzione può essere sia uno strumento (invocabile dagli agenti IA) sia attivata da eventi allo stesso tempo. +* **`toolInputSchema`** (opzionale): un oggetto JSON Schema che descrive i parametri accettati dalla funzione. Lo schema viene calcolato automaticamente dall'analisi statica del codice sorgente, ma puoi impostarlo esplicitamente: + +```ts +export default defineLogicFunction({ + ..., + toolInputSchema: { + type: 'object', + properties: { + companyName: { + type: 'string', + description: 'The name of the company to enrich', + }, + domain: { + type: 'string', + description: 'The company website domain (optional)', + }, + }, + required: ['companyName'], + }, +}); +``` + + +**Scrivi una buona `description`.** Gli agenti IA fanno affidamento sul campo `description` della funzione per decidere quando usare lo strumento. Sii specifico su cosa fa lo strumento e quando dovrebbe essere invocato. + + + + + +Una funzione post-installazione è una funzione logica che viene eseguita automaticamente dopo che la tua app è stata installata in uno spazio di lavoro. Il server la esegue **dopo** che i metadati dell'app sono stati sincronizzati e il client SDK è stato generato, così lo spazio di lavoro è completamente pronto per l'uso e il nuovo schema è attivo. I casi d'uso tipici includono il popolamento di dati predefiniti, la creazione di record iniziali, la configurazione delle impostazioni dello spazio di lavoro o il provisioning di risorse su servizi di terze parti. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + shouldRunSynchronously: false, + handler, +}); +``` + +Puoi anche eseguire manualmente la funzione di post-installazione in qualsiasi momento utilizzando la CLI: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Punti chiave: +* Le funzioni di post-installazione utilizzano `definePostInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* L'handler riceve un `InstallPayload` con `{ previousVersion?: string; newVersion: string }` — `newVersion` è la versione in fase di installazione e `previousVersion` è la versione installata in precedenza (oppure `undefined` in caso di nuova installazione). Usa questi valori per distinguere le nuove installazioni dagli aggiornamenti e per eseguire logiche di migrazione specifiche per versione. +* **Quando viene eseguito l'hook**: solo sulle nuove installazioni, per impostazione predefinita. Passa `shouldRunOnVersionUpgrade: true` se vuoi che venga eseguito anche quando l'app viene aggiornata da una versione precedente. Se omesso, il flag è `false` per impostazione predefinita e gli aggiornamenti saltano l'hook. +* **Modello di esecuzione — asincrono per impostazione predefinita, sincrono su richiesta**: il flag `shouldRunSynchronously` controlla *come* viene eseguito il post-install. + * `shouldRunSynchronously: false` *(default)* — l'hook viene **messo in coda nella coda dei messaggi** con `retryLimit: 3` ed eseguito in modo asincrono in un worker. La risposta di installazione viene restituita non appena il job è messo in coda, quindi un handler lento o in errore non blocca il chiamante. Il worker riproverà fino a tre volte. **Usalo per job di lunga durata** — popolamento di dataset di grandi dimensioni, chiamate a API di terze parti lente, provisioning di risorse esterne, qualsiasi cosa che possa superare una finestra di risposta HTTP ragionevole. + * `shouldRunSynchronously: true` — l'hook viene eseguito **inline durante il flusso di installazione** (stesso executor del pre-install). La richiesta di installazione rimane bloccata finché l'handler non termina e, se genera un'eccezione, il chiamante dell'installazione riceve un `POST_INSTALL_ERROR`. Nessun tentativo automatico. **Usalo per attività rapide che devono completarsi prima della risposta** — ad esempio, emettere un errore di validazione all'utente, oppure un setup rapido di cui il client avrà bisogno immediatamente dopo il ritorno della chiamata di installazione. Tieni presente che la migrazione dei metadati è già stata applicata quando viene eseguito il post-install, quindi un errore in modalità sincrona **non** annulla le modifiche allo schema — si limita a far emergere l'errore. +* Assicurati che il tuo handler sia idempotente. In modalità asincrona la coda può riprovare fino a tre volte; in entrambe le modalità l'hook può essere eseguito di nuovo durante gli aggiornamenti quando `shouldRunOnVersionUpgrade: true`. +* Le variabili d'ambiente `APPLICATION_ID`, `APP_ACCESS_TOKEN` e `API_URL` sono disponibili all'interno dell'handler (come in qualsiasi altra funzione logica), quindi puoi chiamare le API di Twenty con un token di accesso applicativo con ambito sulla tua app. +* È consentita una sola funzione di post-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una. +* I campi `universalIdentifier`, `shouldRunOnVersionUpgrade` e `shouldRunSynchronously` della funzione vengono associati automaticamente al manifest dell'applicazione nel campo `postInstallLogicFunction` durante la build — non è necessario referenziarli in `defineApplication()`. +* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di configurazione più lunghe, come il popolamento dei dati. +* **Non eseguito in modalità dev**: quando un'app è registrata in locale (tramite `yarn twenty dev`), il server salta completamente il flusso di installazione e sincronizza i file direttamente tramite il watcher della CLI — quindi il post-install non viene mai eseguito in modalità dev, indipendentemente da `shouldRunSynchronously`. Usa `yarn twenty exec --postInstall` per attivarlo manualmente su un workspace in esecuzione. + + + + +Una funzione di pre-install è una funzione logica che viene eseguita automaticamente durante l'installazione, **prima che venga applicata la migrazione dei metadati del workspace**. Condivide la stessa struttura di payload del post-install (`InstallPayload`), ma è posizionata prima nel flusso di installazione così da poter preparare lo stato da cui dipenderà la migrazione imminente — usi tipici includono il backup dei dati, la validazione della compatibilità con il nuovo schema o l'archiviazione di record che stanno per essere ristrutturati o eliminati. + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +Puoi anche eseguire manualmente la funzione di pre-installazione in qualsiasi momento utilizzando la CLI: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Punti chiave: +* Le funzioni di pre-install usano `definePreInstallLogicFunction()` — stessa configurazione specialistica del post-install, solo agganciata a uno slot di ciclo di vita diverso. +* Sia gli handler di pre- sia quelli di post-install ricevono lo stesso tipo `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Importalo una volta e riutilizzalo per entrambi gli hook. +* **Quando viene eseguito l'hook**: posizionato appena prima della migrazione dei metadati del workspace (`synchronizeFromManifest`). Prima dell'esecuzione, il server esegue una "sincronizzazione ridotta" puramente additiva che registra nei metadati del workspace la funzione di pre-install della versione **nuova** — nient'altro viene toccato — e poi la esegue. Poiché questa sincronizzazione è solo additiva, gli oggetti, i campi e i dati della versione precedente restano intatti quando il tuo handler viene eseguito: puoi leggere ed eseguire in sicurezza il backup dello stato pre-migrazione. +* **Modello di esecuzione**: il pre-install è eseguito **in modo sincrono** e **blocca l'installazione**. Se l'handler genera un'eccezione, l'installazione viene interrotta prima che vengano applicate modifiche allo schema — il workspace rimane sulla versione precedente in uno stato coerente. Questo è intenzionale: il pre-install è la tua ultima possibilità per rifiutare un aggiornamento rischioso. +* Come per il post-install, è consentita una sola funzione di pre-installazione per applicazione. Viene collegata automaticamente al manifest dell'applicazione nel campo `preInstallLogicFunction` durante la build. +* **Non eseguito in modalità dev**: come per il post-install — il flusso di installazione viene completamente saltato per le app registrate localmente, quindi il pre-install non viene mai eseguito con `yarn twenty dev`. Usa `yarn twenty exec --preInstall` per attivarlo manualmente. + + + + +Entrambi gli hook fanno parte dello stesso flusso di installazione e ricevono lo stesso `InstallPayload`. La differenza è **quando** vengono eseguiti rispetto alla migrazione dei metadati del workspace, e questo modifica quali dati possono gestire in sicurezza. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ install flow │ +│ │ +│ upload package → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +│ │ +│ old schema visible new schema visible │ +└─────────────────────────────────────────────────────────────┘ +``` + +Il pre-install è sempre **sincrono** (blocca l'installazione e può interromperla). Il post-install è **asincrono per impostazione predefinita** — messo in coda su un worker con retry automatici — ma può optare per l'esecuzione sincrona con `shouldRunSynchronously: true`. Vedi l'accordion `definePostInstallLogicFunction` sopra per quando usare ciascuna modalità. + +**Usa `post-install` per tutto ciò che richiede l'esistenza del nuovo schema.** Questo è il caso più comune: + +* Popolamento di dati predefiniti (creazione di record iniziali, viste predefinite, contenuti demo) su oggetti e campi appena aggiunti. +* Registrazione di webhook con servizi di terze parti ora che l'app ha le proprie credenziali. +* Chiamare la tua API per completare il setup che dipende dai metadati sincronizzati. +* Logica idempotente di "ensure this exists" che dovrebbe riconciliare lo stato a ogni aggiornamento — da combinare con `shouldRunOnVersionUpgrade: true`. + +Esempio — eseguire il seeding di un record `PostCard` predefinito dopo l'installazione: + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion }: InstallPayload): Promise => { + if (previousVersion) return; // fresh installs only + + const client = createClient(); + await client.postCard.create({ + data: { title: 'Welcome to Postcard', content: 'Your first card!' }, + }); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Seeds a welcome post card after install.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + handler, +}); +``` + +**Usa `pre-install` quando una migrazione altrimenti distruggerebbe o corromperebbe i dati esistenti.** Poiché il pre-install viene eseguito contro lo schema *precedente* e un suo fallimento annulla l'aggiornamento, è il posto giusto per qualsiasi operazione rischiosa: + +* **Eseguire il backup dei dati che stanno per essere eliminati o ristrutturati** — ad esempio, stai rimuovendo un campo nella v2 e devi copiarne i valori in un altro campo o esportarli su uno storage prima che venga eseguita la migrazione. +* **Archiviare i record che un nuovo vincolo renderebbe non validi** — ad esempio, un campo sta diventando `NOT NULL` e devi prima eliminare o correggere le righe con valori nulli. +* **Validare la compatibilità e rifiutare l'aggiornamento se i dati attuali non possono essere migrati correttamente** — genera un'eccezione dall'handler e l'installazione si interrompe senza applicare modifiche. Questo è più sicuro che scoprire l'incompatibilità a migrazione in corso. +* **Rinominare o rigenerare le chiavi dei dati** prima di una modifica dello schema che farebbe perdere l'associazione. + +Esempio — archiviare i record prima di una migrazione distruttiva: + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { + // Only the 1.x → 2.x upgrade drops the legacy `notes` field. + if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { + return; + } + + const client = createClient(); + const legacyRecords = await client.postCard.findMany({ + where: { notes: { isNotNull: true } }, + }); + + if (legacyRecords.length === 0) return; + + // Copy legacy `notes` into the new `description` field before the migration + // drops the `notes` column. If this fails, the upgrade is aborted and the + // workspace stays on v1 with all data intact. + await Promise.all( + legacyRecords.map((record) => + client.postCard.update({ + where: { id: record.id }, + data: { description: record.notes }, + }), + ), + ); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Backs up legacy notes into description before the v2 migration.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +**Regola generale:** + +| You want to... | Usa | +| ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| Popolare dati predefiniti, configurare il workspace, registrare risorse esterne | `post-install` | +| Eseguire seeding di lunga durata o chiamate a terze parti che non dovrebbero bloccare la risposta dell'installazione | `post-install` (predefinito — `shouldRunSynchronously: false`, con retry del worker) | +| Eseguire un setup rapido di cui il chiamante farà affidamento immediatamente dopo il ritorno della chiamata di installazione | `post-install` con `shouldRunSynchronously: true` | +| Leggere o eseguire il backup dei dati che la prossima migrazione perderebbe | `pre-install` | +| Rifiutare un aggiornamento che corromperebbe i dati esistenti | `pre-install` (genera un'eccezione dall'handler) | +| Eseguire la riconciliazione a ogni aggiornamento | `post-install` con `shouldRunOnVersionUpgrade: true` | +| Eseguire un setup una tantum solo alla prima installazione | `post-install` con `shouldRunOnVersionUpgrade: false` (predefinito) | + + +In caso di dubbio, usa **post-install**. Ricorri al pre-install solo quando la migrazione stessa è distruttiva e devi intercettare lo stato precedente prima che vada perso. + + + + + +## Client API tipizzati (twenty-client-sdk) + +Il pacchetto `twenty-client-sdk` fornisce due client GraphQL tipizzati per interagire con l'API di Twenty dalle tue funzioni logiche e dai componenti front-end. + +| Client | Importa | Endpoint | Generato? | +| ------------------- | ---------------------------- | ------------------------------------------------------------------------ | -------------------------- | +| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — dati dello spazio di lavoro (record, oggetti) | Sì, in fase di dev/build | +| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — configurazione dello spazio di lavoro, caricamenti di file | No, fornito pronto all'uso | + + + + +`CoreApiClient` è il client principale per interrogare e modificare i dati dello spazio di lavoro. Viene **generato dallo schema del tuo spazio di lavoro** durante `yarn twenty dev` o `yarn twenty build`, quindi è completamente tipizzato per corrispondere ai tuoi oggetti e campi. + +```ts +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const client = new CoreApiClient(); + +// Query records +const { companies } = await client.query({ + companies: { + edges: { + node: { + id: true, + name: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, + }, + }, + }, +}); + +// Create a record +const { createCompany } = await client.mutation({ + createCompany: { + __args: { + data: { + name: 'Acme Corp', + }, + }, + id: true, + name: true, + }, +}); +``` + +Il client utilizza una sintassi a selection-set: passa `true` per includere un campo, usa `__args` per gli argomenti e annida oggetti per le relazioni. Ottieni completamento automatico e controllo dei tipi completi basati sullo schema del tuo spazio di lavoro. + + +**CoreApiClient viene generato in fase di dev/build.** Se lo usi senza eseguire prima `yarn twenty dev` o `yarn twenty build`, genera un errore. La generazione avviene automaticamente — la CLI esegue l'introspezione dello schema GraphQL del tuo spazio di lavoro e genera un client tipizzato usando `@genql/cli`. + + +#### Utilizzo di CoreSchema per le annotazioni di tipo + +`CoreSchema` fornisce tipi TypeScript corrispondenti agli oggetti del tuo spazio di lavoro — utile per tipizzare lo stato dei componenti o i parametri delle funzioni: + +```ts +import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; +import { useState } from 'react'; + +const [company, setCompany] = useState< + Pick | undefined +>(undefined); + +const client = new CoreApiClient(); +const result = await client.query({ + company: { + __args: { filter: { position: { eq: 1 } } }, + id: true, + name: true, + }, +}); +setCompany(result.company); +``` + + + + +`MetadataApiClient` è fornito pronto all'uso con l'SDK (nessuna generazione richiesta). Interroga l'endpoint `/metadata` per la configurazione dello spazio di lavoro, le applicazioni e i caricamenti di file. + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; + +const metadataClient = new MetadataApiClient(); + +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, + }, +}); +``` + +#### Caricamento dei file + +`MetadataApiClient` include un metodo `uploadFile` per allegare file ai campi di tipo file: + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import * as fs from 'fs'; + +const metadataClient = new MetadataApiClient(); + +const fileBuffer = fs.readFileSync('./invoice.pdf'); + +const uploadedFile = await metadataClient.uploadFile( + fileBuffer, // file contents as a Buffer + 'invoice.pdf', // filename + 'application/pdf', // MIME type + '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier +); + +console.log(uploadedFile); +// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } +``` + +| Parametro | Tipo | Descrizione | +| ---------------------------------- | -------- | ---------------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | Il contenuto grezzo del file | +| `filename` | `string` | Il nome del file (utilizzato per l'archiviazione e la visualizzazione) | +| `contentType` | `string` | Tipo MIME (predefinito su `application/octet-stream` se omesso) | +| `fieldMetadataUniversalIdentifier` | `string` | L'`universalIdentifier` del campo di tipo file nel tuo oggetto | + +Punti chiave: +* Usa l'`universalIdentifier` del campo (non il suo ID specifico dello spazio di lavoro), quindi il tuo codice di upload funziona in qualsiasi spazio di lavoro in cui la tua app è installata. +* L'`url` restituito è un URL firmato che puoi usare per accedere al file caricato. + + + + + + Quando il tuo codice viene eseguito su Twenty (funzioni logiche o componenti front-end), la piattaforma inietta le credenziali come variabili d'ambiente: + + * `TWENTY_API_URL` — URL di base dell'API di Twenty + * `TWENTY_APP_ACCESS_TOKEN` — Chiave a breve durata con ambito al ruolo funzione predefinito della tua applicazione + + Non è **necessario** passarle ai client — vengono lette automaticamente da `process.env`. I permessi della chiave API sono determinati dal ruolo referenziato in `defaultRoleUniversalIdentifier` nel tuo `application-config.ts`. + diff --git a/packages/twenty-docs/l/it/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/it/developers/extend/apps/publishing.mdx index 59621f29725..edbb932459b 100644 --- a/packages/twenty-docs/l/it/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/it/developers/extend/apps/publishing.mdx @@ -1,12 +1,9 @@ --- title: Pubblicazione +icon: carica description: Distribuisci la tua app Twenty nel marketplace oppure distribuiscila internamente. --- - - Le app sono attualmente in fase alfa. La funzionalità funziona ma è ancora in evoluzione. - - ## Panoramica Una volta che la tua app è stata [compilata e testata localmente](/l/it/developers/extend/apps/building), hai due modalità per distribuirla: diff --git a/packages/twenty-docs/l/it/developers/extend/apps/skills-and-agents.mdx b/packages/twenty-docs/l/it/developers/extend/apps/skills-and-agents.mdx new file mode 100644 index 00000000000..4ab153879e1 --- /dev/null +++ b/packages/twenty-docs/l/it/developers/extend/apps/skills-and-agents.mdx @@ -0,0 +1,69 @@ +--- +title: Skill e agenti +description: Define AI skills and agents for your app. +icon: robot +--- + + + Skills and agents are currently in alpha. La funzionalità funziona ma è ancora in evoluzione. + + +Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts. + + + + +Le skill definiscono istruzioni e capacità riutilizzabili che gli agenti IA possono utilizzare all'interno del tuo spazio di lavoro. Usa `defineSkill()` per definire skill con convalida integrata: + +```ts src/skills/example-skill.ts +import { defineSkill } from 'twenty-sdk/define'; + +export default defineSkill({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'sales-outreach', + label: 'Sales Outreach', + description: 'Guides the AI agent through a structured sales outreach process', + icon: 'IconBrain', + content: `You are a sales outreach assistant. When reaching out to a prospect: +1. Research the company and recent news +2. Identify the prospect's role and likely pain points +3. Draft a personalized message referencing specific details +4. Keep the tone professional but conversational`, +}); +``` + +Punti chiave: +* `name` è una stringa identificativa univoca per la skill (kebab-case consigliato). +* `label` è il nome di visualizzazione leggibile mostrato nell'UI. +* `content` contiene le istruzioni della skill — questo è il testo che l'agente IA utilizza. +* `icon` (opzionale) imposta l'icona visualizzata nell'UI. +* `description` (opzionale) fornisce contesto aggiuntivo sullo scopo della skill. + + + + +Gli agenti sono assistenti IA che vivono all'interno del tuo spazio di lavoro. Usa `defineAgent()` per creare agenti con un prompt di sistema personalizzato: + +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk/define'; + +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +Punti chiave: +* `name` è la stringa identificativa univoca dell'agente (kebab-case consigliato). +* `label` è il nome visualizzato nell'UI. +* `prompt` è il prompt di sistema che definisce il comportamento dell'agente. +* `description` (opzionale) fornisce contesto su ciò che fa l'agente. +* `icon` (opzionale) imposta l'icona visualizzata nell'UI. +* `modelId` (opzionale) sostituisce il modello di IA predefinito utilizzato dall'agente. + + + diff --git a/packages/twenty-docs/l/it/developers/extend/oauth.mdx b/packages/twenty-docs/l/it/developers/extend/oauth.mdx new file mode 100644 index 00000000000..f06a05447ce --- /dev/null +++ b/packages/twenty-docs/l/it/developers/extend/oauth.mdx @@ -0,0 +1,189 @@ +--- +title: OAuth +icon: chiave +description: Authorization code flow with PKCE and client credentials for server-to-server access. +--- + +Twenty implements OAuth 2.0 with authorization code + PKCE for user-facing apps and client credentials for server-to-server access. Clients are registered dynamically via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) — no manual setup in a dashboard. + +## When to Use OAuth + +| Scenario | Auth Method | +| --------------------------------------- | -------------------------------------------------------------------------------- | +| Internal scripts, automation | [API Key](/l/it/developers/extend/api#authentication) | +| External app acting on behalf of a user | **OAuth — Authorization Code** | +| Server-to-server, no user context | **OAuth — Client Credentials** | +| Twenty App with UI extensions | [Apps](/l/it/developers/extend/apps/getting-started) (OAuth is handled automatically) | + +## Register a Client + +Twenty supports **dynamic client registration** per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). No manual setup needed — register programmatically: + +```bash +POST /oauth/register +Content-Type: application/json + +{ + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"], + "grant_types": ["authorization_code"], + "token_endpoint_auth_method": "client_secret_post" +} +``` + +**Response:** + +```json +{ + "client_id": "abc123", + "client_secret": "secret456", + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"] +} +``` + + +Store the `client_secret` securely — it cannot be retrieved later. + + +## Ambiti + +| Scope | Accesso | +| --------- | ---------------------------------------------------- | +| `api` | Full read/write access to the Core and Metadata APIs | +| `profilo` | Read the authenticated user's profile information | + +Request scopes as a space-separated string: `scope=api profile` + +## Authorization Code Flow + +Use this flow when your app acts on behalf of a Twenty user. + +### 1. Redirect the user to authorize + +``` +GET /oauth/authorize? + client_id=YOUR_CLIENT_ID& + response_type=code& + redirect_uri=https://myapp.com/callback& + scope=api& + state=random_state_value& + code_challenge=CHALLENGE& + code_challenge_method=S256 +``` + +| Parametro | Obbligatorio | Descrizione | +| ----------------------- | ------------ | ------------------------------------------------------------ | +| `client_id` | Sì | Your registered client ID | +| `response_type` | Sì | Must be `code` | +| `redirect_uri` | Sì | Must match a registered redirect URI | +| `scope` | No | Space-separated scopes (defaults to `api`) | +| `stato` | Consigliato | Random string to prevent CSRF attacks | +| `code_challenge` | Consigliato | PKCE challenge (SHA-256 hash of verifier, base64url-encoded) | +| `code_challenge_method` | Consigliato | Must be `S256` when using PKCE | + +The user sees a consent screen and approves or denies access. + +### 2. Handle the callback + +After authorization, Twenty redirects back to your `redirect_uri`: + +``` +https://myapp.com/callback?code=AUTH_CODE&state=random_state_value +``` + +Verify that `state` matches what you sent. + +### 3. Exchange the code for tokens + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=authorization_code& +code=AUTH_CODE& +redirect_uri=https://myapp.com/callback& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +code_verifier=YOUR_PKCE_VERIFIER +``` + +**Response:** + +```json +{ + "access_token": "eyJhbG...", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "dGhpcyBpcyBh..." +} +``` + +### 4. Use the access token + +```bash +GET /rest/companies +Authorization: Bearer ACCESS_TOKEN +``` + +### 5. Refresh when expired + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=refresh_token& +refresh_token=YOUR_REFRESH_TOKEN& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET +``` + +## Client Credentials Flow + +For server-to-server integrations with no user interaction: + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=client_credentials& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +scope=api +``` + +The returned token has workspace-level access, not tied to any specific user. + +## Server Discovery + +Twenty publishes its OAuth configuration at a standard discovery endpoint: + +``` +GET /.well-known/oauth-authorization-server +``` + +This returns all endpoints, supported grant types, scopes, and capabilities — useful for building generic OAuth clients. + +## API Endpoints Summary + +| Endpoint | Scopo | +| ----------------------------------------- | --------------------------- | +| `/.well-known/oauth-authorization-server` | Server metadata discovery | +| `/oauth/register` | Dynamic client registration | +| `/oauth/authorize` | User authorization | +| `/oauth/token` | Token exchange and refresh | + +| Ambiente | URL di base | +| ----------------- | ------------------------ | +| **Cloud** | `https://api.twenty.com` | +| **Auto-ospitato** | `https://{your-domain}` | + +## OAuth vs API Keys + +| | API Keys | OAuth | +| ------------------ | ----------------------- | -------------------------------------- | +| **Impostazione** | Generate in Settings | Register a client, implement flow | +| **User context** | None (workspace-level) | Specific user's permissions | +| **Ideale per** | Scripts, internal tools | External apps, multi-user integrations | +| **Token rotation** | Manuale | Automatic via refresh tokens | +| **Scoped access** | Full API access | Granular via scopes | diff --git a/packages/twenty-docs/l/it/developers/extend/webhooks.mdx b/packages/twenty-docs/l/it/developers/extend/webhooks.mdx index bed2ad40101..51856eba5f9 100644 --- a/packages/twenty-docs/l/it/developers/extend/webhooks.mdx +++ b/packages/twenty-docs/l/it/developers/extend/webhooks.mdx @@ -1,11 +1,12 @@ --- title: Webhooks -description: Ricevi notifiche in tempo reale quando si verificano eventi nel tuo CRM. +icon: satellite-dish +description: Get notified when records change — HTTP POST to your endpoint on every create, update, or delete. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -I webhook inviano dati ai tuoi sistemi in tempo reale quando si verificano eventi in Twenty — senza necessità di polling. Usali per mantenere sincronizzati i sistemi esterni, attivare automazioni o inviare avvisi. +Twenty sends an HTTP POST to your URL whenever a record is created, updated, or deleted. All object types are covered, including custom objects. ## Crea un Webhook diff --git a/packages/twenty-docs/l/it/developers/introduction.mdx b/packages/twenty-docs/l/it/developers/introduction.mdx index 4c73b895700..0c3e447899f 100644 --- a/packages/twenty-docs/l/it/developers/introduction.mdx +++ b/packages/twenty-docs/l/it/developers/introduction.mdx @@ -1,23 +1,28 @@ --- -title: Per iniziare -description: Benvenuto nella documentazione per sviluppatori di Twenty, le tue risorse per estendere, effettuare il self-hosting e contribuire a Twenty. +title: Sviluppatori +description: Build apps, use the API, self-host, or contribute to the codebase. --- import { CardTitle } from "/snippets/card-title.mdx" - - Estendi - Crea integrazioni con API, webhook e app personalizzate. + + Apps + Extend Twenty with custom objects, server-side logic, UI components, and AI agents — all as TypeScript packages. - - Self-hosting - Distribuisci e gestisci Twenty sulla tua infrastruttura. + + API + REST and GraphQL APIs, webhooks, and OAuth. - - Contribuisci - Unisciti alla nostra community open source e contribuisci a Twenty. + + Self-Host + Run Twenty on your own infrastructure. + + + + Contribute + Set up the monorepo locally and submit PRs. diff --git a/packages/twenty-docs/l/it/developers/self-host/capabilities/cloud-providers.mdx b/packages/twenty-docs/l/it/developers/self-host/capabilities/cloud-providers.mdx index 3570d578fc9..6b72ddd76b7 100644 --- a/packages/twenty-docs/l/it/developers/self-host/capabilities/cloud-providers.mdx +++ b/packages/twenty-docs/l/it/developers/self-host/capabilities/cloud-providers.mdx @@ -1,5 +1,6 @@ --- title: Altri metodi +icon: cloud --- diff --git a/packages/twenty-docs/l/it/developers/self-host/capabilities/docker-compose.mdx b/packages/twenty-docs/l/it/developers/self-host/capabilities/docker-compose.mdx index 66223b16096..2e649e01d53 100644 --- a/packages/twenty-docs/l/it/developers/self-host/capabilities/docker-compose.mdx +++ b/packages/twenty-docs/l/it/developers/self-host/capabilities/docker-compose.mdx @@ -1,5 +1,6 @@ --- -title: 1-Click con Docker Compose +title: Docker Compose +icon: docker --- diff --git a/packages/twenty-docs/l/it/developers/self-host/capabilities/setup.mdx b/packages/twenty-docs/l/it/developers/self-host/capabilities/setup.mdx index da65c47c053..bcc19fc728e 100644 --- a/packages/twenty-docs/l/it/developers/self-host/capabilities/setup.mdx +++ b/packages/twenty-docs/l/it/developers/self-host/capabilities/setup.mdx @@ -1,5 +1,6 @@ --- title: Impostazione +icon: gear --- # Gestione della Configurazione diff --git a/packages/twenty-docs/l/it/developers/self-host/capabilities/troubleshooting.mdx b/packages/twenty-docs/l/it/developers/self-host/capabilities/troubleshooting.mdx index 85de7e7427b..941e6699537 100644 --- a/packages/twenty-docs/l/it/developers/self-host/capabilities/troubleshooting.mdx +++ b/packages/twenty-docs/l/it/developers/self-host/capabilities/troubleshooting.mdx @@ -1,5 +1,6 @@ --- title: Risoluzione dei problemi +icon: wrench --- ## Risoluzione dei problemi diff --git a/packages/twenty-docs/l/it/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/it/developers/self-host/capabilities/upgrade-guide.mdx index c6132ab9504..a0dd9b35820 100644 --- a/packages/twenty-docs/l/it/developers/self-host/capabilities/upgrade-guide.mdx +++ b/packages/twenty-docs/l/it/developers/self-host/capabilities/upgrade-guide.mdx @@ -1,5 +1,6 @@ --- title: Guida per l'aggiornamento +icon: arrow-up-right-dots --- ## Linee guida generali @@ -16,366 +17,14 @@ Se hai utilizzato Docker Compose, segui questi passaggi: 3. Riporta Twenty online con `docker compose up -d` -Se desideri aggiornare la tua istanza di alcune versioni, ad es. da v0.33.0 a v0.35.0, devi aggiornare la tua istanza in modo sequenziale, in questo esempio da v0.33.0 a v0.34.0, quindi da v0.34.0 a v0.35.0. - **Assicurati che dopo ogni versione aggiornata tu abbia un backup non corrotto.** ## Passaggi di aggiornamento specifici per la versione -## v1.0 +## After v1.21 -Ciao Twenty v1.0! 🎉 +We know support sequential upgrades. You don't need to go through each version one by one. -## v0.60 +## Before v1.21 -### Miglioramenti delle prestazioni - -Tutte le interazioni con l'API dei metadati sono state ottimizzate per migliori prestazioni, specialmente per la manipolazione dei metadati degli oggetti e le operazioni di creazione dello spazio di lavoro. - -Abbiamo ristrutturato la nostra strategia di caching per dare priorità ai cache hit rispetto alle query del database quando possibile, migliorando significativamente le prestazioni delle operazioni dell'API dei metadati. - -Se incontri problemi di runtime dopo l'aggiornamento, potresti dover svuotare la cache per assicurarti che sia sincronizzata con le ultime modifiche. Esegui questo comando nel tuo contenitore twenty-server: - -```bash -yarn command:prod cache:flush -``` - -### v0.55 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.55 - -Non è più necessario eseguire alcun comando, la nuova immagine si occuperà automaticamente di eseguire tutte le migrazioni richieste. - -### Errore `User does not have permission` - -Se riscontri errori di autorizzazione nella maggior parte delle richieste dopo l'aggiornamento, potresti dover svuotare la cache per ricalcolare le autorizzazioni più recenti. - -Nel tuo contenitore `twenty-server`, esegui: - -```bash -yarn command:prod cache:flush -``` - -Questo problema è specifico per questa versione di Twenty e non dovrebbe essere richiesto per i futuri aggiornamenti. - -### v0.54 - -Dalla versione `0.53`, non sono necessarie azioni manuali. - -#### Deprecazione dello schema dei metadati - -Abbiamo fuso lo schema `metadata` in quello `core` per semplificare il recupero dei dati da `TypeORM`. -Abbiamo fuso il passaggio del comando `migrate` all'interno del comando `upgrade`. Non raccomandiamo di eseguire il comando `migrate` manualmente all'interno di nessuno dei tuoi contenitori server/worker. - -### Dalla v0.53 - -A partire da `0.53`, l'aggiornamento è eseguito programmaticamente all'interno del `DockerFile`, il che significa che d'ora in poi, non è necessario eseguire alcun comando manualmente. - -Assicurati di continuare ad aggiornare la tua istanza in modo sequenziale, senza saltare alcuna versione principale (ad es. `0.43.3` a `0.44.0` è consentito, ma `0.43.1` a `0.45.0` non lo è), altrimenti potrebbe portare la sincronizzazione delle versioni dello spazio di lavoro a desincronizzarsi, il che potrebbe causare errori di runtime e funzionalità mancanti. - -Per verificare se uno spazio di lavoro è stato correttamente migrato, puoi controllare la sua versione nel database nella tabella `core.workspace`. - -Dovrebbe sempre essere nell'ambito della versione `major.minor` corrente della tua istanza di Twenty, puoi visualizzare la tua versione nell'admin panel (su `/settings/admin-panel`, accessibile se il tuo utente ha impostata a vero la proprietà `canAccessFullAdminPanel` nel database) o eseguendo `echo $APP_VERSION` all'interno del tuo contenitore `twenty-server`. - -Per correggere una versione di spazio di lavoro desincronizzata, dovrai aggiornare dalla versione corrispondente di Twenty seguendo la guida all'aggiornamento relativa in modo sequenziale fino a raggiungere la versione desiderata. - -#### Rimozione di `auditLog` - -Abbiamo rimosso l'oggetto standard auditLog, il che significa che la dimensione del tuo backup potrebbe ridursi significativamente dopo questa migrazione. - -### v0.51 a v0.52 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.52. - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### Ho uno spazio di lavoro bloccato in una versione tra `0.52.0` e `0.52.6`. - -Purtroppo `0.52.0` e `0.52.6` sono stati completamente rimossi da dockerHub. -Dovrai aggiornare manualmente nel database la versione del workspace a `0.51.0` ed effettuare l'aggiornamento utilizzando la versione `0.52.11` di Twenty seguendo la guida di aggiornamento appena sopra. - -### v0.50 a v0.51 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.51. - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.44.0 a v0.50.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.50.0. - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### Mutazione di docker-compose.yml - -Questa versione include una mutazione di `docker-compose.yml` per dare al servizio `worker` accesso al volume `server-local-data`. -Aggiorna il tuo `docker-compose.yml` locale con [v0.50.0 docker-compose.yml](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml) - -### v0.43.0 a v0.44.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.44.0. - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.42.0 a v0.43.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.43.0. - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -In questa versione, abbiamo anche passato all'immagine postgres:16 in docker-compose.yml. - -#### (Opzione 1) Migrazione del database - -Mantenere l'immagine postgres-spilo esistente va bene, ma dovrai bloccare la versione nel tuo docker-compose.yml a 0.43.0. - -#### (Opzione 2) Migrazione del database - -Se desideri migrare il tuo database alla nuova immagine postgres:16, segui questi passaggi: - -1. Scarica il tuo database dal vecchio contenitore postgres-spilo. - -``` -docker exec -it twenty-db-1 sh -pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql -exit -docker cp twenty-db-1:/home/postgres/databases_backup.sql . -``` - -Assicurati che il tuo file di dump non sia vuoto. - -2. Aggiorna il tuo docker-compose.yml per utilizzare l'immagine postgres:16 come nel file [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) - -3. Ripristinare il database nel nuovo contenitore postgres:16. - -``` -docker cp databases_backup.sql twenty-db-1:/databases_backup.sql -docker exec -it twenty-db-1 sh -psql -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} -f databases_backup.sql -exit -``` - -### v0.41.0 a v0.42.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.42.0. - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.42 -``` - -**Variabili di ambiente** - -* Rimosso: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT` -* Aggiunto: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED` - -### v0.40.0 a v0.41.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.41.0. - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.41 -``` - -**Variabili di ambiente** - -* Rimosso: `AUTH_MICROSOFT_TENANT_ID` - -### v0.35.0 a v0.40.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.40.0. - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.40 -``` - -**Variabili di ambiente** - -* Aggiunto: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL` - -### v0.34.0 a v0.35.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.35.0. - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.35 -``` - -Il comando `yarn database:migrate:prod` applicherà le migrazioni alla struttura del database (schemi core e metadata) -Il comando `yarn command:prod upgrade-0.35` si occupa della migrazione dei dati di tutti gli spazi di lavoro. - -**Variabili di ambiente** - -* Abbiamo sostituito `ENABLE_DB_MIGRATIONS` con `DISABLE_DB_MIGRATIONS` (il valore predefinito ora è `false`, probabilmente non hai bisogno di impostare nulla) - -### v0.33.0 a v0.34.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.34.0. - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.34 -``` - -Il comando `yarn database:migrate:prod` applicherà le migrazioni alla struttura del database (schemi core e metadata) -Il comando `yarn command:prod upgrade-0.34` si occupa della migrazione dei dati di tutti gli spazi di lavoro. - -**Variabili di ambiente** - -* Rimosso: `FRONT_BASE_URL` -* Aggiunto: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT` - -Abbiamo aggiornato il modo in cui gestiamo l'URL del frontend. -Ora puoi impostare l'URL del frontend utilizzando le variabili `FRONT_DOMAIN`, `FRONT_PROTOCOL` e `FRONT_PORT`. -Se FRONT_DOMAIN non è impostato, l'URL del frontend verrà impostato su `SERVER_URL`. - -### v0.32.0 a v0.33.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.33.0. - -``` -yarn command:prod cache:flush -yarn database:migrate:prod -yarn command:prod upgrade-0.33 -``` - -Il comando `yarn command:prod cache:flush` svuoterà la cache di Redis. -Il comando `yarn database:migrate:prod` applicherà le migrazioni alla struttura del database (schemi core e metadata) -Il comando `yarn command:prod upgrade-0.33` si occupa della migrazione dei dati di tutti gli spazi di lavoro. - -A partire da questa versione, l'immagine di twenty-postgres per il database è diventata deprecata e si usa twenty-postgres-spilo. -Se vuoi continuare ad usare l'immagine di twenty-postgres, semplicemente sostituisci `twentycrm/twenty-postgres:${TAG}` con `twentycrm/twenty-postgres` in docker-compose.yml. - -### v0.31.0 a v0.32.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.32.0. - -**Migrazione di schema e dati** - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.32 -``` - -Il comando `yarn database:migrate:prod` applicherà le migrazioni alla struttura del database (schemi core e metadata) -Il comando `yarn command:prod upgrade-0.32` si occupa della migrazione dei dati di tutti gli spazi di lavoro. - -**Variabili di ambiente** - -Abbiamo aggiornato il modo in cui gestiamo la connessione Redis. - -* Rimosso: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD` -* Aggiunto: `REDIS_URL` - -Aggiorna il tuo file `.env` per utilizzare la nuova variabile `REDIS_URL` al posto dei singoli parametri di connessione Redis. - -Abbiamo anche semplificato il modo in cui gestiamo i token JWT. - -* Rimosso: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET` -* Aggiunto: `APP_SECRET` - -Aggiorna il tuo file `.env` per utilizzare la nuova variabile `APP_SECRET` al posto dei singoli segreti dei token (puoi usare lo stesso segreto di prima o generare una nuova stringa casuale) - -**Account collegato** - -Se stai usando un account collegato per sincronizzare le tue email e calendari di Google, dovrai attivare l'[API delle Persone](https://developers.google.com/people) sul tuo console amministrativa di Google. - -### v0.30.0 a v0.31.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.31.0. - -**Migrazione di schema e dati**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.31 -``` - -Il comando `yarn database:migrate:prod` applicherà le migrazioni alla struttura del database (schemi core e metadata) -Il comando `yarn command:prod upgrade-0.31` si occupa della migrazione dei dati di tutti gli spazi di lavoro. - -### v0.24.0 a v0.30.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.30.0. - -**Modifica importante**: -Per migliorare le prestazioni, Twenty ora richiede che la cache Redis sia configurata. Abbiamo aggiornato il nostro [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) per riflettere questa modifica. -Assicurati di aggiornare la tua configurazione e le tue variabili di ambiente di conseguenza: - -``` -REDIS_HOST={your-redis-host} -REDIS_PORT={your-redis-port} -CACHE_STORAGE_TYPE=redis -``` - -**Migrazione di schema e dati**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.30 -``` - -Il comando `yarn database:migrate:prod` applicherà le migrazioni alla struttura del database (schemi core e metadata) -Il comando `yarn command:prod upgrade-0.30` si occupa della migrazione dei dati di tutti gli spazi di lavoro. - -### v0.23.0 a v0.24.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.24.0. - -Esegui i seguenti comandi: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.24 -``` - -Il comando `yarn database:migrate:prod` applicherà le migrazioni alla struttura del database (schemi core e metadata) -Il comando `yarn command:prod upgrade-0.24` si occupa della migrazione dei dati di tutti gli spazi di lavoro. - -### v0.22.0 a v0.23.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.23.0. - -Esegui i seguenti comandi: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.23 -``` - -Il comando `yarn database:migrate:prod` applicherà le migrazioni al database. -Il comando `yarn command:prod upgrade-0.23` si occupa della migrazione dei dati, includendo il trasferimento delle attività a compiti/note. - -### v0.21.0 a v0.22.0 - -Aggiorna la tua istanza di Twenty per utilizzare l'immagine v0.22.0. - -Esegui i seguenti comandi: - -``` -yarn database:migrate:prod -yarn command:prod workspace:sync-metadata -f -yarn command:prod upgrade-0.22 -``` - -Il comando `yarn database:migrate:prod` applicherà le migrazioni al database. -Il comando `yarn command:prod workspace:sync-metadata -f` sincronizzerà la definizione degli oggetti standard nelle tabelle dei metadati e applicherà le migrazioni necessarie agli spazi di lavoro esistenti. -Il comando `yarn command:prod upgrade-0.22` applicherà specifiche trasformazioni dei dati per adattarsi alle nuove opzioni di strumentazione richiesta predefinita dell'oggetto. +Make sure to go through every major tagged version when upgrading (upgrade v1.6.x to v.7.y, then v.7.y to v.8.z, etc.). diff --git a/packages/twenty-docs/l/it/navigation.json b/packages/twenty-docs/l/it/navigation.json index b000b8ee828..e07e5ea11b3 100644 --- a/packages/twenty-docs/l/it/navigation.json +++ b/packages/twenty-docs/l/it/navigation.json @@ -1,24 +1,27 @@ { "tabs": { + "gettingStarted": { + "label": "Per iniziare", + "groups": { + "welcome": { + "label": "Welcome" + }, + "coreConcepts": { + "label": "Core Concepts" + } + } + }, "userGuide": { "label": "Guida utente", "groups": { - "discoverTwenty": { - "label": "Scopri Twenty", - "groups": { - "gettingStartedCapabilities": { - "label": "Funzionalità" - }, - "gettingStartedHowTos": { - "label": "Guide pratiche" - } - } + "userGuideOverview": { + "label": "Panoramica" }, "dataModel": { "label": "Modello dati", "groups": { - "dataModelCapabilities": { - "label": "Funzionalità" + "dataModelReference": { + "label": "Reference" }, "dataModelHowTos": { "label": "Guide pratiche" @@ -28,8 +31,8 @@ "dataMigration": { "label": "Migrazione dei dati", "groups": { - "dataMigrationCapabilities": { - "label": "Funzionalità" + "dataMigrationReference": { + "label": "Reference" }, "dataMigrationHowTos": { "label": "Guide pratiche" @@ -39,8 +42,8 @@ "calendarEmails": { "label": "Calendario & Email", "groups": { - "calendarEmailsCapabilities": { - "label": "Funzionalità" + "calendarEmailsReference": { + "label": "Reference" }, "calendarEmailsHowTos": { "label": "Guide pratiche" @@ -50,8 +53,8 @@ "workflows": { "label": "Flussi di Lavoro", "groups": { - "workflowsCapabilities": { - "label": "Funzionalità" + "workflowsReference": { + "label": "Reference" }, "workflowsHowTos": { "label": "Guide pratiche", @@ -75,21 +78,26 @@ "ai": { "label": "AI", "groups": { - "aiCapabilities": { - "label": "Funzionalità" + "aiReference": { + "label": "Reference" }, "aiHowTos": { "label": "Guide pratiche" } } }, - "viewsPipelines": { - "label": "Viste & Pipeline", + "layout": { + "label": "Disposizione", "groups": { - "viewsPipelinesCapabilities": { - "label": "Funzionalità" + "layoutReference": { + "label": "Reference", + "groups": { + "layoutViews": { + "label": "Viste" + } + } }, - "viewsPipelinesHowTos": { + "layoutHowTos": { "label": "Guide pratiche" } } @@ -97,8 +105,8 @@ "dashboards": { "label": "Cruscotti", "groups": { - "dashboardsCapabilities": { - "label": "Funzionalità" + "dashboardsReference": { + "label": "Reference" }, "dashboardsHowTos": { "label": "Guide pratiche" @@ -108,8 +116,8 @@ "permissionsAccess": { "label": "Autorizzazioni & Accesso", "groups": { - "permissionsAccessCapabilities": { - "label": "Funzionalità" + "permissionsAccessReference": { + "label": "Reference" }, "permissionsAccessHowTos": { "label": "Guide pratiche" @@ -119,8 +127,8 @@ "billing": { "label": "Fatturazione", "groups": { - "billingCapabilities": { - "label": "Funzionalità" + "billingReference": { + "label": "Reference" }, "billingHowTos": { "label": "Guide pratiche" @@ -130,8 +138,8 @@ "settings": { "label": "Impostazioni", "groups": { - "settingsCapabilities": { - "label": "Funzionalità" + "settingsReference": { + "label": "Reference" }, "settingsHowTos": { "label": "Guide pratiche" @@ -143,59 +151,20 @@ "developers": { "label": "Sviluppatori", "groups": { - "developersGroup": { - "label": "Sviluppatori" + "developersOverview": { + "label": "Panoramica" }, - "extend": { - "label": "Estendi", - "groups": { - "apps": { - "label": "App" - } - } + "apps": { + "label": "App" + }, + "api": { + "label": "API" }, "selfHost": { - "label": "Auto-ospitazione", - "groups": { - "selfHostCapabilities": { - "label": "Funzionalità" - } - } + "label": "Auto-ospitazione" }, "contribute": { - "label": "Contribuire", - "groups": { - "contributeCapabilities": { - "label": "Funzionalità", - "groups": { - "frontendDevelopment": { - "label": "Sviluppo Frontend", - "groups": { - "twentyUi": { - "label": "Twenty UI", - "groups": { - "display": { - "label": "Mostra" - }, - "feedback": { - "label": "Feedback" - }, - "input": { - "label": "Input" - }, - "navigation": { - "label": "Navigazione" - } - } - } - } - }, - "backendDevelopment": { - "label": "Sviluppo Backend" - } - } - } - } + "label": "Contribuire" } } } diff --git a/packages/twenty-docs/l/it/twenty-ui/display/app-tooltip.mdx b/packages/twenty-docs/l/it/twenty-ui/display/app-tooltip.mdx index 49d341042f4..1af63ac504c 100644 --- a/packages/twenty-docs/l/it/twenty-ui/display/app-tooltip.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/display/app-tooltip.mdx @@ -1,5 +1,6 @@ --- title: Tooltip dell'app +icon: messaggio --- diff --git a/packages/twenty-docs/l/it/twenty-ui/display/checkmark.mdx b/packages/twenty-docs/l/it/twenty-ui/display/checkmark.mdx index c80a6ff941e..a061b5e58e0 100644 --- a/packages/twenty-docs/l/it/twenty-ui/display/checkmark.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/display/checkmark.mdx @@ -1,5 +1,6 @@ --- title: Segno di spunta +icon: circle-check --- diff --git a/packages/twenty-docs/l/it/twenty-ui/display/icons.mdx b/packages/twenty-docs/l/it/twenty-ui/display/icons.mdx index 4784d5167ec..6b3aa245b46 100644 --- a/packages/twenty-docs/l/it/twenty-ui/display/icons.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/display/icons.mdx @@ -1,5 +1,6 @@ --- title: Icone +icon: icone --- diff --git a/packages/twenty-docs/l/it/twenty-ui/display/soon-pill.mdx b/packages/twenty-docs/l/it/twenty-ui/display/soon-pill.mdx index 1c952904cd9..85552681960 100644 --- a/packages/twenty-docs/l/it/twenty-ui/display/soon-pill.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/display/soon-pill.mdx @@ -2,7 +2,6 @@ title: Soon Pill --- - Un piccolo badge o "pill" per indicare che qualcosa è in arrivo a breve. ```jsx diff --git a/packages/twenty-docs/l/it/twenty-ui/display/tag.mdx b/packages/twenty-docs/l/it/twenty-ui/display/tag.mdx index 251476c1e6c..24dae5f321a 100644 --- a/packages/twenty-docs/l/it/twenty-ui/display/tag.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/display/tag.mdx @@ -1,8 +1,8 @@ --- title: Etichetta +icon: etichetta --- - Componente per categorizzare o etichettare visivamente i contenuti. diff --git a/packages/twenty-docs/l/it/twenty-ui/input/buttons.mdx b/packages/twenty-docs/l/it/twenty-ui/input/buttons.mdx index ff8f2c0ab0d..594dfc2c3ed 100644 --- a/packages/twenty-docs/l/it/twenty-ui/input/buttons.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/input/buttons.mdx @@ -1,5 +1,6 @@ --- title: Pulsanti +icon: hand-pointer --- diff --git a/packages/twenty-docs/l/it/twenty-ui/input/checkbox.mdx b/packages/twenty-docs/l/it/twenty-ui/input/checkbox.mdx index bc40efaa7ce..6d17b5a7a39 100644 --- a/packages/twenty-docs/l/it/twenty-ui/input/checkbox.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/input/checkbox.mdx @@ -1,5 +1,6 @@ --- title: Checkbox +icon: square-check --- diff --git a/packages/twenty-docs/l/it/twenty-ui/input/color-scheme.mdx b/packages/twenty-docs/l/it/twenty-ui/input/color-scheme.mdx index bcddce5f091..95e4572d62a 100644 --- a/packages/twenty-docs/l/it/twenty-ui/input/color-scheme.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/input/color-scheme.mdx @@ -1,5 +1,6 @@ --- title: Schema di colori +icon: tavolozza --- diff --git a/packages/twenty-docs/l/it/twenty-ui/input/radio.mdx b/packages/twenty-docs/l/it/twenty-ui/input/radio.mdx index 752a83a2391..ced29f14c07 100644 --- a/packages/twenty-docs/l/it/twenty-ui/input/radio.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/input/radio.mdx @@ -1,5 +1,6 @@ --- title: Radio +icon: circle-dot --- diff --git a/packages/twenty-docs/l/it/twenty-ui/input/toggle.mdx b/packages/twenty-docs/l/it/twenty-ui/input/toggle.mdx index 5cd57d66f66..244f1c8dd1d 100644 --- a/packages/twenty-docs/l/it/twenty-ui/input/toggle.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/input/toggle.mdx @@ -1,8 +1,8 @@ --- title: Attiva/Disattiva +icon: toggle-on --- - diff --git a/packages/twenty-docs/l/it/twenty-ui/introduction.mdx b/packages/twenty-docs/l/it/twenty-ui/introduction.mdx index 93829a6f077..d89ebe5ebac 100644 --- a/packages/twenty-docs/l/it/twenty-ui/introduction.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/introduction.mdx @@ -1,5 +1,6 @@ --- title: Panoramica +icon: tavolozza description: Libreria di componenti per Twenty CRM --- diff --git a/packages/twenty-docs/l/it/twenty-ui/navigation.mdx b/packages/twenty-docs/l/it/twenty-ui/navigation.mdx index dbc2b299549..074ee82b864 100644 --- a/packages/twenty-docs/l/it/twenty-ui/navigation.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/navigation.mdx @@ -1,5 +1,6 @@ --- title: Navigazione +icon: compass --- diff --git a/packages/twenty-docs/l/it/twenty-ui/navigation/links.mdx b/packages/twenty-docs/l/it/twenty-ui/navigation/links.mdx index 02e8b552cf5..4d6ffdc7176 100644 --- a/packages/twenty-docs/l/it/twenty-ui/navigation/links.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/navigation/links.mdx @@ -1,5 +1,6 @@ --- title: Collegamenti +icon: collegamento --- diff --git a/packages/twenty-docs/l/it/twenty-ui/navigation/menu-item.mdx b/packages/twenty-docs/l/it/twenty-ui/navigation/menu-item.mdx index 8cf01548b81..8a8698491c4 100644 --- a/packages/twenty-docs/l/it/twenty-ui/navigation/menu-item.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/navigation/menu-item.mdx @@ -1,8 +1,8 @@ --- title: Voce di menu +icon: bars --- - Una voce di menu versatile progettata per essere utilizzata in un menu o in un elenco di navigazione. diff --git a/packages/twenty-docs/l/it/twenty-ui/navigation/navigation-bar.mdx b/packages/twenty-docs/l/it/twenty-ui/navigation/navigation-bar.mdx index f652e6cbeef..ed0fe3bf55a 100644 --- a/packages/twenty-docs/l/it/twenty-ui/navigation/navigation-bar.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/navigation/navigation-bar.mdx @@ -1,8 +1,8 @@ --- title: Barra di navigazione +icon: bars --- - Rende una barra di navigazione che contiene più componenti `NavigationBarItem`. diff --git a/packages/twenty-docs/l/it/twenty-ui/progress-bar.mdx b/packages/twenty-docs/l/it/twenty-ui/progress-bar.mdx index 54c985a4906..e7ef06b6bc1 100644 --- a/packages/twenty-docs/l/it/twenty-ui/progress-bar.mdx +++ b/packages/twenty-docs/l/it/twenty-ui/progress-bar.mdx @@ -2,7 +2,6 @@ title: Feedback --- - Indica progresso o conto alla rovescia e si muove da destra a sinistra. diff --git a/packages/twenty-docs/l/it/user-guide/billing/overview.mdx b/packages/twenty-docs/l/it/user-guide/billing/overview.mdx index 456cc57a60b..7a431d5007b 100644 --- a/packages/twenty-docs/l/it/user-guide/billing/overview.mdx +++ b/packages/twenty-docs/l/it/user-guide/billing/overview.mdx @@ -3,7 +3,6 @@ title: Fatturazione description: Comprendi i prezzi di Twenty e gestisci il tuo abbonamento. --- - Twenty offre piani tariffari flessibili per soddisfare le esigenze del tuo team. Gestisci il tuo abbonamento, tieni traccia dei crediti dei flussi di lavoro e accedi alle fatture direttamente da **Impostazioni → Fatturazione**. ## Cosa c'è in questa sezione diff --git a/packages/twenty-docs/l/it/user-guide/calendar-emails/overview.mdx b/packages/twenty-docs/l/it/user-guide/calendar-emails/overview.mdx index e5bfad418a0..a136abf1523 100644 --- a/packages/twenty-docs/l/it/user-guide/calendar-emails/overview.mdx +++ b/packages/twenty-docs/l/it/user-guide/calendar-emails/overview.mdx @@ -3,7 +3,6 @@ title: Calendario & Email description: Collega i tuoi account email e calendario a Twenty. --- - ## Opzioni di Connessione ### Account Google (Gmail & Google Calendar) diff --git a/packages/twenty-docs/l/it/user-guide/dashboards/overview.mdx b/packages/twenty-docs/l/it/user-guide/dashboards/overview.mdx index 3f7fb3087e7..67086f162f5 100644 --- a/packages/twenty-docs/l/it/user-guide/dashboards/overview.mdx +++ b/packages/twenty-docs/l/it/user-guide/dashboards/overview.mdx @@ -3,7 +3,6 @@ title: Cruscotti description: Scopri le basi della reportistica e delle dashboard in Twenty. --- - Le dashboard sono attualmente in beta. Attivale in **Impostazioni → Aggiornamenti → Accesso anticipato**. diff --git a/packages/twenty-docs/l/it/user-guide/data-migration/overview.mdx b/packages/twenty-docs/l/it/user-guide/data-migration/overview.mdx index 26ab6479d6d..e0bb2014e80 100644 --- a/packages/twenty-docs/l/it/user-guide/data-migration/overview.mdx +++ b/packages/twenty-docs/l/it/user-guide/data-migration/overview.mdx @@ -5,7 +5,6 @@ description: Importa ed esporta i dati del tuo CRM tramite file CSV o API. import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## Metodi di importazione Twenty supporta due metodi principali per importare dati: diff --git a/packages/twenty-docs/l/it/user-guide/data-model/overview.mdx b/packages/twenty-docs/l/it/user-guide/data-model/overview.mdx index b6fc8e7c8f5..c97d1ab3191 100644 --- a/packages/twenty-docs/l/it/user-guide/data-model/overview.mdx +++ b/packages/twenty-docs/l/it/user-guide/data-model/overview.mdx @@ -3,7 +3,6 @@ title: Modello dati description: Scopri cos'è un modello di dati e come progettarne uno che si adatti alla tua azienda. --- - ## Cos'è un modello di dati? Un modello di dati è la struttura che definisce come le informazioni sono organizzate nel tuo CRM. Consideralo come il **progetto** dei dati dei tuoi clienti — lo progetti una volta, poi lo popoli con i tuoi dati reali. diff --git a/packages/twenty-docs/l/it/user-guide/introduction.mdx b/packages/twenty-docs/l/it/user-guide/introduction.mdx index 2ff1a3cde74..032f876830f 100644 --- a/packages/twenty-docs/l/it/user-guide/introduction.mdx +++ b/packages/twenty-docs/l/it/user-guide/introduction.mdx @@ -1,16 +1,11 @@ --- -title: Scopri Twenty +title: Guida utente description: Benvenuto nella Guida utente di Twenty, la tua risorsa per configurazioni avanzate e migliori pratiche. --- import { CardTitle } from "/snippets/card-title.mdx" - - Scopri Twenty - Scopri cos'è Twenty e come può aiutare la tua azienda. - - Modello di dati Personalizza il tuo modello di dati per adattarlo ai tuoi processi aziendali. @@ -36,9 +31,9 @@ import { CardTitle } from "/snippets/card-title.mdx" Potenzia il tuo team con agenti IA. - - Viste e pipeline - Organizza i tuoi dati con viste e pipeline operative. + + Layout + Navigation, views, and record page customization. diff --git a/packages/twenty-docs/l/it/user-guide/layout/capabilities/navigation.mdx b/packages/twenty-docs/l/it/user-guide/layout/capabilities/navigation.mdx new file mode 100644 index 00000000000..0b4d2291c0c --- /dev/null +++ b/packages/twenty-docs/l/it/user-guide/layout/capabilities/navigation.mdx @@ -0,0 +1,32 @@ +--- +title: Navigazione +description: Customize the left sidebar to match how your team works. +--- + +The left sidebar is your primary way to move around Twenty. It's fully customizable — you can reorganize it to match your workflow without touching any settings page. + +## Reordering items + +Drag and drop any item in the sidebar to change its position. The order is saved per user, so each team member can arrange their own sidebar. + +## Cartelle + +Group related items into folders. For example, you might create a "Sales" folder containing your pipeline views, a "Support" folder for tickets, or an "Operations" folder for internal objects. + +To create a folder, right-click in the sidebar or use the `+` button. + +## Hiding objects + +Objects you don't use can be hidden from the sidebar. They're not deleted — they're just out of the way. You can show them again anytime from Settings > Data Model. + +## Preferiti + +Pin views, records, or searches to the Favorites section at the top of the sidebar for one-click access. Favorites are personal — each user manages their own. + +## Custom links + +Add links to external tools directly in the sidebar. Useful for linking to your wiki, dashboards in other tools, or any URL your team uses regularly. + +## Command menu + +Press `Cmd+K` (or `Ctrl+K`) to open the command menu — a quick-access search bar for jumping to any record, view, or action without navigating the sidebar. diff --git a/packages/twenty-docs/l/it/user-guide/layout/capabilities/record-pages.mdx b/packages/twenty-docs/l/it/user-guide/layout/capabilities/record-pages.mdx new file mode 100644 index 00000000000..3c436c9eff9 --- /dev/null +++ b/packages/twenty-docs/l/it/user-guide/layout/capabilities/record-pages.mdx @@ -0,0 +1,51 @@ +--- +title: Pagine dei record +description: Personalizza il layout delle singole pagine di dettaglio dei record con schede e widget. +--- + +Quando apri un record in Twenty, la pagina di dettaglio è composta da **schede** e **widget**. Entrambi sono completamente personalizzabili per ciascun tipo di oggetto. + +## Schede + +Ogni pagina del record può avere più schede — simili alle schede di un browser. Utilizzale per organizzare i diversi aspetti di un record. Ad esempio, un record Azienda potrebbe avere schede per Panoramica, Comunicazione, Attività e File. + +Puoi: + +* Aggiungi e rimuovi schede +* Rinomina le schede +* Riordina le schede trascinandole +* Imposta quale scheda viene mostrata per impostazione predefinita + +## Widget + +I widget sono gli elementi costitutivi all'interno di ogni scheda. Tipi di widget disponibili: + +| Widget | Cosa mostra | +| ---------------------- | ------------------------------------------------- | +| **Campi** | Campi del record, raggruppati o singolarmente | +| **Record correlati** | Tabella di record collegati tramite una relazione | +| **Email** | Cronologia delle email dagli account collegati | +| **Calendario** | Eventi di calendario associati al record | +| **Sequenza temporale** | Cronologia di attività ed eventi | +| **Attività** | Attività associate | +| **Note** | Note in testo formattato | +| **File** | File allegati | +| **Grafici** | Dati visivi provenienti da record correlati | +| **iFrame** | Contenuto esterno incorporato | +| **Testo formattato** | Contenuto statico o descrizioni | + +## Personalizzazione di una pagina del record + +1. Apri un record qualsiasi +2. Premi `Cmd+K` e cerca "Modifica il layout della pagina del record" +3. Ora sei in modalità di personalizzazione: + * **Aggiungi widget** dal selettore di widget + * **Trascina i widget** per riposizionarli sulla griglia + * **Ridimensiona i widget** trascinando i bordi + * **Configura i campi** mostrati all'interno di ciascun widget + * **Gestisci le schede** — aggiungi, rimuovi, rinomina, riordina +4. Salva le modifiche — si applicano a tutti i record di quel tipo di oggetto + +## Visibilità dei campi + +All'interno di un widget Campi, puoi controllare quali campi sono visibili e in quale ordine. Questo ti consente di creare layout mirati — ad esempio, mostrando solo i campi più importanti nella scheda Panoramica e inserendo i campi dettagliati in una scheda separata. diff --git a/packages/twenty-docs/l/it/user-guide/layout/overview.mdx b/packages/twenty-docs/l/it/user-guide/layout/overview.mdx new file mode 100644 index 00000000000..05ccc3b97a2 --- /dev/null +++ b/packages/twenty-docs/l/it/user-guide/layout/overview.mdx @@ -0,0 +1,45 @@ +--- +title: Disposizione +description: Customize how you navigate, browse, and view records in Twenty. +--- + +Twenty's layout is customizable at three levels: how you navigate the app, how you browse lists of records, and what you see when you open an individual record. + +## Navigazione + +The left sidebar is fully customizable. Puoi: + +* **Reorder items** by dragging and dropping +* **Create folders** to group related objects and views +* **Hide objects** you don't use +* **Add custom links** to external tools +* **Pin favorites** for quick access to views, records, or searches + +[Navigation reference →](/l/it/user-guide/layout/capabilities/navigation) + +## Viste + +Views control how lists of records are displayed. Twenty supports three view types: + +| Vista | Best for | +| ------------ | ---------------------------------------------------------------------- | +| **Table** | Working with many records at once — spreadsheet-style rows and columns | +| **Kanban** | Pipeline tracking — drag-and-drop cards organized by stage | +| **Calendar** | Time-based planning — records plotted by a date field | + +Each view saves its own filters, sorting, field visibility, and grouping configuration. Views can be shared with the workspace or kept private. + +[Table views →](/l/it/user-guide/views-pipelines/capabilities/table-views) · [Kanban views →](/l/it/user-guide/views-pipelines/capabilities/kanban-views) · [Calendar view →](/l/it/user-guide/views-pipelines/capabilities/calendar-view) + +## Record pages + +When you open a record, the detail page is built from configurable tabs and widgets. Puoi: + +* **Add, remove, and reorder tabs** on any record type +* **Configure widgets** — fields, related records, emails, timeline, calendar, tasks, notes, files, charts, iframes, and more +* **Drag and resize widgets** on a grid layout +* **Control field visibility** per widget + +Enter layout customization mode from the command menu (`Cmd+K` → "Edit record page layout"). + +[Record pages reference →](/l/it/user-guide/layout/capabilities/record-pages) diff --git a/packages/twenty-docs/l/it/user-guide/permissions-access/overview.mdx b/packages/twenty-docs/l/it/user-guide/permissions-access/overview.mdx index 48cbbe89a11..825063448ef 100644 --- a/packages/twenty-docs/l/it/user-guide/permissions-access/overview.mdx +++ b/packages/twenty-docs/l/it/user-guide/permissions-access/overview.mdx @@ -3,7 +3,6 @@ title: Permissions & Access description: Gestisci ruoli, permessi e il controllo degli accessi nel tuo spazio di lavoro. --- - Il sistema di permessi di Twenty ti consente di controllare chi può accedere e modificare i dati nel tuo spazio di lavoro. Crea ruoli, assegna permessi e configura SSO per un accesso sicuro. ## Cosa c'è in questa sezione diff --git a/packages/twenty-docs/l/it/user-guide/settings/overview.mdx b/packages/twenty-docs/l/it/user-guide/settings/overview.mdx index f23d12dff7d..7bb727059ec 100644 --- a/packages/twenty-docs/l/it/user-guide/settings/overview.mdx +++ b/packages/twenty-docs/l/it/user-guide/settings/overview.mdx @@ -3,7 +3,6 @@ title: Impostazioni description: Configura il tuo spazio di lavoro Twenty con le configurazioni essenziali. --- - ## Configurazione iniziale Quando crei per la prima volta il tuo spazio di lavoro, ci sono diverse impostazioni chiave da configurare. diff --git a/packages/twenty-docs/l/it/user-guide/views-pipelines/overview.mdx b/packages/twenty-docs/l/it/user-guide/views-pipelines/overview.mdx index 2e4126ef23e..d6a470d96f5 100644 --- a/packages/twenty-docs/l/it/user-guide/views-pipelines/overview.mdx +++ b/packages/twenty-docs/l/it/user-guide/views-pipelines/overview.mdx @@ -5,7 +5,6 @@ description: Scopri come creare e gestire le viste in Twenty. import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## Comprendere le viste Le viste sono configurazioni salvate che determinano come vengono visualizzati i tuoi dati. Ogni vista può avere: diff --git a/packages/twenty-docs/l/it/user-guide/workflows/overview.mdx b/packages/twenty-docs/l/it/user-guide/workflows/overview.mdx index 7256e981133..6d3dfdda4b7 100644 --- a/packages/twenty-docs/l/it/user-guide/workflows/overview.mdx +++ b/packages/twenty-docs/l/it/user-guide/workflows/overview.mdx @@ -3,7 +3,6 @@ title: Flussi di Lavoro description: Scopri come creare automazioni in Twenty. --- - ## Perché i Workflow sono importanti Twenty è stato creato per offrire la massima flessibilità ai suoi utenti. Piuttosto che costringerti ad adattare i tuoi processi aziendali a funzionalità rigide e predefinite, i workflow ti consentono di creare automazioni che creano il CRM che meglio supporta i tuoi casi d'uso aziendali unici. diff --git a/packages/twenty-docs/l/pt/developers/contribute/capabilities/backend-development/server-commands.mdx b/packages/twenty-docs/l/pt/developers/contribute/capabilities/backend-development/server-commands.mdx index 8a78ced5de8..601d7168521 100644 --- a/packages/twenty-docs/l/pt/developers/contribute/capabilities/backend-development/server-commands.mdx +++ b/packages/twenty-docs/l/pt/developers/contribute/capabilities/backend-development/server-commands.mdx @@ -1,5 +1,6 @@ --- title: Comandos de Backend +icon: terminal --- ## Comandos Úteis diff --git a/packages/twenty-docs/l/pt/developers/contribute/capabilities/bug-and-requests.mdx b/packages/twenty-docs/l/pt/developers/contribute/capabilities/bug-and-requests.mdx index c8b8d4f27d7..d8a2fac2941 100644 --- a/packages/twenty-docs/l/pt/developers/contribute/capabilities/bug-and-requests.mdx +++ b/packages/twenty-docs/l/pt/developers/contribute/capabilities/bug-and-requests.mdx @@ -1,5 +1,6 @@ --- title: Bugs, solicitações e Pull Requests +icon: bug info: Reporte issues, solicite funcionalidades e contribua com código --- diff --git a/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/best-practices-front.mdx b/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/best-practices-front.mdx index 14c7094c503..5c107b7f88b 100644 --- a/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/best-practices-front.mdx +++ b/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/best-practices-front.mdx @@ -1,5 +1,6 @@ --- title: Melhores Práticas +icon: star --- Este documento descreve as melhores práticas que você deve seguir ao trabalhar no frontend. diff --git a/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx b/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx index b41c212dd7d..a55c2b85e6c 100644 --- a/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx +++ b/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx @@ -1,5 +1,6 @@ --- title: Arquitetura de Pastas +icon: folder-tree info: Um olhar detalhado sobre nossa arquitetura de pastas --- diff --git a/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/frontend-commands.mdx b/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/frontend-commands.mdx index 31c274ae2b0..5860b3948ed 100644 --- a/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/frontend-commands.mdx +++ b/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/frontend-commands.mdx @@ -1,5 +1,6 @@ --- title: Comandos do Frontend +icon: terminal --- ## Comandos Úteis diff --git a/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/style-guide.mdx b/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/style-guide.mdx index 9b935516c2c..211d536781b 100644 --- a/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/style-guide.mdx +++ b/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/style-guide.mdx @@ -1,5 +1,6 @@ --- title: Guia de Estilo +icon: paintbrush --- Este documento inclui as regras a seguir ao escrever código. diff --git a/packages/twenty-docs/l/pt/developers/contribute/capabilities/local-setup.mdx b/packages/twenty-docs/l/pt/developers/contribute/capabilities/local-setup.mdx index 958cbed5387..0e57a8b7cf3 100644 --- a/packages/twenty-docs/l/pt/developers/contribute/capabilities/local-setup.mdx +++ b/packages/twenty-docs/l/pt/developers/contribute/capabilities/local-setup.mdx @@ -1,5 +1,6 @@ --- title: Configuração Local +icon: laptop-code description: O guia para contribuidores (ou desenvolvedores curiosos) que desejam executar o Twenty localmente. --- diff --git a/packages/twenty-docs/l/pt/developers/contribute/commands.mdx b/packages/twenty-docs/l/pt/developers/contribute/commands.mdx new file mode 100644 index 00000000000..5c34531484f --- /dev/null +++ b/packages/twenty-docs/l/pt/developers/contribute/commands.mdx @@ -0,0 +1,77 @@ +--- +title: Commands +icon: terminal +description: Useful commands for developing Twenty. +--- + +Commands can be run from the repository root using `npx nx`. Use `npx nx run {project}:{command}` for explicit targeting. + +## Starting the App + +```bash +npx nx start twenty-front # Frontend dev server (http://localhost:3001) +npx nx start twenty-server # Backend server (http://localhost:3000) +npx nx run twenty-server:worker # Background worker +``` + +## Database + +```bash +npx nx database:reset twenty-server # Reset and seed database +npx nx run twenty-server:database:migrate:prod # Run migrations +npx nx run twenty-server:database:migrate:generate --name --type # Generate a migration +``` + +## Linting + +```bash +npx nx lint:diff-with-main twenty-front # Lint changed files (fastest) +npx nx lint:diff-with-main twenty-server +npx nx lint twenty-front --configuration=fix # Auto-fix +``` + +## Type Checking + +```bash +npx nx typecheck twenty-front +npx nx typecheck twenty-server +``` + +## Testes + +```bash +# Frontend +npx nx test twenty-front # Jest unit tests +npx nx storybook:build twenty-front # Build Storybook +npx nx storybook:test twenty-front # Storybook tests + +# Backend +npx nx run twenty-server:test:unit # Unit tests +npx nx run twenty-server:test:integration # Integration tests +npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset + +# Single file (fastest) +npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs +``` + +## GraphQL + +```bash +npx nx run twenty-front:graphql:generate # Regenerate types +npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema +``` + +## Traduções + +```bash +npx nx run twenty-front:lingui:extract # Extract strings +npx nx run twenty-front:lingui:compile # Compile translations +``` + +## Build + +```bash +npx nx build twenty-shared # Must be built first +npx nx build twenty-front +npx nx build twenty-server +``` diff --git a/packages/twenty-docs/l/pt/developers/contribute/style-guide.mdx b/packages/twenty-docs/l/pt/developers/contribute/style-guide.mdx new file mode 100644 index 00000000000..8b29c657f21 --- /dev/null +++ b/packages/twenty-docs/l/pt/developers/contribute/style-guide.mdx @@ -0,0 +1,176 @@ +--- +title: Guia de Estilo +icon: paintbrush +description: Code conventions and best practices for contributing to Twenty. +--- + +## React + +### Functional components only + +Always use TSX functional components with named exports. + +```tsx +// ❌ Bad +const MyComponent = () => { + return
Hello World
; +}; +export default MyComponent; + +// ✅ Good +export function MyComponent() { + return
Hello World
; +}; +``` + +### Propriedades + +Create a type named `{ComponentName}Props`. Use destructuring. Don't use `React.FC`. + +```tsx +type MyComponentProps = { + name: string; +}; + +export const MyComponent = ({ name }: MyComponentProps) =>
Hello {name}
; +``` + +### No single-variable prop spreading + +```tsx +// ❌ Bad +const MyComponent = (props: MyComponentProps) => ; + +// ✅ Good +const MyComponent = ({ prop1, prop2 }: MyComponentProps) => ; +``` + +## Gerenciamento de Estado + +### Jotai atoms for global state + +```tsx +import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; +import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; + +export const myAtomState = createAtomState({ + key: 'myAtomState', + defaultValue: 'default value', +}); +``` + +* Prefer atoms over prop drilling +* Don't use `useRef` for state — use `useState` or atoms +* Use atom families and selectors for lists + +### Avoid unnecessary re-renders + +* Extract `useEffect` and data fetching into sibling sidecar components +* Prefer event handlers (`handleClick`, `handleChange`) over `useEffect` +* Don't use `React.memo()` — fix the root cause instead +* Limit `useCallback` / `useMemo` usage + +```tsx +// ❌ Bad — useEffect in the same component causes re-renders +export const Page = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return
{data}
; +}; + +// ✅ Good — extract into sibling +export const PageData = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return <>; +}; +export const Page = () => { + const [data] = useAtomState(dataState); + return
{data}
; +}; +``` + +## TypeScript + +* **`type` over `interface`** — more flexible, easier to compose +* **String literals over enums** — except for GraphQL codegen enums and internal library APIs +* **No `any`** — strict TypeScript enforced +* **No type imports** — use regular imports (enforced by Oxlint `typescript/consistent-type-imports`) +* **Use [Zod](https://github.com/colinhacks/zod)** for runtime validation of untyped objects + +## JavaScript + +```tsx +// Use nullish-coalescing (??) instead of || +const value = process.env.MY_VALUE ?? 'default'; + +// Use optional chaining +onClick?.(); +``` + +## Nomeação + +* **Variables**: camelCase, descriptive (`email` not `value`, `fieldMetadata` not `fm`) +* **Constants**: SCREAMING_SNAKE_CASE +* **Types/Classes**: PascalCase +* **Files/directories**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`) +* **Event handlers**: `handleClick` (not `onClick` for the handler function) +* **Component props**: prefix with component name (`ButtonProps`) +* **Styled components**: prefix with `Styled` (`StyledTitle`) + +## Estilização + +Use [Linaria](https://github.com/callstack/linaria) styled components. Use theme values — avoid hardcoded `px`, `rem`, or colors. + +```tsx +// ❌ Bad +const StyledButton = styled.button` + color: #333333; + font-size: 1rem; + margin-left: 4px; +`; + +// ✅ Good +const StyledButton = styled.button` + color: ${({ theme }) => theme.font.color.primary}; + font-size: ${({ theme }) => theme.font.size.md}; + margin-left: ${({ theme }) => theme.spacing(1)}; +`; +``` + +## Importações + +Use aliases instead of relative paths: + +```tsx +// ❌ Bad +import { Foo } from '../../../../../testing/decorators/Foo'; + +// ✅ Good +import { Foo } from '~/testing/decorators/Foo'; +import { Bar } from '@/modules/bar/components/Bar'; +``` + +## Folder Structure + +``` +front +└── modules/ # Feature modules +│ └── module1/ +│ ├── components/ +│ ├── constants/ +│ ├── contexts/ +│ ├── graphql/ (fragments, queries, mutations) +│ ├── hooks/ +│ ├── states/ (atoms, selectors) +│ ├── types/ +│ └── utils/ +└── pages/ # Route-level components +└── ui/ # Reusable UI components (display, input, feedback, ...) +``` + +* Modules can import from other modules, but `ui/` should stay dependency-free +* Use `internal/` subfolders for module-private code +* Components under 300 lines, services under 500 lines diff --git a/packages/twenty-docs/l/pt/developers/extend/api.mdx b/packages/twenty-docs/l/pt/developers/extend/api.mdx index 67642009461..9cabba4941d 100644 --- a/packages/twenty-docs/l/pt/developers/extend/api.mdx +++ b/packages/twenty-docs/l/pt/developers/extend/api.mdx @@ -1,147 +1,55 @@ --- title: APIs -description: Consulte e modifique seus dados de CRM programaticamente usando REST ou GraphQL. +icon: plug +description: REST and GraphQL APIs generated from your workspace schema. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -O Twenty foi desenvolvido para ser amigável ao desenvolvedor, oferecendo APIs poderosas que se adaptam ao seu modelo de dados personalizado. Oferecemos quatro tipos distintos de API para atender diferentes necessidades de integração. +## Schema-per-tenant APIs -## Abordagem Focada no Desenvolvedor +There is no static API reference for Twenty. Each workspace has its own schema — when you add a custom object (say `Invoice`), it immediately gets REST and GraphQL endpoints identical to built-in objects like `Company` or `Person`. The API is generated from the schema, so endpoints use your object and field names directly — no opaque IDs. -Twenty gera APIs especificamente para o seu modelo de dados: +Your workspace-specific API documentation is available under **Settings → API & Webhooks** after creating an API key. It includes an interactive playground where you can execute real calls against your data. -* **Nenhum ID longo necessário**: Use os nomes dos seus objetos e campos diretamente nos endpoints -* **Objetos padrão e personalizados tratados igualmente**: Seus objetos personalizados recebem o mesmo tratamento de API que os incorporados -* **Endpoints dedicados**: Cada objeto e campo tem seu próprio endpoint de API -* **Documentação personalizada**: Gerada especificamente para o modelo de dados do seu workspace +## Two APIs - -Sua documentação de API personalizada fica disponível em **Configurações → API & Webhooks** após criar uma chave de API. Como o Twenty gera APIs que correspondem ao seu modelo de dados personalizado, a documentação é exclusiva do seu workspace. - +**Core API** — `/rest/` and `/graphql/` -## Os dois tipos de API +CRUD on records: People, Companies, Opportunities, your custom objects. Query, filter, traverse relations. -### Core API +**Metadata API** — `/rest/metadata/` and `/metadata/` -Acessada em `/rest/` ou `/graphql/` +Schema management: create/modify/delete objects, fields, and relations. This is how you programmatically change your data model. -Trabalhe com seus **registros** (os dados): +Both are available as REST and GraphQL. GraphQL adds batch upserts and the ability to traverse relations in a single query. Same underlying data either way. -* Criar, ler, atualizar e excluir Pessoas, Empresas, Oportunidades, etc. -* Consultar e filtrar dados -* Gerenciar relações de registros +## Base URLs -### Metadata API - -Acessada em `/rest/metadata/` ou `/metadata/` - -Gerencie seu **workspace e modelo de dados**: - -* Criar, modificar ou excluir objetos e campos -* Configurar as configurações do workspace -* Defina relacionamentos entre objetos - -## REST vs GraphQL - -As APIs Core e Metadata estão disponíveis nos formatos REST e GraphQL: - -| Formato | Operações disponíveis | -| ----------- | --------------------------------------------------------------------------------- | -| **REST** | CRUD, operações em lote, upserts | -| **GraphQL** | Os mesmos + **upserts em lote**, consultas de relacionamento em uma única chamada | - -Escolha com base nas suas necessidades — ambos os formatos acessam os mesmos dados. - -## Endpoints de API - -| Ambiente | URL base | -| ------------------ | ------------------------- | -| **Nuvem** | `https://api.twenty.com/` | -| **Auto-hospedado** | `https://{your-domain}/` | +| Ambiente | URL base | +| ----------- | ------------------------- | +| Cloud | `https://api.twenty.com/` | +| Self-Hosted | `https://{your-domain}/` | ## Autenticação -Toda solicitação de API requer uma chave de API no cabeçalho: - ``` Authorization: Bearer YOUR_API_KEY ``` -### Criar uma Chave de API - -1. Vá para **Configurações → APIs & Webhooks** -2. Clique em **+ Criar chave** -3. Configurar: - * **Nome**: Nome descritivo para a chave - * **Data de expiração**: Quando a chave expira -4. Clique em **Salvar** -5. **Copie imediatamente** — a chave é exibida apenas uma vez +Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access. - -Sua chave de API concede acesso a dados confidenciais. Não a compartilhe com serviços não confiáveis. Se for comprometida, desative-a imediatamente e gere uma nova. - +For OAuth-based access (external apps acting on behalf of users), see [OAuth](/l/pt/developers/extend/oauth). -### Atribuir uma função a uma chave de API +## Batch operations -Para maior segurança, atribua uma função específica para limitar o acesso: +Both REST and GraphQL support batching up to 60 records per request — create, update, or delete. GraphQL also supports batch upsert (create-or-update in one call) using plural names like `CreateCompanies`. -1. Vá para **Configurações → Funções** -2. Clique na função que deseja atribuir -3. Abra a aba de **Atribuição** -4. Em **Chaves de API**, clique em **+ Atribuir à chave de API** -5. Selecione a chave de API +## Rate limits -A chave herdará as permissões dessa função. Veja [Permissões](/l/pt/user-guide/permissions-access/capabilities/permissions) para obter detalhes. - -### Gerenciar Chaves de API - -**Regenerar**: Configurações → APIs & Webhooks → Clique na chave → **Regenerar** - -**Excluir**: Configurações → APIs & Webhooks → Clique na chave → **Excluir** - -## Playground de API - -Teste suas APIs diretamente no navegador com nosso playground integrado — disponível tanto para **REST** quanto para **GraphQL**. - -### Acesse o Playground - -1. Vá para **Configurações → APIs & Webhooks** -2. Crie uma chave de API (obrigatório) -3. Clique em **REST API** ou **GraphQL API** para abrir o playground - -### O que você obtém - -* **Documentação interativa**: Gerada para o seu modelo de dados específico -* **Testes ao vivo**: Execute chamadas de API reais no seu workspace -* **Explorador de esquema**: Navegue pelos objetos, campos e relacionamentos disponíveis -* **Construtor de solicitações**: Construa consultas com preenchimento automático - -O playground reflete seus objetos e campos personalizados, portanto, a documentação está sempre precisa para o seu workspace. - -## Operações em Lote - -Tanto REST quanto GraphQL suportam operações em lote: - -* **Tamanho do lote**: Até 60 registros por requisição -* **Operações**: Criar, atualizar e excluir vários registros - -**Recursos exclusivos do GraphQL:** - -* **Upsert em lote**: Criar ou atualizar em uma única chamada -* Use nomes de objetos no plural (por exemplo, `CreateCompanies` em vez de `CreateCompany`) - -## Limites de taxa - -As solicitações de API são limitadas para garantir a estabilidade da plataforma: - -| Limite | Valor | -| ------------------- | ------------------------ | -| **Solicitações** | 100 chamadas por minuto | -| **Tamanho do lote** | 60 registros por chamada | - - -Use operações em lote para maximizar a taxa de transferência — processe até 60 registros em uma única chamada de API em vez de fazer solicitações individuais. - +| Limite | Valor | +| ---------- | ------------------------ | +| Requests | 100 per minute | +| Batch size | 60 registros por chamada | diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/building.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/building.mdx index 3c9fe67ad98..8a36bd2cb40 100644 --- a/packages/twenty-docs/l/pt/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/pt/developers/extend/apps/building.mdx @@ -1,2062 +1,104 @@ --- -title: Criando aplicativos -description: Defina objetos, funções de lógica, componentes de front-end e muito mais com o SDK da Twenty. +title: Arquitetura +description: How Twenty apps work — sandboxing, lifecycle, and the building blocks. +icon: sitemap --- - - Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo. - +Twenty apps are TypeScript packages that extend your workspace with custom objects, logic, UI components, and AI capabilities. They run on the Twenty platform with full sandboxing and permission controls. -O pacote `twenty-sdk` fornece blocos de construção tipados para criar seu app. Esta página cobre todos os tipos de entidade e clientes de API disponíveis no SDK. +## How apps work -## Funções DefineEntity +An app is a collection of **entities** declared using `defineEntity()` functions from the `twenty-sdk` package. The SDK detects these declarations via AST analysis at build time and produces a **manifest** — a complete description of what your app adds to a workspace. -O SDK fornece funções para definir as entidades do seu app. Você deve usar `export default defineEntity({...})` para que o SDK detecte suas entidades. Essas funções validam sua configuração em tempo de compilação e oferecem autocompletar na IDE e segurança de tipos. - - - **A organização de arquivos fica a seu critério.** - A detecção de entidades é baseada em AST — o SDK encontra chamadas a `export default defineEntity(...)` independentemente de onde o arquivo esteja. Agrupar arquivos por tipo (por exemplo, `logic-functions/`, `roles/`) é apenas uma convenção, não um requisito. - - - - - -Papéis encapsulam permissões sobre os objetos e ações do seu espaço de trabalho. - -```ts restricted-company-role.ts -import { - defineRole, - PermissionFlag, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -export default defineRole({ - universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', - label: 'My new role', - description: 'A role that can be used in your workspace', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - fieldUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); ``` - - - - -Todo app deve ter exatamente uma chamada a `defineApplication` que descreve: - -* **Identidade**: identificadores, nome de exibição e descrição. -* **Permissões**: qual papel é usado por suas funções e componentes de front-end. -* **Variáveis (opcional)**: pares chave–valor expostos às suas funções como variáveis de ambiente. -* **(Opcional) Funções de pré-instalação/pós-instalação**: funções de lógica que são executadas antes ou depois da instalação. - -```ts src/application-config.ts -import { defineApplication } from 'twenty-sdk/define'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Notas: -* Os campos `universalIdentifier` são IDs determinísticos que você controla. Gere-os uma vez e mantenha-os estáveis entre sincronizações. -* `applicationVariables` tornam-se variáveis de ambiente para suas funções e componentes de front-end (por exemplo, `DEFAULT_RECIPIENT_NAME` fica disponível como `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` deve fazer referência a um papel definido com `defineRole()` (veja acima). -* As funções de pré-instalação e pós-instalação são detectadas automaticamente durante a construção do manifesto — você não precisa referenciá-las em `defineApplication()`. - -#### Metadados do Marketplace - -Se você planeja [publicar seu app](/l/pt/developers/extend/apps/publishing), estes campos opcionais controlam como seu app aparece no marketplace: - -| Campo | Descrição | -| ------------------ | ----------------------------------------------------------------------------------------------------------------- | -| `autor` | Nome do autor ou da empresa | -| `categoria` | Categoria do app para filtragem no marketplace | -| `logoUrl` | Caminho para o logo do seu app (por exemplo, `public/logo.png`) | -| `screenshots` | Array de caminhos de capturas de tela (por exemplo, `public/screenshot-1.png`) | -| `aboutDescription` | Descrição em markdown mais longa para a aba "Sobre". Se omitido, o marketplace usa o `README.md` do pacote no npm | -| `websiteUrl` | Link para seu site | -| `termsUrl` | Link para os Termos de Serviço | -| `emailSupport` | Endereço de e-mail de suporte | -| `issueReportUrl` | Link para o rastreador de problemas | - -#### Papéis e permissões - -O campo `defaultRoleUniversalIdentifier` em `application-config.ts` designa o papel padrão usado pelas funções de lógica e pelos componentes de front-end do seu app. Veja `defineRole` acima para detalhes. - -* O token em tempo de execução injetado como `TWENTY_APP_ACCESS_TOKEN` é derivado desse papel. -* O cliente tipado é restrito às permissões concedidas a esse papel. -* Siga o princípio do menor privilégio: crie um papel dedicado com apenas as permissões de que suas funções precisam. - -##### Papel de função padrão - -Ao criar um novo app com o scaffold, a CLI cria um arquivo de papel padrão: - -```ts src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk/define'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: true, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [], - fieldPermissions: [], - permissionFlags: [], -}); -``` - -O `universalIdentifier` desse papel é referenciado em `application-config.ts` como `defaultRoleUniversalIdentifier`: - -* **\*.role.ts** define o que o papel pode fazer. -* **application-config.ts** aponta para esse papel para que suas funções herdem suas permissões. - -Notas: -* Comece pelo papel gerado pelo scaffold e depois restrinja-o progressivamente seguindo o princípio do menor privilégio. -* Substitua `objectPermissions` e `fieldPermissions` pelos objetos e campos de que suas funções realmente precisam. -* `permissionFlags` controlam o acesso a recursos em nível de plataforma. Mantenha-os no mínimo necessário. -* Veja um exemplo funcional: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - - - - -Objetos personalizados descrevem tanto o esquema quanto o comportamento de registros no seu espaço de trabalho. Use `defineObject()` para definir objetos com validação integrada: - -```ts postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk/define'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Pontos-chave: - -* Use `defineObject()` para validação integrada e melhor suporte na IDE. -* O `universalIdentifier` deve ser exclusivo e estável entre implantações. -* Cada campo requer `name`, `type`, `label` e seu próprio `universalIdentifier` estável. -* O array `fields` é opcional — você pode definir objetos sem campos personalizados. -* Você pode criar novos objetos usando `yarn twenty add`, que orienta você sobre nomeação, campos e relacionamentos. - - -**Os campos base são criados automaticamente.** Quando você define um objeto personalizado, o Twenty adiciona automaticamente campos padrão -como `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` e `deletedAt`. -Você não precisa definir esses no seu array `fields` — adicione apenas seus campos personalizados. -Você pode substituir os campos padrão definindo um campo com o mesmo nome no seu array `fields`, -mas isso não é recomendado. - - - - - -Use `defineField()` para adicionar campos a objetos que não são seus — como objetos padrão do Twenty (Person, Company, etc.). ou a objetos de outros apps. Ao contrário dos campos inline em `defineObject()`, os campos independentes exigem um `objectUniversalIdentifier` para especificar qual objeto eles estendem: - -```ts src/fields/company-loyalty-tier.field.ts -import { defineField, FieldType } from 'twenty-sdk/define'; - -export default defineField({ - universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', - objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object - name: 'loyaltyTier', - type: FieldType.SELECT, - label: 'Loyalty Tier', - icon: 'IconStar', - options: [ - { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, - { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, - { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, - ], -}); -``` - -Pontos-chave: -* `objectUniversalIdentifier` identifica o objeto de destino. Para objetos padrão, use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` exportado de `twenty-sdk`. -* Ao definir campos inline em `defineObject()`, você não precisa de `objectUniversalIdentifier` — ele é herdado do objeto pai. -* `defineField()` é a única forma de adicionar campos a objetos que você não criou com `defineObject()`. - - - - -As relações conectam objetos entre si. No Twenty, as relações são sempre **bidirecionais** — você define ambos os lados, e cada lado faz referência ao outro. - -Existem dois tipos de relação: - -| Tipo de relação | Descrição | Tem chave estrangeira? | -| --------------- | ----------------------------------------------------------------- | ---------------------- | -| `MANY_TO_ONE` | Muitos registros deste objeto apontam para um registro do destino | Sim (`joinColumnName`) | -| `ONE_TO_MANY` | Um registro deste objeto possui muitos registros do destino | Não (lado inverso) | - -#### Como as relações funcionam - -Toda relação requer **dois campos** que façam referência um ao outro: - -1. O lado **MANY_TO_ONE** — fica no objeto que contém a chave estrangeira -2. O lado **ONE_TO_MANY** — fica no objeto que possui a coleção - -Ambos os campos usam `FieldType.RELATION` e fazem referência cruzada um ao outro via `relationTargetFieldMetadataUniversalIdentifier`. - -#### Exemplo: Um cartão postal tem muitos destinatários - -Suponha que um `PostCard` possa ser enviado para muitos registros `PostCardRecipient`. Cada destinatário pertence a exatamente um cartão postal. - -**Etapa 1: Defina o lado ONE_TO_MANY em PostCard** (o lado "um"): - -```ts src/fields/post-card-recipients-on-post-card.field.ts -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; -// Import from the other side -import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; - -export default defineField({ - universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCardRecipients', - label: 'Post Card Recipients', - icon: 'IconUsers', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); -``` - -**Etapa 2: Defina o lado MANY_TO_ONE em PostCardRecipient** (o lado "muitos" — contém a chave estrangeira): - -```ts src/fields/post-card-on-post-card-recipient.field.ts -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; -// Import from the other side -import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; - -export default defineField({ - universalIdentifier: POST_CARD_FIELD_ID, - objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - icon: 'IconMail', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, -}); +your-app/ +├── src/ +│ ├── application-config.ts ← defineApplication (required, one per app) +│ ├── roles/ ← defineRole +│ ├── objects/ ← defineObject +│ ├── fields/ ← defineField +│ ├── logic-functions/ ← defineLogicFunction +│ ├── front-components/ ← defineFrontComponent +│ ├── skills/ ← defineSkill +│ ├── agents/ ← defineAgent +│ ├── views/ ← defineView +│ ├── navigation-menu-items/ ← defineNavigationMenuItem +│ └── page-layouts/ ← definePageLayout +├── public/ ← Static assets (images, icons) +└── package.json ``` -**Importações circulares:** Ambos os campos de relação referenciam o `universalIdentifier` um do outro. Para evitar problemas de importação circular, exporte os IDs dos seus campos como constantes nomeadas de cada arquivo e importe-os no outro arquivo. O sistema de build resolve isso em tempo de compilação. + **File organization is up to you.** Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. The folder structure above is a convention, not a requirement. -#### Relacionando a objetos padrão - -Para criar uma relação com um objeto integrado do Twenty (Person, Company, etc.), use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: - -```ts src/fields/person-on-self-hosting-user.field.ts -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; -import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; - -export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; -export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; - -export default defineField({ - universalIdentifier: PERSON_FIELD_ID, - objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - description: 'Person matching with the self hosting user', - isNullable: true, - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'personId', - }, -}); -``` - -#### Propriedades de campos de relação - -| Propriedade | Obrigatório | Descrição | -| ------------------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------- | -| `tipo` | Sim | Deve ser `FieldType.RELATION` | -| `relationTargetObjectMetadataUniversalIdentifier` | Sim | O `universalIdentifier` do objeto de destino | -| `relationTargetFieldMetadataUniversalIdentifier` | Sim | O `universalIdentifier` do campo correspondente no objeto de destino | -| `universalSettings.relationType` | Sim | `RelationType.MANY_TO_ONE` ou `RelationType.ONE_TO_MANY` | -| `universalSettings.onDelete` | Apenas para MANY_TO_ONE | O que acontece quando o registro referenciado é excluído: `CASCADE`, `SET_NULL`, `RESTRICT` ou `NO_ACTION` | -| `universalSettings.joinColumnName` | Apenas para MANY_TO_ONE | Nome da coluna no banco de dados para a chave estrangeira (por exemplo, `postCardId`) | - -#### Campos de relação inline em defineObject - -Você também pode definir campos de relação diretamente dentro de `defineObject()`. Nesse caso, omita `objectUniversalIdentifier` — ele é herdado do objeto pai: - -```ts -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCardRecipient', - // ... - fields: [ - { - universalIdentifier: POST_CARD_FIELD_ID, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, - }, - // ... other fields - ], -}); -``` - - - -Cada arquivo de função usa `defineLogicFunction()` para exportar uma configuração com um handler e gatilhos opcionais. - -```ts src/logic-functions/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - httpRouteTriggerSettings: { - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: true, - }, - /*databaseEventTriggerSettings: { - eventName: 'people.created', - },*/ - /*cronTriggerSettings: { - pattern: '0 0 1 1 *', - },*/ -}); -``` - -Tipos de gatilho disponíveis: -* **httpRoute**: Expõe sua função em um caminho e método HTTP **no endpoint `/s/`**: -> por exemplo, `path: '/post-card/create'` é acessível em `https://your-twenty-server.com/s/post-card/create` -* **cron**: Executa sua função em um agendamento usando uma expressão CRON. -* **databaseEvent**: Executa em eventos do ciclo de vida de objetos do espaço de trabalho. Quando a operação do evento é `updated`, campos específicos a serem observados podem ser especificados no array `updatedFields`. Se deixar indefinido ou vazio, qualquer atualização acionará a função. -> por exemplo, `person.updated`, `*.created`, `company.*` - - -Você também pode executar manualmente uma função usando a CLI: - -```bash filename="Terminal" -yarn twenty exec -n create-new-post-card -p '{"key": "value"}' -``` - -```bash filename="Terminal" -yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -Você pode acompanhar os logs com: - -```bash filename="Terminal" -yarn twenty logs -``` - - -#### Payload de gatilho de rota - -Quando um gatilho de rota invoca sua função de lógica, ela recebe um objeto `RoutePayload` que segue o [formato HTTP API v2 da AWS](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). -Importe o tipo `RoutePayload` de `twenty-sdk`: - -```ts -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; - -const handler = async (event: RoutePayload) => { - const { headers, queryStringParameters, pathParameters, body } = event; - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -O tipo `RoutePayload` tem a seguinte estrutura: - - | Propriedade | Tipo | Descrição | Exemplo | - | ---------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------- | - | `headers` | `Record\` | Cabeçalhos HTTP (apenas aqueles listados em `forwardedRequestHeaders`) | veja a seção abaixo | - | `queryStringParameters` | `Record\` | Parâmetros de query string (valores múltiplos unidos por vírgulas) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | - | `pathParameters` | `Record\` | Parâmetros de caminho extraídos do padrão de rota | `/users/:id`, `/users/123` -> `{ id: '123' }` | - | `body` | `object \| null` | Corpo da requisição analisado (JSON) | `{ id: 1 }` -> `{ id: 1 }` | - | `isBase64Encoded` | `boolean` | Se o corpo está codificado em base64 | | - | `requestContext.http.method` | `string` | Método HTTP (GET, POST, PUT, PATCH, DELETE) | | - | `requestContext.http.path` | `string` | Caminho bruto da requisição | | - - -#### forwardedRequestHeaders - -Por padrão, os cabeçalhos HTTP das requisições recebidas **não** são repassados para sua função de lógica por motivos de segurança. -Para acessar cabeçalhos específicos, liste-os explicitamente no array `forwardedRequestHeaders`: - -```ts -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - httpRouteTriggerSettings: { - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, -}); -``` - -No seu handler, acesse os cabeçalhos encaminhados assim: - -```ts -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - -Os nomes dos cabeçalhos são normalizados para minúsculas. Acesse-os usando chaves em minúsculas (por exemplo, `event.headers['content-type']`). - - -#### Expor uma função como ferramenta - -Funções lógicas podem ser expostas como **ferramentas** para agentes de IA e fluxos de trabalho. Quando marcada como ferramenta, uma função fica detectável pelos recursos de IA do Twenty e pode ser usada em automações de fluxos de trabalho. - -Para marcar uma função de lógica como ferramenta, defina `isTool: true`: - -```ts src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, -}); -``` - -Pontos-chave: - -* Você pode combinar `isTool` com gatilhos — uma função pode ser ao mesmo tempo uma ferramenta (chamável por agentes de IA) e acionada por eventos. -* **`toolInputSchema`** (opcional): Um objeto JSON Schema que descreve os parâmetros que sua função aceita. O schema é calculado automaticamente a partir da análise estática do código-fonte, mas você pode defini-lo explicitamente: - -```ts -export default defineLogicFunction({ - ..., - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - - -**Escreva uma boa `description`.** Os agentes de IA dependem do campo `description` da função para decidir quando usar a ferramenta. Seja específico sobre o que a ferramenta faz e quando ela deve ser chamada. - - - - - -Uma função de pós-instalação é uma função lógica que é executada automaticamente assim que seu aplicativo terminar de ser instalado em um espaço de trabalho. O servidor a executa **depois** que os metadados do aplicativo forem sincronizados e o cliente do SDK for gerado, para que o espaço de trabalho esteja totalmente pronto para uso e o novo esquema esteja disponível. Casos de uso típicos incluem popular dados padrão, criar registros iniciais, configurar as definições do espaço de trabalho ou provisionar recursos em serviços de terceiros. - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - shouldRunSynchronously: false, - handler, -}); -``` - -Você também pode executar manualmente a função de pós-instalação a qualquer momento usando a CLI: - -```bash filename="Terminal" -yarn twenty exec --postInstall -``` - -Pontos-chave: -* As funções de pós-instalação usam `definePostInstallLogicFunction()` — uma variante especializada que omite as configurações de gatilho (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* O manipulador recebe um `InstallPayload` com `{ previousVersion?: string; newVersion: string }` — `newVersion` é a versão que está sendo instalada, e `previousVersion` é a versão que foi instalada anteriormente (ou `undefined` em uma instalação nova). Use esses valores para distinguir instalações novas de atualizações e para executar lógica de migração específica da versão. -* **Quando o hook é executado**: apenas em instalações novas, por padrão. Passe `shouldRunOnVersionUpgrade: true` se você também quiser que ele seja executado quando o app for atualizado a partir de uma versão anterior. Quando omitida, a flag tem valor padrão `false` e as atualizações ignoram o hook. -* **Modelo de execução — assíncrono por padrão, síncrono opcional**: a flag `shouldRunSynchronously` controla *como* a pós-instalação é executada. - * `shouldRunSynchronously: false` *(padrão)* — o hook é **enfileirado na fila de mensagens** com `retryLimit: 3` e é executado de forma assíncrona em um worker. A resposta da instalação retorna assim que o job é enfileirado, então um manipulador lento ou com falha não bloqueia quem chamou. O worker tentará novamente até três vezes. **Use isto para jobs de longa duração** — popular grandes conjuntos de dados, chamar APIs de terceiros lentas, provisionar recursos externos, qualquer coisa que possa exceder uma janela razoável de resposta HTTP. - * `shouldRunSynchronously: true` — o hook é executado **inline durante o fluxo de instalação** (mesmo executor da pré-instalação). A requisição de instalação bloqueia até o manipulador terminar e, se ele lançar uma exceção, quem chamou a instalação recebe um `POST_INSTALL_ERROR`. Sem novas tentativas automáticas. **Use isto para trabalhos rápidos que precisam ser concluídos antes da resposta** — por exemplo, emitir um erro de validação para o usuário ou fazer uma configuração rápida da qual o cliente dependerá imediatamente após a chamada de instalação retornar. Tenha em mente que a migração de metadados já foi aplicada quando a pós-instalação é executada, então uma falha no modo síncrono **não** reverte as alterações de esquema — ela apenas expõe o erro. -* Garanta que seu manipulador seja idempotente. No modo assíncrono, a fila pode tentar novamente até três vezes; em qualquer modo, o hook pode ser executado novamente em atualizações quando `shouldRunOnVersionUpgrade: true`. -* As variáveis de ambiente `APPLICATION_ID`, `APP_ACCESS_TOKEN` e `API_URL` estão disponíveis dentro do manipulador (assim como em qualquer outra função de lógica), então você pode chamar a API da Twenty com um token de acesso de aplicativo com escopo para o seu app. -* É permitida apenas uma função de pós-instalação por app. A geração do manifesto apresentará erro se mais de uma for detectada. -* O `universalIdentifier`, `shouldRunOnVersionUpgrade` e `shouldRunSynchronously` da função são anexados automaticamente ao manifesto do aplicativo no campo `postInstallLogicFunction` durante o build — você não precisa referenciá-los em `defineApplication()`. -* O tempo limite padrão é definido como 300 segundos (5 minutos) para permitir tarefas de configuração mais longas, como o pré-carregamento de dados. -* **Não executado no modo de desenvolvimento**: quando um app é registrado localmente (via `yarn twenty dev`), o servidor pula completamente o fluxo de instalação e sincroniza arquivos diretamente pelo watcher da CLI — portanto, a pós-instalação nunca é executada no modo de desenvolvimento, independentemente de `shouldRunSynchronously`. Use `yarn twenty exec --postInstall` para acioná-lo manualmente em um workspace em execução. - - - - -Uma função de pré-instalação é uma função de lógica que é executada automaticamente durante a instalação, **antes que a migração de metadados do workspace seja aplicada**. Ela compartilha o mesmo formato de payload que a pós-instalação (`InstallPayload`), mas está posicionada mais cedo no fluxo de instalação para poder preparar o estado do qual a próxima migração depende — usos típicos incluem fazer backup de dados, validar a compatibilidade com o novo esquema ou arquivar registros que estão prestes a ser reestruturados ou removidos. - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -Você também pode executar manualmente a função de pré-instalação a qualquer momento usando a CLI: - -```bash filename="Terminal" -yarn twenty exec --preInstall -``` - -Pontos-chave: -* Funções de pré-instalação usam `definePreInstallLogicFunction()` — a mesma configuração especializada da pós-instalação, apenas anexada a um ponto diferente do ciclo de vida. -* Os manipuladores de pré e pós-instalação recebem o mesmo tipo `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Importe-o uma vez e reutilize-o para ambos os hooks. -* **Quando o hook é executado**: posicionado imediatamente antes da migração de metadados do workspace (`synchronizeFromManifest`). Antes de executar, o servidor realiza uma "sincronização simplificada" puramente aditiva que registra a função de pré-instalação da **nova** versão nos metadados do workspace — nada mais é alterado — e então a executa. Como essa sincronização é apenas aditiva, os objetos, campos e dados da versão anterior ainda estão intactos quando seu manipulador é executado: você pode ler e fazer backup com segurança do estado pré-migração. -* **Modelo de execução**: a pré-instalação é executada **de forma síncrona** e **bloqueia a instalação**. Se o manipulador lançar uma exceção, a instalação é abortada antes que quaisquer alterações de esquema sejam aplicadas — o workspace permanece na versão anterior em um estado consistente. Isto é intencional: a pré-instalação é sua última chance de recusar uma atualização arriscada. -* Assim como na pós-instalação, é permitida apenas uma função de pré-instalação por app. Ela é anexada ao manifesto do aplicativo sob `preInstallLogicFunction` automaticamente durante o build. -* **Não é executada no modo de desenvolvimento**: igual à pós-instalação — o fluxo de instalação é totalmente ignorado para apps registrados localmente, portanto a pré-instalação nunca é executada com `yarn twenty dev`. Use `yarn twenty exec --preInstall` para acioná-lo manualmente. - - - - -Ambos os hooks fazem parte do mesmo fluxo de instalação e recebem o mesmo `InstallPayload`. A diferença é **quando** eles são executados em relação à migração de metadados do workspace, e isso muda quais dados eles podem manipular com segurança. +## Entity types + +| Entidade | Finalidade | Documentação | +| ------------------------ | ----------------------------------------- | ------------------------------------------------------------ | +| **Application** | App identity, permissions, variables | [Data Model](/l/pt/developers/extend/apps/data-model) | +| **Role** | Permission sets for objects and fields | [Data Model](/l/pt/developers/extend/apps/data-model) | +| **Objeto** | Custom data tables with fields | [Data Model](/l/pt/developers/extend/apps/data-model) | +| **Campo** | Extend existing objects, define relations | [Data Model](/l/pt/developers/extend/apps/data-model) | +| **Logic Function** | Server-side TypeScript with triggers | [Funções lógicas](/l/pt/developers/extend/apps/logic-functions) | +| **Front Component** | Sandboxed React UI in Twenty's page | [Front Components](/l/pt/developers/extend/apps/front-components) | +| **Skill** | Reusable AI agent instructions | [Skills & Agents](/l/pt/developers/extend/apps/skills-and-agents) | +| **Agent** | AI assistants with custom prompts | [Skills & Agents](/l/pt/developers/extend/apps/skills-and-agents) | +| **View** | Pre-configured record list views | [Layout](/l/pt/developers/extend/apps/layout) | +| **Navigation Menu Item** | Custom sidebar entries | [Layout](/l/pt/developers/extend/apps/layout) | +| **Page Layout** | Custom record page tabs and widgets | [Layout](/l/pt/developers/extend/apps/layout) | + +## Sandboxing + +* **Logic functions** run in isolated Node.js processes on the server. They only access data through the typed API client, scoped to the app's role permissions. +* **Front components** run in Web Workers using Remote DOM — sandboxed from the main page but rendering native DOM elements (not iframes). They communicate with Twenty via a message-passing host API. +* **Permissions** are enforced at the API level. The runtime token (`TWENTY_APP_ACCESS_TOKEN`) is derived from the role defined in `defineApplication()`. + +## App lifecycle ``` -┌─────────────────────────────────────────────────────────────┐ -│ install flow │ -│ │ -│ upload package → [pre-install] → metadata migration → │ -│ generate SDK → [post-install] │ -│ │ -│ old schema visible new schema visible │ -└─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────┐ +│ Development │ +│ npx create-twenty-app → yarn twenty dev (live sync) │ +├─────────────────────────────────────────────────────────┤ +│ Build & Deploy │ +│ yarn twenty build → yarn twenty deploy │ +├─────────────────────────────────────────────────────────┤ +│ Install flow │ +│ upload → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +├─────────────────────────────────────────────────────────┤ +│ Publish │ +│ npm publish → appears in Twenty marketplace │ +└─────────────────────────────────────────────────────────┘ ``` -A pré-instalação é sempre **síncrona** (ela bloqueia a instalação e pode abortá-la). A pós-instalação é **assíncrona por padrão** — enfileirada em um worker com novas tentativas automáticas — mas pode optar por execução síncrona com `shouldRunSynchronously: true`. Veja o acordeão `definePostInstallLogicFunction` acima para saber quando usar cada modo. - -**Use `post-install` para qualquer coisa que precise que o novo esquema exista.** Este é o caso mais comum: - -* Popular dados padrão (criando registros iniciais, visualizações padrão, conteúdo de demonstração) em objetos e campos recém-adicionados. -* Registrar webhooks com serviços de terceiros agora que o app tem suas credenciais. -* Chamar sua própria API para finalizar a configuração que depende dos metadados sincronizados. -* Lógica idempotente de "garantir que isso exista" que deve reconciliar o estado em cada atualização — combine com `shouldRunOnVersionUpgrade: true`. - -Exemplo — popular um registro `PostCard` padrão após a instalação: - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion }: InstallPayload): Promise => { - if (previousVersion) return; // fresh installs only - - const client = createClient(); - await client.postCard.create({ - data: { title: 'Welcome to Postcard', content: 'Your first card!' }, - }); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Seeds a welcome post card after install.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - handler, -}); -``` - -**Use `pre-install` quando uma migração, de outra forma, destruiria ou corromperia dados existentes.** Como a pré-instalação roda contra o esquema *anterior* e sua falha reverte a atualização, é o lugar certo para qualquer coisa arriscada: - -* **Fazer backup de dados que estão prestes a ser removidos ou reestruturados** — por exemplo, você está removendo um campo na v2 e precisa copiar seus valores para outro campo ou exportá-los para um armazenamento antes que a migração seja executada. -* **Arquivar registros que uma nova restrição invalidaria** — por exemplo, um campo está se tornando `NOT NULL` e você precisa excluir ou corrigir linhas com valores nulos primeiro. -* **Validar a compatibilidade e recusar a atualização se os dados atuais não puderem ser migrados de forma limpa** — lance uma exceção no manipulador e a instalação é abortada sem alterações aplicadas. Isto é mais seguro do que descobrir a incompatibilidade no meio da migração. -* **Renomear ou reatribuir chaves de dados** antes de uma alteração de esquema que perderia a associação. - -Exemplo — arquivar registros antes de uma migração destrutiva: - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { - // Only the 1.x → 2.x upgrade drops the legacy `notes` field. - if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { - return; - } - - const client = createClient(); - const legacyRecords = await client.postCard.findMany({ - where: { notes: { isNotNull: true } }, - }); - - if (legacyRecords.length === 0) return; - - // Copy legacy `notes` into the new `description` field before the migration - // drops the `notes` column. If this fails, the upgrade is aborted and the - // workspace stays on v1 with all data intact. - await Promise.all( - legacyRecords.map((record) => - client.postCard.update({ - where: { id: record.id }, - data: { description: record.notes }, - }), - ), - ); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Backs up legacy notes into description before the v2 migration.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -**Regra geral:** - -| Você quer… | Usar | -| ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| Popular dados padrão, configurar o workspace, registrar recursos externos | `post-install` | -| Executar processos longos de popular dados ou chamadas a terceiros que não devem bloquear a resposta da instalação | `post-install` (padrão — `shouldRunSynchronously: false`, com novas tentativas do worker) | -| Executar uma configuração rápida da qual o chamador dependerá imediatamente após o retorno da chamada de instalação | `post-install` com `shouldRunSynchronously: true` | -| Ler ou fazer backup de dados que a próxima migração perderia | `pre-install` | -| Rejeitar uma atualização que corromperia dados existentes | `pre-install` (lançar uma exceção no manipulador) | -| Executar reconciliação em cada atualização | `post-install` com `shouldRunOnVersionUpgrade: true` | -| Fazer uma configuração única apenas na primeira instalação | `post-install` com `shouldRunOnVersionUpgrade: false` (padrão) | - - -Em caso de dúvida, use **post-install** como padrão. Recurra à pré-instalação somente quando a própria migração for destrutiva e você precisar interceptar o estado anterior antes que ele desapareça. - - - - - -Componentes de front-end são componentes React que renderizam diretamente dentro da UI do Twenty. Eles são executados em um Web Worker isolado usando Remote DOM — seu código é sandboxed, mas renderiza nativamente na página, não em um iframe. - -#### Onde os componentes de front-end podem ser usados - -Os componentes de front-end podem ser renderizados em dois locais dentro do Twenty: - -* **Painel lateral** — Componentes de front-end não headless abrem no painel lateral direito. Este é o comportamento padrão quando um componente de front-end é acionado pelo menu de comandos. -* **Widgets (painéis e páginas de registro)** — Componentes de front-end podem ser incorporados como widgets nos layouts de página. Ao configurar um painel ou o layout de uma página de registro, os usuários podem adicionar um widget de componente de front-end. - -#### Exemplo básico - -A maneira mais rápida de ver um componente de front-end em ação é registrá-lo como um **comando**. Adicionar um campo `command` com `isPinned: true` faz com que ele apareça como um botão de ação rápida no canto superior direito da página — não é necessário layout de página: - -```tsx src/front-components/hello-world.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; - -const HelloWorld = () => { - return ( -
-

Hello from my app!

-

This component renders inside Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', - name: 'hello-world', - description: 'A simple front component', - component: HelloWorld, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', - shortLabel: 'Hello', - label: 'Hello World', - icon: 'IconBolt', - isPinned: true, - availabilityType: 'GLOBAL', - }, -}); -``` - -Após sincronizar com `yarn twenty dev` (ou executando uma única vez o `yarn twenty dev --once`), a ação rápida aparece no canto superior direito da página: - -
- Botão de ação rápida no canto superior direito -
- -Clique nele para renderizar o componente inline. - -{/* TODO: add screenshot of the rendered front component */} - -#### Campos de configuração - -| Campo | Obrigatório | Descrição | -| --------------------- | ----------- | ----------------------------------------------------------------------------------------- | -| `universalIdentifier` | Sim | ID único e estável para este componente | -| `component` | Sim | Uma função de componente React | -| `name` | Não | Nome de Exibição | -| `description` | Não | Descrição do que o componente faz | -| `isHeadless` | Não | Defina como `true` se o componente não tiver interface visível (veja abaixo) | -| `command` | Não | Registre o componente como um comando (veja [opções de comando](#command-options) abaixo) | - -#### Colocando um componente de front-end em uma página - -Além de comandos, você pode incorporar um componente de front-end diretamente em uma página de registro adicionando-o como um widget em um **layout de página**. Veja a seção [definePageLayout](#definepagelayout) para obter detalhes. - -#### Headless vs não headless - -Os componentes de front-end têm dois modos de renderização controlados pela opção `isHeadless`: - -**Não headless (padrão)** — O componente renderiza uma interface visível. Quando acionado pelo menu de comandos, ele é aberto no painel lateral. Este é o comportamento padrão quando `isHeadless` é `false` ou omitido. - -**Headless (`isHeadless: true`)** — The component mounts invisibly in the background. Ele não abre o painel lateral. Componentes headless são projetados para ações que executam lógica e, em seguida, se desmontam — por exemplo, executar uma tarefa assíncrona, navegar para uma página ou exibir um modal de confirmação. Eles se combinam naturalmente com os componentes Command do SDK descritos abaixo. - -```tsx src/front-components/sync-tracker.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; -import { useEffect } from 'react'; - -const SyncTracker = () => { - const recordId = useRecordId(); - - useEffect(() => { - enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); - }, [recordId]); - - return null; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'sync-tracker', - description: 'Tracks record views silently', - isHeadless: true, - component: SyncTracker, -}); -``` - -Como o componente retorna `null`, o Twenty ignora renderizar um contêiner para ele — nenhum espaço vazio aparece no layout. O componente ainda tem acesso a todos os hooks e à API de comunicação do host. - -#### Componentes Command do SDK - -O pacote `twenty-sdk` fornece quatro componentes auxiliares Command projetados para componentes de front-end headless. Cada componente executa uma ação ao montar, trata erros exibindo uma notificação de snackbar e desmonta automaticamente o componente de front-end ao concluir. - -Importe-os de `twenty-sdk/command`: - -* **`Command`** — Executa um callback assíncrono via a prop `execute`. -* **`CommandLink`** — Navega para um caminho do app. Props: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — Abre um modal de confirmação. Se o usuário confirmar, executa o callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — Abre uma página específica do painel lateral. Props: `page`, `pageTitle`, `pageIcon`. - -Aqui está um exemplo completo de um componente de front-end headless usando `Command` para executar uma ação a partir do menu de comandos: - -```tsx src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -E um exemplo usando `CommandModal` para solicitar confirmação antes de executar: - -```tsx src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Acessando o contexto de execução - -Dentro do seu componente, use hooks do SDK para acessar o usuário atual, o registro e a instância do componente: - -```tsx src/front-components/record-info.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - useUserId, - useRecordId, - useFrontComponentId, -} from 'twenty-sdk/front-component'; - -const RecordInfo = () => { - const userId = useUserId(); - const recordId = useRecordId(); - const componentId = useFrontComponentId(); - - return ( -
-

User: {userId}

-

Record: {recordId ?? 'No record context'}

-

Component: {componentId}

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', - name: 'record-info', - component: RecordInfo, -}); -``` - -Hooks disponíveis: - -| Hook | Retorna | Descrição | -| --------------------------------------------- | ------------------ | ------------------------------------------------------------------ | -| `useUserId()` | `string` ou `null` | O ID do usuário atual | -| `useRecordId()` | `string` ou `null` | O ID do registro atual (quando colocado em uma página de registro) | -| `useFrontComponentId()` | `string` | O ID desta instância do componente | -| `useFrontComponentExecutionContext(selector)` | varia | Acesse o contexto de execução completo com uma função seletora | - -#### API de comunicação do host - -Componentes de front-end podem acionar navegação, modais e notificações usando funções de `twenty-sdk`: - -| Função | Descrição | -| ----------------------------------------------- | ------------------------------------- | -| `navigate(to, params?, queryParams?, options?)` | Navegar para uma página no app | -| `openSidePanelPage(params)` | Abrir um painel lateral | -| `closeSidePanel()` | Fecha o painel lateral | -| `openCommandConfirmationModal(params)` | Mostrar um diálogo de confirmação | -| `enqueueSnackbar(params)` | Mostrar uma notificação do tipo toast | -| `unmountFrontComponent()` | Desmontar o componente | -| `updateProgress(progress)` | Atualizar um indicador de progresso | - -Aqui está um exemplo que usa a API do host para exibir um snackbar e fechar o painel lateral após a conclusão de uma ação: - -```tsx src/front-components/archive-record.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId } from 'twenty-sdk/front-component'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -#### Opções de comando - -Adicionar um campo `command` a `defineFrontComponent` registra o componente no menu de comandos (Cmd+K). Se `isPinned` for `true`, ele também aparece como um botão de ação rápida no canto superior direito da página. - -| Campo | Obrigatório | Descrição | -| --------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `universalIdentifier` | Sim | ID exclusivo e estável para o comando | -| `label` | Sim | Rótulo completo exibido no menu de comandos (Cmd+K) | -| `shortLabel` | Não | Rótulo mais curto exibido no botão fixado de ação rápida | -| `icon` | Não | Nome do ícone exibido ao lado do rótulo (por exemplo, `'IconBolt'`, `'IconSend'`) | -| `isPinned` | Não | Quando `true`, mostra o comando como um botão de ação rápida no canto superior direito da página | -| `availabilityType` | Não | Controla onde o comando aparece: `'GLOBAL'` (sempre disponível), `'RECORD_SELECTION'` (apenas quando registros estão selecionados) ou `'FALLBACK'` (exibido quando nenhum outro comando corresponde) | -| `availabilityObjectUniversalIdentifier` | Não | Restringe o comando a páginas de um tipo específico de objeto (por exemplo, somente em registros de Company) | -| `conditionalAvailabilityExpression` | Não | Uma expressão booleana para controlar dinamicamente se o comando é visível (veja abaixo) | - -#### Expressões de disponibilidade condicional - -O campo `conditionalAvailabilityExpression` permite controlar quando um comando é visível com base no contexto da página atual. Importe variáveis tipadas e operadores de `twenty-sdk` para construir expressões: - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - pageType, - numberOfSelectedRecords, - objectPermissions, - everyEquals, - isDefined, -} from 'twenty-sdk/front-component'; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'bulk-action', - component: BulkAction, - command: { - universalIdentifier: '...', - label: 'Bulk Update', - availabilityType: 'RECORD_SELECTION', - conditionalAvailabilityExpression: everyEquals( - objectPermissions, - 'canUpdateObjectRecords', - true, - ), - }, -}); -``` - -**Variáveis de contexto** — representam o estado atual da página: - -| Variável | Tipo | Descrição | -| ------------------------------ | --------- | --------------------------------------------------------------------------- | -| `pageType` | `string` | Tipo de página atual (por exemplo, `'RecordIndexPage'`, `'RecordShowPage'`) | -| `isInSidePanel` | `boolean` | Se o componente é renderizado em um painel lateral | -| `numberOfSelectedRecords` | `number` | Número de registros atualmente selecionados | -| `isSelectAll` | `boolean` | Se "selecionar tudo" está ativo | -| `selectedRecords` | `array` | Os objetos de registro selecionados | -| `favoriteRecordIds` | `array` | IDs dos registros marcados como favoritos | -| `objectPermissions` | `object` | Permissões para o tipo de objeto atual | -| `targetObjectReadPermissions` | `object` | Permissões de leitura para o objeto alvo | -| `targetObjectWritePermissions` | `object` | Permissões de escrita para o objeto alvo | -| `featureFlags` | `object` | Flags de recurso ativas | -| `objectMetadataItem` | `object` | Metadados do tipo de objeto atual | -| `hasAnySoftDeleteFilterOnView` | `boolean` | Se a visualização atual tem um filtro de soft-delete | - -**Operadores** — combine variáveis em expressões booleanas: - -| Operador | Descrição | -| ----------------------------------- | ---------------------------------------------------------------------- | -| `isDefined(value)` | `true` se o valor não for null/undefined | -| `isNonEmptyString(value)` | `true` se o valor for uma string não vazia | -| `includes(array, value)` | `true` se o array contiver o valor | -| `includesEvery(array, prop, value)` | `true` se a propriedade de cada item incluir o valor | -| `every(array, prop)` | `true` se a propriedade for truthy em cada item | -| `everyDefined(array, prop)` | `true` se a propriedade estiver definida em cada item | -| `everyEquals(array, prop, value)` | `true` se a propriedade for igual ao valor em cada item | -| `some(array, prop)` | `true` se a propriedade for truthy em pelo menos um item | -| `someDefined(array, prop)` | `true` se a propriedade estiver definida em pelo menos um item | -| `someEquals(array, prop, value)` | `true` se a propriedade for igual ao valor em pelo menos um item | -| `someNonEmptyString(array, prop)` | `true` se a propriedade for uma string não vazia em pelo menos um item | -| `none(array, prop)` | `true` se a propriedade for falsy em cada item | -| `noneDefined(array, prop)` | `true` se a propriedade for undefined em cada item | -| `noneEquals(array, prop, value)` | `true` se a propriedade não for igual ao valor em nenhum item | - -#### Recursos públicos - -Componentes de front-end podem acessar arquivos do diretório `public/` do app usando `getPublicAssetUrl`: - -```tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -const Logo = () => Logo; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'logo', - component: Logo, -}); -``` - -Veja a [seção de recursos públicos](#accessing-public-assets-with-getpublicasseturl) para obter detalhes. - -#### Estilização - -Componentes de front-end suportam várias abordagens de estilização. Você pode usar: - -* **Estilos inline** — `style={{ color: 'red' }}` -* **Componentes de UI do Twenty** — importe de `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar e mais) -* **Emotion** — CSS-in-JS com `@emotion/react` -* **Styled-components** — padrões `styled.div` -* **Tailwind CSS** — classes utilitárias -* **Qualquer biblioteca CSS-in-JS** compatível com React - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Button, Tag, Status } from 'twenty-sdk/ui'; - -const StyledWidget = () => { - return ( -
-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', - name: 'styled-widget', - component: StyledWidget, -}); -``` - -
- - - -As habilidades definem instruções e capacidades reutilizáveis que os agentes de IA podem usar no seu espaço de trabalho. Use `defineSkill()` para definir habilidades com validação integrada: - -```ts src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk/define'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -Pontos-chave: -* `name` é uma string de identificador exclusivo para a habilidade (recomenda-se kebab-case). -* `label` é o nome de exibição legível por humanos mostrado na UI. -* `content` contém as instruções da habilidade — este é o texto que o agente de IA usa. -* `icon` (opcional) define o ícone exibido na UI. -* `description` (opcional) fornece contexto adicional sobre a finalidade da habilidade. - - - - -Agentes são assistentes de IA que vivem dentro do seu espaço de trabalho. Use `defineAgent()` para criar agentes com um prompt de sistema personalizado: - -```ts src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk/define'; - -export default defineAgent({ - universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'Helps the sales team draft outreach emails and research prospects', - icon: 'IconRobot', - prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', -}); -``` - -Pontos-chave: -* `name` é a string de identificador exclusiva do agente (recomenda-se kebab-case). -* `label` é o nome de exibição mostrado na UI. -* `prompt` é o prompt do sistema que define o comportamento do agente. -* `description` (opcional) fornece contexto sobre o que o agente faz. -* `icon` (opcional) define o ícone exibido na UI. -* `modelId` (opcional) substitui o modelo de IA padrão usado pelo agente. - - - - -As visualizações são configurações salvas de como os registros de um objeto são exibidos — incluindo quais campos são visíveis, sua ordem e quaisquer filtros ou grupos aplicados. Use `defineView()` para enviar visualizações pré-configuradas com seu app: - -```ts src/views/example-view.ts -import { defineView, ViewKey } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; - -export default defineView({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'All example items', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - icon: 'IconList', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', - fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 200, - }, - ], -}); -``` - -Pontos-chave: -* `objectUniversalIdentifier` especifica a qual objeto esta visualização se aplica. -* `key` determina o tipo de visualização (por exemplo, `ViewKey.INDEX` para a visualização de lista principal). -* `fields` controla quais colunas aparecem e sua ordem. Cada campo referencia um `fieldMetadataUniversalIdentifier`. -* Você também pode definir `filters`, `filterGroups`, `groups` e `fieldGroups` para configurações mais avançadas. -* `position` controla a ordenação quando existem várias visualizações para o mesmo objeto. - - - - -Os itens do menu de navegação adicionam entradas personalizadas à barra lateral do espaço de trabalho. Use `defineNavigationMenuItem()` para vincular a visualizações, URLs externas ou objetos: - -```ts src/navigation-menu-items/example-navigation-menu-item.ts -import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; -import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; - -export default defineNavigationMenuItem({ - universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', - name: 'example-navigation-menu-item', - icon: 'IconList', - color: 'blue', - position: 0, - type: NavigationMenuItemType.VIEW, - viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, -}); -``` - -Pontos-chave: -* `type` determina para o que o item de menu aponta: `NavigationMenuItemType.VIEW` para uma visualização salva ou `NavigationMenuItemType.LINK` para uma URL externa. -* Para links de visualização, defina `viewUniversalIdentifier`. Para links externos, defina `link`. -* `position` controla a ordenação na barra lateral. -* `icon` e `color` (opcionais) personalizam a aparência. - - - - -Layouts de página permitem personalizar como uma página de detalhes do registro se parece — quais abas aparecem, quais widgets estão dentro de cada aba e como eles são organizados. Use `definePageLayout()` para enviar layouts personalizados com seu app: - -```ts src/page-layouts/example-record-page-layout.ts -import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; - -export default definePageLayout({ - universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', - name: 'Example Record Page', - type: 'RECORD_PAGE', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - tabs: [ - { - universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', - title: 'Hello World', - position: 50, - icon: 'IconWorld', - layoutMode: PageLayoutTabLayoutMode.CANVAS, - widgets: [ - { - universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', - title: 'Hello World', - type: 'FRONT_COMPONENT', - configuration: { - configurationType: 'FRONT_COMPONENT', - frontComponentUniversalIdentifier: - HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, - }, - }, - ], - }, - ], -}); -``` - -Pontos-chave: -* `type` geralmente é `'RECORD_PAGE'` para personalizar a visualização de detalhes de um objeto específico. -* `objectUniversalIdentifier` especifica a qual objeto este layout se aplica. -* Cada `tab` define uma seção da página com um `title`, `position` e `layoutMode` (`CANVAS` para layout livre). -* Cada `widget` dentro de uma aba pode renderizar um componente de front-end, uma lista de relações ou outros tipos de widget incorporados. -* `position` nas abas controla sua ordem. Use valores mais altos (por exemplo, 50) para colocar abas personalizadas após as nativas. - - -
- -## Recursos públicos (pasta `public/`) - -A pasta `public/` na raiz do seu app contém arquivos estáticos — imagens, ícones, fontes ou quaisquer outros recursos de que seu app precisa em tempo de execução. Esses arquivos são incluídos automaticamente nas compilações, sincronizados durante o modo de desenvolvimento e enviados para o servidor. - -Arquivos colocados em `public/` são: - -* **Publicamente acessíveis** — depois de sincronizados com o servidor, os recursos são servidos em uma URL pública. Não é necessária autenticação para acessá-los. -* **Disponíveis em componentes de front-end** — use URLs de recursos para exibir imagens, ícones ou qualquer mídia dentro de seus componentes React. -* **Disponíveis em funções lógicas** — referencie URLs de recursos em e-mails, respostas de API ou qualquer lógica no lado do servidor. -* **Usados para metadados do marketplace** — os campos `logoUrl` e `screenshots` em `defineApplication()` referenciam arquivos desta pasta (por exemplo, `public/logo.png`). Eles são exibidos no marketplace quando seu app é publicado. -* **Sincronizados automaticamente no modo de desenvolvimento** — quando você adiciona, atualiza ou exclui um arquivo em `public/`, ele é sincronizado automaticamente com o servidor. Não é necessário reiniciar. -* **Incluídos nas compilações** — `yarn twenty build` agrupa todos os recursos públicos na saída de distribuição. - -### Acessando recursos públicos com `getPublicAssetUrl` - -Use o helper `getPublicAssetUrl` de `twenty-sdk` para obter a URL completa de um arquivo no seu diretório `public/`. Funciona tanto em **funções lógicas** quanto em **componentes de front-end**. - -**Em uma função lógica:** - -```ts src/logic-functions/send-invoice.ts -import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; - -const handler = async (): Promise => { - const logoUrl = getPublicAssetUrl('logo.png'); - const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); - - // Fetch the file content (no auth required — public endpoint) - const response = await fetch(invoiceUrl); - const buffer = await response.arrayBuffer(); - - return { logoUrl, size: buffer.byteLength }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'a1b2c3d4-...', - name: 'send-invoice', - description: 'Sends an invoice with the app logo', - timeoutSeconds: 10, - handler, -}); -``` - -**Em um componente de front-end:** - -```tsx src/front-components/company-card.tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -export default defineFrontComponent(() => { - const logoUrl = getPublicAssetUrl('logo.png'); - - return App logo; -}); -``` - -O argumento `path` é relativo à pasta `public/` do seu app. Tanto `getPublicAssetUrl('logo.png')` quanto `getPublicAssetUrl('public/logo.png')` resolvem para a mesma URL — o prefixo `public/` é removido automaticamente, se presente. - -## Usando pacotes npm - -Você pode instalar e usar qualquer pacote npm no seu app. Tanto funções lógicas quanto componentes de front-end são empacotados com [esbuild](https://esbuild.github.io/), que incorpora todas as dependências na saída — nenhum `node_modules` é necessário em tempo de execução. - -### Instalando um pacote - -```bash filename="Terminal" -yarn add axios -``` - -Em seguida, importe-o no seu código: - -```ts src/logic-functions/fetch-data.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import axios from 'axios'; - -const handler = async (): Promise => { - const { data } = await axios.get('https://api.example.com/data'); - - return { data }; -}; - -export default defineLogicFunction({ - universalIdentifier: '...', - name: 'fetch-data', - description: 'Fetches data from an external API', - timeoutSeconds: 10, - handler, -}); -``` - -O mesmo vale para componentes de front-end: - -```tsx src/front-components/chart.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { format } from 'date-fns'; - -const DateWidget = () => { - return

Today is {format(new Date(), 'MMMM do, yyyy')}

; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Como o empacotamento funciona - -A etapa de build usa o esbuild para produzir um único arquivo independente por função lógica e por componente de front-end. Todos os pacotes importados são incorporados ao bundle. - -**Funções lógicas** são executadas em um ambiente Node.js. Módulos nativos do Node (`fs`, `path`, `crypto`, `http`, etc.) estão disponíveis e não precisam ser instalados. - -**Componentes de front-end** são executados em um Web Worker. Módulos nativos do Node **não** estão disponíveis — apenas APIs do navegador e pacotes npm que funcionam em um ambiente de navegador. - -Ambos os ambientes têm `twenty-client-sdk/core` e `twenty-client-sdk/metadata` disponíveis como módulos pré-fornecidos — eles não são empacotados, mas resolvidos em tempo de execução pelo servidor. - -## Gerando entidades com `yarn twenty add` - -Em vez de criar arquivos de entidade manualmente, você pode usar o scaffolder interativo: - -```bash filename="Terminal" -yarn twenty add -``` - -Isso solicita que você escolha um tipo de entidade e orienta você pelos campos obrigatórios. Ele gera um arquivo pronto para uso com um `universalIdentifier` estável e a chamada correta de `defineEntity()`. - -Você também pode passar o tipo de entidade diretamente para pular o primeiro prompt: - -```bash filename="Terminal" -yarn twenty add object -yarn twenty add logicFunction -yarn twenty add frontComponent -``` - -### Tipos de entidade disponíveis - -| Tipo de entidade | Comando | Arquivo gerado | -| ------------------------- | ------------------------------------ | ------------------------------------------------------- | -| Objeto | `yarn twenty add object` | `src/objects/\.ts` | -| Campo | `yarn twenty add field` | `src/fields/\.ts` | -| Função lógica | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | -| Componente de front-end | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | -| Função | `yarn twenty add role` | `src/roles/\.ts` | -| Habilidade | `yarn twenty add skill` | `src/skills/\.ts` | -| Agente | `yarn twenty add agent` | `src/agents/\.ts` | -| Vista | `yarn twenty add view` | `src/views/\.ts` | -| Item do menu de navegação | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | -| Layout da página | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | - -### O que o scaffolder gera - -Cada tipo de entidade tem seu próprio modelo. Por exemplo, `yarn twenty add object` solicita: - -1. **Nome (singular)** — por exemplo, `invoice` -2. **Nome (plural)** — por exemplo, `invoices` -3. **Rótulo (singular)** — preenchido automaticamente a partir do nome (por exemplo, `Invoice`) -4. **Rótulo (plural)** — preenchido automaticamente (por exemplo, `Invoices`) -5. **Criar uma view e um item de navegação?** — se você responder sim, o scaffolder também gera uma view correspondente e um link na barra lateral para o novo objeto. - -Outros tipos de entidade têm prompts mais simples — a maioria pede apenas um nome. - -O tipo de entidade `field` é mais detalhado: ele solicita o nome do campo, rótulo, tipo (a partir de uma lista de todos os tipos de campo disponíveis como `TEXT`, `NUMBER`, `SELECT`, `RELATION`, etc.) e o `universalIdentifier` do objeto de destino. - -### Caminho de saída personalizado - -Use a opção `--path` para colocar o arquivo gerado em um local personalizado: - -```bash filename="Terminal" -yarn twenty add logicFunction --path src/custom-folder -``` - -## Clientes de API tipados (twenty-client-sdk) - -O pacote `twenty-client-sdk` fornece dois clientes GraphQL tipados para interagir com a API do Twenty a partir das suas funções de lógica e componentes de front-end. - -| Cliente | Importar | Endpoint | Gerado? | -| ------------------- | ---------------------------- | -------------------------------------------------------------------- | -------------------------- | -| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — dados do espaço de trabalho (registros, objetos) | Sim, em tempo de dev/build | -| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — configuração do espaço de trabalho, upload de arquivos | Não, vem pré-compilado | - - - - -`CoreApiClient` é o cliente principal para consultar e mutar dados do espaço de trabalho. Ele é **gerado a partir do schema do seu espaço de trabalho** durante `yarn twenty dev` ou `yarn twenty build`, então é totalmente tipado para corresponder aos seus objetos e campos. - -```ts -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const client = new CoreApiClient(); - -// Query records -const { companies } = await client.query({ - companies: { - edges: { - node: { - id: true, - name: true, - domainName: { - primaryLinkLabel: true, - primaryLinkUrl: true, - }, - }, - }, - }, -}); - -// Create a record -const { createCompany } = await client.mutation({ - createCompany: { - __args: { - data: { - name: 'Acme Corp', - }, - }, - id: true, - name: true, - }, -}); -``` - -O cliente usa uma sintaxe de selection-set: passe `true` para incluir um campo, use `__args` para argumentos e aninhe objetos para relações. Você tem preenchimento automático e verificação de tipos completos com base no schema do seu espaço de trabalho. - - -**CoreApiClient é gerado em tempo de dev/build.** Se você usá-lo sem executar primeiro `yarn twenty dev` ou `yarn twenty build`, ele lançará um erro. A geração ocorre automaticamente — a CLI analisa o schema GraphQL do seu espaço de trabalho e gera um cliente tipado usando `@genql/cli`. - - -#### Usando CoreSchema para anotações de tipo - -`CoreSchema` fornece tipos TypeScript que correspondem aos objetos do seu espaço de trabalho — útil para tipar o estado de componentes ou parâmetros de função: - -```ts -import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; -import { useState } from 'react'; - -const [company, setCompany] = useState< - Pick | undefined ->(undefined); - -const client = new CoreApiClient(); -const result = await client.query({ - company: { - __args: { filter: { position: { eq: 1 } } }, - id: true, - name: true, - }, -}); -setCompany(result.company); -``` - - - - -`MetadataApiClient` é fornecido pré-compilado com o SDK (não é necessário gerar). Ele consulta o endpoint `/metadata` para configuração do espaço de trabalho, aplicativos e upload de arquivos. - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const metadataClient = new MetadataApiClient(); - -// List first 10 objects in the workspace -const { objects } = await metadataClient.query({ - objects: { - edges: { - node: { - id: true, - nameSingular: true, - namePlural: true, - labelSingular: true, - isCustom: true, - }, - }, - __args: { - filter: {}, - paging: { first: 10 }, - }, - }, -}); -``` - -#### Carregamento de arquivos - -`MetadataApiClient` inclui um método `uploadFile` para anexar arquivos a campos do tipo arquivo: - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -| Parâmetro | Tipo | Descrição | -| ---------------------------------- | -------- | -------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | O conteúdo bruto do arquivo | -| `filename` | `string` | O nome do arquivo (usado para armazenamento e exibição) | -| `contentType` | `string` | Tipo MIME (padrão para `application/octet-stream` se omitido) | -| `fieldMetadataUniversalIdentifier` | `string` | O `universalIdentifier` do campo do tipo arquivo no seu objeto | - -Pontos-chave: -* Usa o `universalIdentifier` do campo (não o ID específico do espaço de trabalho), de modo que seu código de upload funcione em qualquer espaço de trabalho onde seu app esteja instalado. -* A `url` retornada é um URL assinado que você pode usar para acessar o arquivo enviado. - - - - - - Quando seu código é executado no Twenty (funções de lógica ou componentes de front-end), a plataforma injeta credenciais como variáveis de ambiente: - - * `TWENTY_API_URL` — URL base da API do Twenty - * `TWENTY_APP_ACCESS_TOKEN` — Chave de curta duração com escopo para o papel de função padrão do seu aplicativo - - Você **não** precisa passá-las para os clientes — eles leem de `process.env` automaticamente. As permissões da chave de API são determinadas pelo papel referenciado em `defaultRoleUniversalIdentifier` no seu `application-config.ts`. - - -## Testando seu aplicativo - -O SDK fornece APIs programáticas que permitem compilar, implantar, instalar e desinstalar seu aplicativo a partir de código de teste. Em conjunto com [Vitest](https://vitest.dev/) e os clientes de API tipados, você pode escrever testes de integração que verificam que seu aplicativo funciona de ponta a ponta em um servidor Twenty real. - -### Configuração - -O aplicativo gerado pelo scaffolder já inclui o Vitest. Se você configurá-lo manualmente, instale as dependências: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Crie um `vitest.config.ts` na raiz do seu aplicativo: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Crie um arquivo de configuração que verifique se o servidor está acessível antes da execução dos testes: - -```ts src/__tests__/setup-test.ts -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { beforeAll } from 'vitest'; - -const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; -const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### APIs programáticas do SDK - -O subcaminho `twenty-sdk/cli` exporta funções que você pode chamar diretamente a partir do código de teste: - -| Função | Descrição | -| -------------- | ------------------------------------------------------------ | -| `appBuild` | Compilar o aplicativo e, opcionalmente, empacotar um tarball | -| `appDeploy` | Enviar um tarball para o servidor | -| `appInstall` | Instalar o aplicativo no espaço de trabalho ativo | -| `appUninstall` | Desinstalar o aplicativo do espaço de trabalho ativo | - -Cada função retorna um objeto de resultado com `success: boolean` e `data` ou `error`. - -### Escrevendo um teste de integração - -Aqui está um exemplo completo que compila, implanta e instala o aplicativo e, em seguida, verifica se ele aparece no espaço de trabalho: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Executando testes - -Certifique-se de que seu servidor Twenty local esteja em execução e, em seguida: - -```bash filename="Terminal" -yarn test -``` - -Ou no modo watch durante o desenvolvimento: - -```bash filename="Terminal" -yarn test:watch -``` - -### Verificação de tipos - -Você também pode executar a verificação de tipos no seu aplicativo sem executar os testes: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Isso executa `tsc --noEmit` e informa quaisquer erros de tipo. - -## Referência da CLI - -Além de `dev`, `build`, `add` e `typecheck`, a CLI fornece comandos para executar funções, visualizar logs e gerenciar instalações de aplicativos. - -### Executando funções (`yarn twenty exec`) - -Execute manualmente uma função de lógica sem acioná-la via HTTP, cron ou evento de banco de dados: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Visualizando logs de funções (`yarn twenty logs`) - -Transmita os logs de execução das funções de lógica do seu aplicativo: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - - -Isso é diferente de `yarn twenty server logs`, que mostra os logs do contêiner Docker. `yarn twenty logs` mostra os logs de execução de funções do seu aplicativo a partir do servidor Twenty. - - -### Desinstalando um aplicativo (`yarn twenty uninstall`) - -Remova seu aplicativo do espaço de trabalho ativo: - -```bash filename="Terminal" -yarn twenty uninstall - -# Skip the confirmation prompt -yarn twenty uninstall --yes -``` - -## Gerenciando remotos - -Um **remoto** é um servidor Twenty ao qual seu aplicativo se conecta. Durante a configuração, o gerador de scaffold cria um para você automaticamente. Você pode adicionar mais remotos ou alternar entre eles a qualquer momento. - -```bash filename="Terminal" -# Add a new remote (opens a browser for OAuth login) -yarn twenty remote add - -# Connect to a local Twenty server (auto-detects port 2020 or 3000) -yarn twenty remote add --local - -# Add a remote non-interactively (useful for CI) -yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote - -# List all configured remotes -yarn twenty remote list - -# Switch the active remote -yarn twenty remote switch -``` - -Suas credenciais são armazenadas em `~/.twenty/config.json`. - -## CI com GitHub Actions - -O gerador de scaffold cria um workflow do GitHub Actions pronto para uso em `.github/workflows/ci.yml`. Ele executa seus testes de integração automaticamente a cada push para `main` e em pull requests. - -O workflow: - -1. Faz checkout do seu código -2. Inicializa um servidor Twenty temporário usando a ação `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` -3. Instala as dependências com `yarn install --immutable` -4. Executa `yarn test` com `TWENTY_API_URL` e `TWENTY_API_KEY` injetados a partir das saídas da ação - -```yaml .github/workflows/ci.yml -name: CI - -on: - push: - branches: - - main - pull_request: {} - -env: - TWENTY_VERSION: latest - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Spawn Twenty instance - id: twenty - uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main - with: - twenty-version: ${{ env.TWENTY_VERSION }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Enable Corepack - run: corepack enable - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - - name: Install dependencies - run: yarn install --immutable - - - name: Run integration tests - run: yarn test - env: - TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} - TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} -``` - -Você não precisa configurar nenhum segredo — a ação `spawn-twenty-docker-image` inicia um servidor Twenty efêmero diretamente no runner e fornece os detalhes de conexão. O segredo `GITHUB_TOKEN` é fornecido automaticamente pelo GitHub. - -Para fixar uma versão específica do Twenty em vez de `latest`, altere a variável de ambiente `TWENTY_VERSION` no topo do workflow. +* **`yarn twenty dev`** — watches your source files and live-syncs changes to a connected Twenty server. The typed API client is regenerated automatically when the schema changes. +* **`yarn twenty build`** — compiles TypeScript, bundles logic functions and front components with esbuild, and produces a manifest. +* **Pre/post-install hooks** — optional logic functions that run during installation. See [Logic Functions](/l/pt/developers/extend/apps/logic-functions) for details. + +## Próximos passos + + + + Define objects, fields, roles, and relations. + + + Server-side functions with HTTP, cron, and event triggers. + + + Sandboxed React components inside Twenty's UI. + + + Views, navigation items, and record page layouts. + + + AI skills and agents with custom prompts. + + + CLI commands, testing, assets, remotes, and CI. + + + Deploy to a server or publish to the marketplace. + + diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/cli-and-testing.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/cli-and-testing.mdx new file mode 100644 index 00000000000..f0048d73622 --- /dev/null +++ b/packages/twenty-docs/l/pt/developers/extend/apps/cli-and-testing.mdx @@ -0,0 +1,434 @@ +--- +title: CLI & Testing +description: CLI commands, testing setup, public assets, npm packages, remotes, and CI configuration. +icon: terminal +--- + +## Recursos públicos (pasta `public/`) + +A pasta `public/` na raiz do seu app contém arquivos estáticos — imagens, ícones, fontes ou quaisquer outros recursos de que seu app precisa em tempo de execução. Esses arquivos são incluídos automaticamente nas compilações, sincronizados durante o modo de desenvolvimento e enviados para o servidor. + +Arquivos colocados em `public/` são: + +* **Publicamente acessíveis** — depois de sincronizados com o servidor, os recursos são servidos em uma URL pública. Não é necessária autenticação para acessá-los. +* **Disponíveis em componentes de front-end** — use URLs de recursos para exibir imagens, ícones ou qualquer mídia dentro de seus componentes React. +* **Disponíveis em funções lógicas** — referencie URLs de recursos em e-mails, respostas de API ou qualquer lógica no lado do servidor. +* **Usados para metadados do marketplace** — os campos `logoUrl` e `screenshots` em `defineApplication()` referenciam arquivos desta pasta (por exemplo, `public/logo.png`). Eles são exibidos no marketplace quando seu app é publicado. +* **Sincronizados automaticamente no modo de desenvolvimento** — quando você adiciona, atualiza ou exclui um arquivo em `public/`, ele é sincronizado automaticamente com o servidor. Não é necessário reiniciar. +* **Incluídos nas compilações** — `yarn twenty build` agrupa todos os recursos públicos na saída de distribuição. + +### Acessando recursos públicos com `getPublicAssetUrl` + +Use o helper `getPublicAssetUrl` de `twenty-sdk` para obter a URL completa de um arquivo no seu diretório `public/`. Funciona tanto em **funções lógicas** quanto em **componentes de front-end**. + +**Em uma função lógica:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; + +const handler = async (): Promise => { + const logoUrl = getPublicAssetUrl('logo.png'); + const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); + + // Fetch the file content (no auth required — public endpoint) + const response = await fetch(invoiceUrl); + const buffer = await response.arrayBuffer(); + + return { logoUrl, size: buffer.byteLength }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'a1b2c3d4-...', + name: 'send-invoice', + description: 'Sends an invoice with the app logo', + timeoutSeconds: 10, + handler, +}); +``` + +**Em um componente de front-end:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +O argumento `path` é relativo à pasta `public/` do seu app. Tanto `getPublicAssetUrl('logo.png')` quanto `getPublicAssetUrl('public/logo.png')` resolvem para a mesma URL — o prefixo `public/` é removido automaticamente, se presente. + +## Usando pacotes npm + +Você pode instalar e usar qualquer pacote npm no seu app. Tanto funções lógicas quanto componentes de front-end são empacotados com [esbuild](https://esbuild.github.io/), que incorpora todas as dependências na saída — nenhum `node_modules` é necessário em tempo de execução. + +### Instalando um pacote + +```bash filename="Terminal" +yarn add axios +``` + +Em seguida, importe-o no seu código: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +O mesmo vale para componentes de front-end: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### Como o empacotamento funciona + +A etapa de build usa o esbuild para produzir um único arquivo independente por função lógica e por componente de front-end. Todos os pacotes importados são incorporados ao bundle. + +**Funções lógicas** são executadas em um ambiente Node.js. Módulos nativos do Node (`fs`, `path`, `crypto`, `http`, etc.) estão disponíveis e não precisam ser instalados. + +**Componentes de front-end** são executados em um Web Worker. Módulos nativos do Node **não** estão disponíveis — apenas APIs do navegador e pacotes npm que funcionam em um ambiente de navegador. + +Ambos os ambientes têm `twenty-client-sdk/core` e `twenty-client-sdk/metadata` disponíveis como módulos pré-fornecidos — eles não são empacotados, mas resolvidos em tempo de execução pelo servidor. + +## Testando seu aplicativo + +O SDK fornece APIs programáticas que permitem compilar, implantar, instalar e desinstalar seu aplicativo a partir de código de teste. Em conjunto com [Vitest](https://vitest.dev/) e os clientes de API tipados, você pode escrever testes de integração que verificam que seu aplicativo funciona de ponta a ponta em um servidor Twenty real. + +### Configuração + +O aplicativo gerado pelo scaffolder já inclui o Vitest. Se você configurá-lo manualmente, instale as dependências: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Crie um `vitest.config.ts` na raiz do seu aplicativo: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Crie um arquivo de configuração que verifique se o servidor está acessível antes da execução dos testes: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### APIs programáticas do SDK + +O subcaminho `twenty-sdk/cli` exporta funções que você pode chamar diretamente a partir do código de teste: + +| Função | Descrição | +| -------------- | ------------------------------------------------------------ | +| `appBuild` | Compilar o aplicativo e, opcionalmente, empacotar um tarball | +| `appDeploy` | Enviar um tarball para o servidor | +| `appInstall` | Instalar o aplicativo no espaço de trabalho ativo | +| `appUninstall` | Desinstalar o aplicativo do espaço de trabalho ativo | + +Cada função retorna um objeto de resultado com `success: boolean` e `data` ou `error`. + +### Escrevendo um teste de integração + +Aqui está um exemplo completo que compila, implanta e instala o aplicativo e, em seguida, verifica se ele aparece no espaço de trabalho: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Executando testes + +Certifique-se de que seu servidor Twenty local esteja em execução e, em seguida: + +```bash filename="Terminal" +yarn test +``` + +Ou no modo watch durante o desenvolvimento: + +```bash filename="Terminal" +yarn test:watch +``` + +### Verificação de tipos + +Você também pode executar a verificação de tipos no seu aplicativo sem executar os testes: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +Isso executa `tsc --noEmit` e informa quaisquer erros de tipo. + +## Referência da CLI + +Além de `dev`, `build`, `add` e `typecheck`, a CLI fornece comandos para executar funções, visualizar logs e gerenciar instalações de aplicativos. + +### Executando funções (`yarn twenty exec`) + +Execute manualmente uma função de lógica sem acioná-la via HTTP, cron ou evento de banco de dados: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute the post-install function +yarn twenty exec --postInstall +``` + +### Visualizando logs de funções (`yarn twenty logs`) + +Transmita os logs de execução das funções de lógica do seu aplicativo: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +Isso é diferente de `yarn twenty server logs`, que mostra os logs do contêiner Docker. `yarn twenty logs` mostra os logs de execução de funções do seu aplicativo a partir do servidor Twenty. + + +### Desinstalando um aplicativo (`yarn twenty uninstall`) + +Remova seu aplicativo do espaço de trabalho ativo: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` + +## Gerenciando remotos + +Um **remoto** é um servidor Twenty ao qual seu aplicativo se conecta. Durante a configuração, o gerador de scaffold cria um para você automaticamente. Você pode adicionar mais remotos ou alternar entre eles a qualquer momento. + +```bash filename="Terminal" +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add + +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local + +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote + +# List all configured remotes +yarn twenty remote list + +# Switch the active remote +yarn twenty remote switch +``` + +Suas credenciais são armazenadas em `~/.twenty/config.json`. + +## CI com GitHub Actions + +O gerador de scaffold cria um workflow do GitHub Actions pronto para uso em `.github/workflows/ci.yml`. Ele executa seus testes de integração automaticamente a cada push para `main` e em pull requests. + +O workflow: + +1. Faz checkout do seu código +2. Inicializa um servidor Twenty temporário usando a ação `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` +3. Instala as dependências com `yarn install --immutable` +4. Executa `yarn test` com `TWENTY_API_URL` e `TWENTY_API_KEY` injetados a partir das saídas da ação + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +Você não precisa configurar nenhum segredo — a ação `spawn-twenty-docker-image` inicia um servidor Twenty efêmero diretamente no runner e fornece os detalhes de conexão. O segredo `GITHUB_TOKEN` é fornecido automaticamente pelo GitHub. + +Para fixar uma versão específica do Twenty em vez de `latest`, altere a variável de ambiente `TWENTY_VERSION` no topo do workflow. diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/data-model.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/data-model.mdx new file mode 100644 index 00000000000..633e07ba191 --- /dev/null +++ b/packages/twenty-docs/l/pt/developers/extend/apps/data-model.mdx @@ -0,0 +1,494 @@ +--- +title: Modelo de dados +description: Define objects, fields, roles, and application metadata with the Twenty SDK. +icon: database +--- + +The `twenty-sdk` package provides `defineEntity` functions to declare your app's data model. Você deve usar `export default defineEntity({...})` para que o SDK detecte suas entidades. Essas funções validam sua configuração em tempo de compilação e oferecem autocompletar na IDE e segurança de tipos. + + + **A organização de arquivos fica a seu critério.** + A detecção de entidades é baseada em AST — o SDK encontra chamadas a `export default defineEntity(...)` independentemente de onde o arquivo esteja. Agrupar arquivos por tipo (por exemplo, `logic-functions/`, `roles/`) é apenas uma convenção, não um requisito. + + + + + +Papéis encapsulam permissões sobre os objetos e ações do seu espaço de trabalho. + +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export default defineRole({ + universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', + label: 'My new role', + description: 'A role that can be used in your workspace', + canReadAllObjectRecords: false, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + canReadObjectRecords: true, + canUpdateObjectRecords: true, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + }, + ], + fieldPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + fieldUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, + canReadFieldValue: false, + canUpdateFieldValue: false, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Todo app deve ter exatamente uma chamada a `defineApplication` que descreve: + +* **Identidade**: identificadores, nome de exibição e descrição. +* **Permissões**: qual papel é usado por suas funções e componentes de front-end. +* **Variáveis (opcional)**: pares chave–valor expostos às suas funções como variáveis de ambiente. +* **(Opcional) Funções de pré-instalação/pós-instalação**: funções de lógica que são executadas antes ou depois da instalação. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk/define'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +Notas: +* Os campos `universalIdentifier` são IDs determinísticos que você controla. Gere-os uma vez e mantenha-os estáveis entre sincronizações. +* `applicationVariables` tornam-se variáveis de ambiente para suas funções e componentes de front-end (por exemplo, `DEFAULT_RECIPIENT_NAME` fica disponível como `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` deve fazer referência a um papel definido com `defineRole()` (veja acima). +* As funções de pré-instalação e pós-instalação são detectadas automaticamente durante a construção do manifesto — você não precisa referenciá-las em `defineApplication()`. + +#### Metadados do Marketplace + +Se você planeja [publicar seu app](/l/pt/developers/extend/apps/publishing), estes campos opcionais controlam como seu app aparece no marketplace: + +| Campo | Descrição | +| ------------------ | ----------------------------------------------------------------------------------------------------------------- | +| `author` | Nome do autor ou da empresa | +| `category` | Categoria do app para filtragem no marketplace | +| `logoUrl` | Caminho para o logo do seu app (por exemplo, `public/logo.png`) | +| `screenshots` | Array de caminhos de capturas de tela (por exemplo, `public/screenshot-1.png`) | +| `aboutDescription` | Descrição em markdown mais longa para a aba "Sobre". Se omitido, o marketplace usa o `README.md` do pacote no npm | +| `websiteUrl` | Link para seu site | +| `termsUrl` | Link para os Termos de Serviço | +| `emailSupport` | Endereço de e-mail de suporte | +| `issueReportUrl` | Link para o rastreador de problemas | + +#### Papéis e permissões + +O campo `defaultRoleUniversalIdentifier` em `application-config.ts` designa o papel padrão usado pelas funções de lógica e pelos componentes de front-end do seu app. Veja `defineRole` acima para detalhes. + +* O token em tempo de execução injetado como `TWENTY_APP_ACCESS_TOKEN` é derivado desse papel. +* O cliente tipado é restrito às permissões concedidas a esse papel. +* Siga o princípio do menor privilégio: crie um papel dedicado com apenas as permissões de que suas funções precisam. + +##### Papel de função padrão + +Ao criar um novo app com o scaffold, a CLI cria um arquivo de papel padrão: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk/define'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + label: 'Default function role', + description: 'Default role for function Twenty client', + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [], + fieldPermissions: [], + permissionFlags: [], +}); +``` + +O `universalIdentifier` desse papel é referenciado em `application-config.ts` como `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** define o que o papel pode fazer. +* **application-config.ts** aponta para esse papel para que suas funções herdem suas permissões. + +Notas: +* Comece pelo papel gerado pelo scaffold e depois restrinja-o progressivamente seguindo o princípio do menor privilégio. +* Substitua `objectPermissions` e `fieldPermissions` pelos objetos e campos de que suas funções realmente precisam. +* `permissionFlags` controlam o acesso a recursos em nível de plataforma. Mantenha-os no mínimo necessário. +* Veja um exemplo funcional: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + + +Objetos personalizados descrevem tanto o esquema quanto o comportamento de registros no seu espaço de trabalho. Use `defineObject()` para definir objetos com validação integrada: + +```ts postCard.object.ts +import { defineObject, FieldType } from 'twenty-sdk/define'; + +enum PostCardStatus { + DRAFT = 'DRAFT', + SENT = 'SENT', + DELIVERED = 'DELIVERED', + RETURNED = 'RETURNED', +} + +export default defineObject({ + universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', + nameSingular: 'postCard', + namePlural: 'postCards', + labelSingular: 'Post Card', + labelPlural: 'Post Cards', + description: 'A post card object', + icon: 'IconMail', + fields: [ + { + universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', + name: 'content', + type: FieldType.TEXT, + label: 'Content', + description: "Postcard's content", + icon: 'IconAbc', + }, + { + universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', + name: 'recipientName', + type: FieldType.FULL_NAME, + label: 'Recipient name', + icon: 'IconUser', + }, + { + universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', + name: 'recipientAddress', + type: FieldType.ADDRESS, + label: 'Recipient address', + icon: 'IconHome', + }, + { + universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', + name: 'status', + type: FieldType.SELECT, + label: 'Status', + icon: 'IconSend', + defaultValue: `'${PostCardStatus.DRAFT}'`, + options: [ + { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, + { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, + { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, + { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, + ], + }, + { + universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', + name: 'deliveredAt', + type: FieldType.DATE_TIME, + label: 'Delivered at', + icon: 'IconCheck', + isNullable: true, + defaultValue: null, + }, + ], +}); +``` + +Pontos-chave: + +* Use `defineObject()` para validação integrada e melhor suporte na IDE. +* O `universalIdentifier` deve ser exclusivo e estável entre implantações. +* Cada campo requer `name`, `type`, `label` e seu próprio `universalIdentifier` estável. +* O array `fields` é opcional — você pode definir objetos sem campos personalizados. +* Você pode criar novos objetos usando `yarn twenty add`, que orienta você sobre nomeação, campos e relacionamentos. + + +**Os campos base são criados automaticamente.** Quando você define um objeto personalizado, o Twenty adiciona automaticamente campos padrão +como `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` e `deletedAt`. +Você não precisa definir esses no seu array `fields` — adicione apenas seus campos personalizados. +Você pode substituir os campos padrão definindo um campo com o mesmo nome no seu array `fields`, +mas isso não é recomendado. + + + + + +Use `defineField()` para adicionar campos a objetos que não são seus — como objetos padrão do Twenty (Person, Company, etc.). ou a objetos de outros apps. Ao contrário dos campos inline em `defineObject()`, os campos independentes exigem um `objectUniversalIdentifier` para especificar qual objeto eles estendem: + +```ts src/fields/company-loyalty-tier.field.ts +import { defineField, FieldType } from 'twenty-sdk/define'; + +export default defineField({ + universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', + objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object + name: 'loyaltyTier', + type: FieldType.SELECT, + label: 'Loyalty Tier', + icon: 'IconStar', + options: [ + { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, + { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, + { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, + ], +}); +``` + +Pontos-chave: +* `objectUniversalIdentifier` identifica o objeto de destino. Para objetos padrão, use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` exportado de `twenty-sdk`. +* Ao definir campos inline em `defineObject()`, você não precisa de `objectUniversalIdentifier` — ele é herdado do objeto pai. +* `defineField()` é a única forma de adicionar campos a objetos que você não criou com `defineObject()`. + + + + +As relações conectam objetos entre si. No Twenty, as relações são sempre **bidirecionais** — você define ambos os lados, e cada lado faz referência ao outro. + +Existem dois tipos de relação: + +| Tipo de relação | Descrição | Tem chave estrangeira? | +| --------------- | ----------------------------------------------------------------- | ---------------------- | +| `MANY_TO_ONE` | Muitos registros deste objeto apontam para um registro do destino | Sim (`joinColumnName`) | +| `ONE_TO_MANY` | Um registro deste objeto possui muitos registros do destino | Não (lado inverso) | + +#### Como as relações funcionam + +Toda relação requer **dois campos** que façam referência um ao outro: + +1. O lado **MANY_TO_ONE** — fica no objeto que contém a chave estrangeira +2. O lado **ONE_TO_MANY** — fica no objeto que possui a coleção + +Ambos os campos usam `FieldType.RELATION` e fazem referência cruzada um ao outro via `relationTargetFieldMetadataUniversalIdentifier`. + +#### Exemplo: Um cartão postal tem muitos destinatários + +Suponha que um `PostCard` possa ser enviado para muitos registros `PostCardRecipient`. Cada destinatário pertence a exatamente um cartão postal. + +**Etapa 1: Defina o lado ONE_TO_MANY em PostCard** (o lado "um"): + +```ts src/fields/post-card-recipients-on-post-card.field.ts +import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; +// Import from the other side +import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; + +export default defineField({ + universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCardRecipients', + label: 'Post Card Recipients', + icon: 'IconUsers', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); +``` + +**Etapa 2: Defina o lado MANY_TO_ONE em PostCardRecipient** (o lado "muitos" — contém a chave estrangeira): + +```ts src/fields/post-card-on-post-card-recipient.field.ts +import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; +// Import from the other side +import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; + +export default defineField({ + universalIdentifier: POST_CARD_FIELD_ID, + objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + icon: 'IconMail', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, +}); +``` + + +**Importações circulares:** Ambos os campos de relação referenciam o `universalIdentifier` um do outro. Para evitar problemas de importação circular, exporte os IDs dos seus campos como constantes nomeadas de cada arquivo e importe-os no outro arquivo. O sistema de build resolve isso em tempo de compilação. + + +#### Relacionando a objetos padrão + +Para criar uma relação com um objeto integrado do Twenty (Person, Company, etc.), use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: + +```ts src/fields/person-on-self-hosting-user.field.ts +import { + defineField, + FieldType, + RelationType, + OnDeleteAction, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; +import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; + +export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; +export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; + +export default defineField({ + universalIdentifier: PERSON_FIELD_ID, + objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'person', + label: 'Person', + description: 'Person matching with the self hosting user', + isNullable: true, + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'personId', + }, +}); +``` + +#### Propriedades de campos de relação + +| Propriedade | Obrigatório | Descrição | +| ------------------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------- | +| `type` | Sim | Deve ser `FieldType.RELATION` | +| `relationTargetObjectMetadataUniversalIdentifier` | Sim | O `universalIdentifier` do objeto de destino | +| `relationTargetFieldMetadataUniversalIdentifier` | Sim | O `universalIdentifier` do campo correspondente no objeto de destino | +| `universalSettings.relationType` | Sim | `RelationType.MANY_TO_ONE` ou `RelationType.ONE_TO_MANY` | +| `universalSettings.onDelete` | Apenas para MANY_TO_ONE | O que acontece quando o registro referenciado é excluído: `CASCADE`, `SET_NULL`, `RESTRICT` ou `NO_ACTION` | +| `universalSettings.joinColumnName` | Apenas para MANY_TO_ONE | Nome da coluna no banco de dados para a chave estrangeira (por exemplo, `postCardId`) | + +#### Campos de relação inline em defineObject + +Você também pode definir campos de relação diretamente dentro de `defineObject()`. Nesse caso, omita `objectUniversalIdentifier` — ele é herdado do objeto pai: + +```ts +export default defineObject({ + universalIdentifier: '...', + nameSingular: 'postCardRecipient', + // ... + fields: [ + { + universalIdentifier: POST_CARD_FIELD_ID, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, + }, + // ... other fields + ], +}); +``` + + + +## Gerando entidades com `yarn twenty add` + +Em vez de criar arquivos de entidade manualmente, você pode usar o scaffolder interativo: + +```bash filename="Terminal" +yarn twenty add +``` + +Isso solicita que você escolha um tipo de entidade e orienta você pelos campos obrigatórios. Ele gera um arquivo pronto para uso com um `universalIdentifier` estável e a chamada correta de `defineEntity()`. + +Você também pode passar o tipo de entidade diretamente para pular o primeiro prompt: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Tipos de entidade disponíveis + +| Tipo de entidade | Comando | Arquivo gerado | +| ------------------------- | ------------------------------------ | ------------------------------------------------------- | +| Objeto | `yarn twenty add object` | `src/objects/\.ts` | +| Campo | `yarn twenty add field` | `src/fields/\.ts` | +| Função lógica | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | +| Componente de front-end | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | +| Papel | `yarn twenty add role` | `src/roles/\.ts` | +| Habilidade | `yarn twenty add skill` | `src/skills/\.ts` | +| Agente | `yarn twenty add agent` | `src/agents/\.ts` | +| Vista | `yarn twenty add view` | `src/views/\.ts` | +| Item do menu de navegação | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | +| Layout da página | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | + +### O que o scaffolder gera + +Cada tipo de entidade tem seu próprio modelo. Por exemplo, `yarn twenty add object` solicita: + +1. **Nome (singular)** — por exemplo, `invoice` +2. **Nome (plural)** — por exemplo, `invoices` +3. **Rótulo (singular)** — preenchido automaticamente a partir do nome (por exemplo, `Invoice`) +4. **Rótulo (plural)** — preenchido automaticamente (por exemplo, `Invoices`) +5. **Criar uma view e um item de navegação?** — se você responder sim, o scaffolder também gera uma view correspondente e um link na barra lateral para o novo objeto. + +Outros tipos de entidade têm prompts mais simples — a maioria pede apenas um nome. + +O tipo de entidade `field` é mais detalhado: ele solicita o nome do campo, rótulo, tipo (a partir de uma lista de todos os tipos de campo disponíveis como `TEXT`, `NUMBER`, `SELECT`, `RELATION`, etc.) e o `universalIdentifier` do objeto de destino. + +### Caminho de saída personalizado + +Use a opção `--path` para colocar o arquivo gerado em um local personalizado: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/front-components.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/front-components.mdx new file mode 100644 index 00000000000..e9e3c168d46 --- /dev/null +++ b/packages/twenty-docs/l/pt/developers/extend/apps/front-components.mdx @@ -0,0 +1,419 @@ +--- +title: Componentes de front-end +description: Build React components that render inside Twenty's UI with sandboxed isolation. +icon: window-maximize +--- + +Componentes de front-end são componentes React que renderizam diretamente dentro da UI do Twenty. Eles são executados em um Web Worker isolado usando Remote DOM — seu código é sandboxed, mas renderiza nativamente na página, não em um iframe. + +## Onde os componentes de front-end podem ser usados + +Os componentes de front-end podem ser renderizados em dois locais dentro do Twenty: + +* **Painel lateral** — Componentes de front-end não headless abrem no painel lateral direito. Este é o comportamento padrão quando um componente de front-end é acionado pelo menu de comandos. +* **Widgets (painéis e páginas de registro)** — Componentes de front-end podem ser incorporados como widgets nos layouts de página. Ao configurar um painel ou o layout de uma página de registro, os usuários podem adicionar um widget de componente de front-end. + +## Exemplo básico + +A maneira mais rápida de ver um componente de front-end em ação é registrá-lo como um **comando**. Adicionar um campo `command` com `isPinned: true` faz com que ele apareça como um botão de ação rápida no canto superior direito da página — não é necessário layout de página: + +```tsx src/front-components/hello-world.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; + +const HelloWorld = () => { + return ( +
+

Hello from my app!

+

This component renders inside Twenty.

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, +}); +``` + +Após sincronizar com `yarn twenty dev` (ou executando uma única vez o `yarn twenty dev --once`), a ação rápida aparece no canto superior direito da página: + +
+ Botão de ação rápida no canto superior direito +
+ +Clique nele para renderizar o componente inline. + +## Campos de configuração + +| Campo | Obrigatório | Descrição | +| --------------------- | ----------- | ----------------------------------------------------------------------------------------- | +| `universalIdentifier` | Sim | ID único e estável para este componente | +| `component` | Sim | Uma função de componente React | +| `name` | Não | Nome de Exibição | +| `description` | Não | Descrição do que o componente faz | +| `isHeadless` | Não | Defina como `true` se o componente não tiver interface visível (veja abaixo) | +| `command` | Não | Registre o componente como um comando (veja [opções de comando](#command-options) abaixo) | + +## Colocando um componente de front-end em uma página + +Além de comandos, você pode incorporar um componente de front-end diretamente em uma página de registro adicionando-o como um widget em um **layout de página**. Veja a seção [definePageLayout](/l/pt/developers/extend/apps/skills-and-agents#definepagelayout) para obter detalhes. + +## Headless vs não headless + +Os componentes de front-end têm dois modos de renderização controlados pela opção `isHeadless`: + +**Não headless (padrão)** — O componente renderiza uma interface visível. Quando acionado pelo menu de comandos, ele é aberto no painel lateral. Este é o comportamento padrão quando `isHeadless` é `false` ou omitido. + +**Headless (`isHeadless: true`)** — The component mounts invisibly in the background. Ele não abre o painel lateral. Componentes headless são projetados para ações que executam lógica e, em seguida, se desmontam — por exemplo, executar uma tarefa assíncrona, navegar para uma página ou exibir um modal de confirmação. Eles se combinam naturalmente com os componentes Command do SDK descritos abaixo. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Como o componente retorna `null`, o Twenty ignora renderizar um contêiner para ele — nenhum espaço vazio aparece no layout. O componente ainda tem acesso a todos os hooks e à API de comunicação do host. + +## Componentes Command do SDK + +O pacote `twenty-sdk` fornece quatro componentes auxiliares Command projetados para componentes de front-end headless. Cada componente executa uma ação ao montar, trata erros exibindo uma notificação de snackbar e desmonta automaticamente o componente de front-end ao concluir. + +Importe-os de `twenty-sdk/command`: + +* **`Command`** — Executa um callback assíncrono via a prop `execute`. +* **`CommandLink`** — Navega para um caminho do app. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Abre um modal de confirmação. Se o usuário confirmar, executa o callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Abre uma página específica do painel lateral. Props: `page`, `pageTitle`, `pageIcon`. + +Aqui está um exemplo completo de um componente de front-end headless usando `Command` para executar uma ação a partir do menu de comandos: + +```tsx src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +E um exemplo usando `CommandModal` para solicitar confirmação antes de executar: + +```tsx src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +## Acessando o contexto de execução + +Dentro do seu componente, use hooks do SDK para acessar o usuário atual, o registro e a instância do componente: + +```tsx src/front-components/record-info.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk/front-component'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Hooks disponíveis: + +| Hook | Retorna | Descrição | +| --------------------------------------------- | ------------------ | ------------------------------------------------------------------ | +| `useUserId()` | `string` ou `null` | O ID do usuário atual | +| `useRecordId()` | `string` ou `null` | O ID do registro atual (quando colocado em uma página de registro) | +| `useFrontComponentId()` | `string` | O ID desta instância do componente | +| `useFrontComponentExecutionContext(selector)` | varia | Acesse o contexto de execução completo com uma função seletora | + +## API de comunicação do host + +Componentes de front-end podem acionar navegação, modais e notificações usando funções de `twenty-sdk`: + +| Função | Descrição | +| ----------------------------------------------- | ------------------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Navegar para uma página no app | +| `openSidePanelPage(params)` | Abrir um painel lateral | +| `closeSidePanel()` | Fecha o painel lateral | +| `openCommandConfirmationModal(params)` | Mostrar um diálogo de confirmação | +| `enqueueSnackbar(params)` | Mostrar uma notificação do tipo toast | +| `unmountFrontComponent()` | Desmontar o componente | +| `updateProgress(progress)` | Atualizar um indicador de progresso | + +Aqui está um exemplo que usa a API do host para exibir um snackbar e fechar o painel lateral após a conclusão de uma ação: + +```tsx src/front-components/archive-record.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId } from 'twenty-sdk/front-component'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + +## Opções de comando + +Adicionar um campo `command` a `defineFrontComponent` registra o componente no menu de comandos (Cmd+K). Se `isPinned` for `true`, ele também aparece como um botão de ação rápida no canto superior direito da página. + +| Campo | Obrigatório | Descrição | +| --------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `universalIdentifier` | Sim | ID exclusivo e estável para o comando | +| `label` | Sim | Rótulo completo exibido no menu de comandos (Cmd+K) | +| `shortLabel` | Não | Rótulo mais curto exibido no botão fixado de ação rápida | +| `icon` | Não | Nome do ícone exibido ao lado do rótulo (por exemplo, `'IconBolt'`, `'IconSend'`) | +| `isPinned` | Não | Quando `true`, mostra o comando como um botão de ação rápida no canto superior direito da página | +| `availabilityType` | Não | Controla onde o comando aparece: `'GLOBAL'` (sempre disponível), `'RECORD_SELECTION'` (apenas quando registros estão selecionados) ou `'FALLBACK'` (exibido quando nenhum outro comando corresponde) | +| `availabilityObjectUniversalIdentifier` | Não | Restringe o comando a páginas de um tipo específico de objeto (por exemplo, somente em registros de Company) | +| `conditionalAvailabilityExpression` | Não | Uma expressão booleana para controlar dinamicamente se o comando é visível (veja abaixo) | + +## Expressões de disponibilidade condicional + +O campo `conditionalAvailabilityExpression` permite controlar quando um comando é visível com base no contexto da página atual. Importe variáveis tipadas e operadores de `twenty-sdk` para construir expressões: + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk/front-component'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Variáveis de contexto** — representam o estado atual da página: + +| Variável | Tipo | Descrição | +| ------------------------------ | --------- | --------------------------------------------------------------------------- | +| `pageType` | `string` | Tipo de página atual (por exemplo, `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Se o componente é renderizado em um painel lateral | +| `numberOfSelectedRecords` | `number` | Número de registros atualmente selecionados | +| `isSelectAll` | `boolean` | Se "selecionar tudo" está ativo | +| `selectedRecords` | `array` | Os objetos de registro selecionados | +| `favoriteRecordIds` | `array` | IDs dos registros marcados como favoritos | +| `objectPermissions` | `object` | Permissões para o tipo de objeto atual | +| `targetObjectReadPermissions` | `object` | Permissões de leitura para o objeto alvo | +| `targetObjectWritePermissions` | `object` | Permissões de escrita para o objeto alvo | +| `featureFlags` | `object` | Flags de recurso ativas | +| `objectMetadataItem` | `object` | Metadados do tipo de objeto atual | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Se a visualização atual tem um filtro de soft-delete | + +**Operadores** — combine variáveis em expressões booleanas: + +| Operador | Descrição | +| ----------------------------------- | ---------------------------------------------------------------------- | +| `isDefined(value)` | `true` se o valor não for null/undefined | +| `isNonEmptyString(value)` | `true` se o valor for uma string não vazia | +| `includes(array, value)` | `true` se o array contiver o valor | +| `includesEvery(array, prop, value)` | `true` se a propriedade de cada item incluir o valor | +| `every(array, prop)` | `true` se a propriedade for truthy em cada item | +| `everyDefined(array, prop)` | `true` se a propriedade estiver definida em cada item | +| `everyEquals(array, prop, value)` | `true` se a propriedade for igual ao valor em cada item | +| `some(array, prop)` | `true` se a propriedade for truthy em pelo menos um item | +| `someDefined(array, prop)` | `true` se a propriedade estiver definida em pelo menos um item | +| `someEquals(array, prop, value)` | `true` se a propriedade for igual ao valor em pelo menos um item | +| `someNonEmptyString(array, prop)` | `true` se a propriedade for uma string não vazia em pelo menos um item | +| `none(array, prop)` | `true` se a propriedade for falsy em cada item | +| `noneDefined(array, prop)` | `true` se a propriedade for undefined em cada item | +| `noneEquals(array, prop, value)` | `true` se a propriedade não for igual ao valor em nenhum item | + +## Recursos públicos + +Componentes de front-end podem acessar arquivos do diretório `public/` do app usando `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +Veja a [seção de recursos públicos](/l/pt/developers/extend/apps/cli-and-testing#public-assets-public-folder) para obter detalhes. + +## Estilização + +Componentes de front-end suportam várias abordagens de estilização. Você pode usar: + +* **Estilos inline** — `style={{ color: 'red' }}` +* **Componentes de UI do Twenty** — importe de `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar e mais) +* **Emotion** — CSS-in-JS com `@emotion/react` +* **Styled-components** — padrões `styled.div` +* **Tailwind CSS** — classes utilitárias +* **Qualquer biblioteca CSS-in-JS** compatível com React + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/getting-started.mdx index 60735b57112..e371cd5754c 100644 --- a/packages/twenty-docs/l/pt/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/pt/developers/extend/apps/getting-started.mdx @@ -1,12 +1,9 @@ --- title: Primeiros passos +icon: rocket description: Crie seu primeiro app do Twenty em minutos. --- - -Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo. - - ## O que são aplicativos? Os aplicativos permitem que você estenda o Twenty com objetos e campos personalizados, funções lógicas, componentes de front-end, habilidades de IA e mais — tudo gerenciado como código. Em vez de configurar tudo pela UI, você define seu modelo de dados e a lógica em TypeScript e implanta em um ou mais workspaces. diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/layout.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/layout.mdx new file mode 100644 index 00000000000..36f2af8b5bc --- /dev/null +++ b/packages/twenty-docs/l/pt/developers/extend/apps/layout.mdx @@ -0,0 +1,131 @@ +--- +title: Layout +description: Define views, navigation menu items, and page layouts to shape how your app appears in Twenty. +icon: table-columns +--- + +Layout entities control how your app surfaces inside Twenty's UI — what lives in the sidebar, which saved views ship with the app, and how a record detail page is arranged. + +## Layout concepts + +| Concept | What it controls | Entidade | +| ------------------------ | --------------------------------------------------------------------------------- | -------------------------- | +| **View** | A saved list configuration for an object — visible fields, order, filters, groups | `defineView` | +| **Navigation Menu Item** | An entry in the left sidebar that links to a view or an external URL | `defineNavigationMenuItem` | +| **Page Layout** | The tabs and widgets that make up a record's detail page | `definePageLayout` | + +Views, navigation items, and page layouts reference each other by `universalIdentifier`: + +* A **navigation menu item** of type `VIEW` points at a `defineView` identifier, so the sidebar link opens that saved view. +* A **page layout** of type `RECORD_PAGE` targets an object and can embed [front components](/l/pt/developers/extend/apps/front-components) inside its tabs as widgets. + + + + +As visualizações são configurações salvas de como os registros de um objeto são exibidos — incluindo quais campos são visíveis, sua ordem e quaisquer filtros ou grupos aplicados. Use `defineView()` para enviar visualizações pré-configuradas com seu app: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +Pontos-chave: +* `objectUniversalIdentifier` especifica a qual objeto esta visualização se aplica. +* `key` determina o tipo de visualização (por exemplo, `ViewKey.INDEX` para a visualização de lista principal). +* `fields` controla quais colunas aparecem e sua ordem. Cada campo referencia um `fieldMetadataUniversalIdentifier`. +* Você também pode definir `filters`, `filterGroups`, `groups` e `fieldGroups` para configurações mais avançadas. +* `position` controla a ordenação quando existem várias visualizações para o mesmo objeto. + + + + +Os itens do menu de navegação adicionam entradas personalizadas à barra lateral do espaço de trabalho. Use `defineNavigationMenuItem()` para vincular a visualizações, URLs externas ou objetos: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Pontos-chave: +* `type` determina para o que o item de menu aponta: `NavigationMenuItemType.VIEW` para uma visualização salva ou `NavigationMenuItemType.LINK` para uma URL externa. +* Para links de visualização, defina `viewUniversalIdentifier`. Para links externos, defina `link`. +* `position` controla a ordenação na barra lateral. +* `icon` e `color` (opcionais) personalizam a aparência. + + + + +Layouts de página permitem personalizar como uma página de detalhes do registro se parece — quais abas aparecem, quais widgets estão dentro de cada aba e como eles são organizados. Use `definePageLayout()` para enviar layouts personalizados com seu app: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Pontos-chave: +* `type` geralmente é `'RECORD_PAGE'` para personalizar a visualização de detalhes de um objeto específico. +* `objectUniversalIdentifier` especifica a qual objeto este layout se aplica. +* Cada `tab` define uma seção da página com um `title`, `position` e `layoutMode` (`CANVAS` para layout livre). +* Cada `widget` dentro de uma aba pode renderizar um componente de front-end, uma lista de relações ou outros tipos de widget incorporados. +* `position` nas abas controla sua ordem. Use valores mais altos (por exemplo, 50) para colocar abas personalizadas após as nativas. + + + diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/logic-functions.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/logic-functions.mdx new file mode 100644 index 00000000000..33fd2a94bab --- /dev/null +++ b/packages/twenty-docs/l/pt/developers/extend/apps/logic-functions.mdx @@ -0,0 +1,559 @@ +--- +title: Funções lógicas +description: Define server-side TypeScript functions with HTTP, cron, and database event triggers. +icon: bolt +--- + +Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents. + + + + +Cada arquivo de função usa `defineLogicFunction()` para exportar uma configuração com um manipulador e gatilhos opcionais. + +```ts src/logic-functions/createPostCard.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; + +const handler = async (params: RoutePayload) => { + const client = new CoreApiClient(); + const name = 'name' in params.queryStringParameters + ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' + : 'Hello world'; + + const result = await client.mutation({ + createPostCard: { + __args: { data: { name } }, + id: true, + name: true, + }, + }); + return result; +}; + +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'create-new-post-card', + timeoutSeconds: 2, + handler, + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: true, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ +}); +``` + +Tipos de gatilho disponíveis: +* **httpRoute**: Expõe sua função em um caminho e método HTTP **no endpoint `/s/`**: +> por exemplo, `path: '/post-card/create'` é acessível em `https://your-twenty-server.com/s/post-card/create` +* **cron**: Executa sua função em um agendamento usando uma expressão CRON. +* **databaseEvent**: Executa em eventos do ciclo de vida de objetos do espaço de trabalho. Quando a operação do evento é `updated`, campos específicos a serem observados podem ser especificados no array `updatedFields`. Se deixar indefinido ou vazio, qualquer atualização acionará a função. +> por exemplo, `person.updated`, `*.created`, `company.*` + + +Você também pode executar manualmente uma função usando a CLI: + +```bash filename="Terminal" +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' +``` + +```bash filename="Terminal" +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + +Você pode acompanhar os logs com: + +```bash filename="Terminal" +yarn twenty logs +``` + + +#### Payload de gatilho de rota + +Quando um gatilho de rota invoca sua função de lógica, ela recebe um objeto `RoutePayload` que segue o [formato HTTP API v2 da AWS](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +Importe o tipo `RoutePayload` de `twenty-sdk`: + +```ts +import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; + +const handler = async (event: RoutePayload) => { + const { headers, queryStringParameters, pathParameters, body } = event; + const { method, path } = event.requestContext.http; + + return { message: 'Success' }; +}; +``` + +O tipo `RoutePayload` tem a seguinte estrutura: + + | Propriedade | Tipo | Descrição | Exemplo | + | ---------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record\` | Cabeçalhos HTTP (apenas aqueles listados em `forwardedRequestHeaders`) | veja a seção abaixo | + | `queryStringParameters` | `Record\` | Parâmetros de query string (valores múltiplos unidos por vírgulas) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record\` | Parâmetros de caminho extraídos do padrão de rota | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Corpo da requisição analisado (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | Se o corpo está codificado em base64 | | + | `requestContext.http.method` | `string` | Método HTTP (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | Caminho bruto da requisição | | + + +#### forwardedRequestHeaders + +Por padrão, os cabeçalhos HTTP das requisições recebidas **não** são repassados para sua função de lógica por motivos de segurança. +Para acessar cabeçalhos específicos, liste-os explicitamente no array `forwardedRequestHeaders`: + +```ts +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'webhook-handler', + handler, + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, +}); +``` + +No seu manipulador, acesse os cabeçalhos encaminhados assim: + +```ts +const handler = async (event: RoutePayload) => { + const signature = event.headers['x-webhook-signature']; + const contentType = event.headers['content-type']; + + // Validate webhook signature... + return { received: true }; +}; +``` + + +Os nomes dos cabeçalhos são normalizados para minúsculas. Acesse-os usando chaves em minúsculas (por exemplo, `event.headers['content-type']`). + + +#### Expor uma função como ferramenta + +Funções lógicas podem ser expostas como **ferramentas** para agentes de IA e fluxos de trabalho. Quando marcada como ferramenta, uma função fica detectável pelos recursos de IA do Twenty e pode ser usada em automações de fluxos de trabalho. + +Para marcar uma função de lógica como ferramenta, defina `isTool: true`: + +```ts src/logic-functions/enrich-company.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const handler = async (params: { companyName: string; domain?: string }) => { + const client = new CoreApiClient(); + + const result = await client.mutation({ + createTask: { + __args: { + data: { + title: `Enrich data for ${params.companyName}`, + body: `Domain: ${params.domain ?? 'unknown'}`, + }, + }, + id: true, + }, + }); + + return { taskId: result.createTask.id }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + name: 'enrich-company', + description: 'Enrich a company record with external data', + timeoutSeconds: 10, + handler, + isTool: true, +}); +``` + +Pontos-chave: + +* Você pode combinar `isTool` com gatilhos — uma função pode ser ao mesmo tempo uma ferramenta (chamável por agentes de IA) e acionada por eventos. +* **`toolInputSchema`** (opcional): Um objeto JSON Schema que descreve os parâmetros que sua função aceita. O schema é calculado automaticamente a partir da análise estática do código-fonte, mas você pode defini-lo explicitamente: + +```ts +export default defineLogicFunction({ + ..., + toolInputSchema: { + type: 'object', + properties: { + companyName: { + type: 'string', + description: 'The name of the company to enrich', + }, + domain: { + type: 'string', + description: 'The company website domain (optional)', + }, + }, + required: ['companyName'], + }, +}); +``` + + +**Escreva uma boa `description`.** Os agentes de IA dependem do campo `description` da função para decidir quando usar a ferramenta. Seja específico sobre o que a ferramenta faz e quando ela deve ser chamada. + + + + + +Uma função de pós-instalação é uma função lógica que é executada automaticamente assim que seu aplicativo terminar de ser instalado em um espaço de trabalho. O servidor a executa **depois** que os metadados do aplicativo forem sincronizados e o cliente do SDK for gerado, para que o espaço de trabalho esteja totalmente pronto para uso e o novo esquema esteja disponível. Casos de uso típicos incluem popular dados padrão, criar registros iniciais, configurar as definições do espaço de trabalho ou provisionar recursos em serviços de terceiros. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + shouldRunSynchronously: false, + handler, +}); +``` + +Você também pode executar manualmente a função de pós-instalação a qualquer momento usando a CLI: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Pontos-chave: +* As funções de pós-instalação usam `definePostInstallLogicFunction()` — uma variante especializada que omite as configurações de gatilho (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* O manipulador recebe um `InstallPayload` com `{ previousVersion?: string; newVersion: string }` — `newVersion` é a versão que está sendo instalada, e `previousVersion` é a versão que foi instalada anteriormente (ou `undefined` em uma instalação nova). Use esses valores para distinguir instalações novas de atualizações e para executar lógica de migração específica da versão. +* **Quando o hook é executado**: apenas em instalações novas, por padrão. Passe `shouldRunOnVersionUpgrade: true` se você também quiser que ele seja executado quando o app for atualizado a partir de uma versão anterior. Quando omitida, a flag tem valor padrão `false` e as atualizações ignoram o hook. +* **Modelo de execução — assíncrono por padrão, síncrono opcional**: a flag `shouldRunSynchronously` controla *como* a pós-instalação é executada. + * `shouldRunSynchronously: false` *(padrão)* — o hook é **enfileirado na fila de mensagens** com `retryLimit: 3` e é executado de forma assíncrona em um worker. A resposta da instalação retorna assim que o job é enfileirado, então um manipulador lento ou com falha não bloqueia quem chamou. O worker tentará novamente até três vezes. **Use isto para jobs de longa duração** — popular grandes conjuntos de dados, chamar APIs de terceiros lentas, provisionar recursos externos, qualquer coisa que possa exceder uma janela razoável de resposta HTTP. + * `shouldRunSynchronously: true` — o hook é executado **inline durante o fluxo de instalação** (mesmo executor da pré-instalação). A requisição de instalação bloqueia até o manipulador terminar e, se ele lançar uma exceção, quem chamou a instalação recebe um `POST_INSTALL_ERROR`. Sem novas tentativas automáticas. **Use isto para trabalhos rápidos que precisam ser concluídos antes da resposta** — por exemplo, emitir um erro de validação para o usuário ou fazer uma configuração rápida da qual o cliente dependerá imediatamente após a chamada de instalação retornar. Tenha em mente que a migração de metadados já foi aplicada quando a pós-instalação é executada, então uma falha no modo síncrono **não** reverte as alterações de esquema — ela apenas expõe o erro. +* Garanta que seu manipulador seja idempotente. No modo assíncrono, a fila pode tentar novamente até três vezes; em qualquer modo, o hook pode ser executado novamente em atualizações quando `shouldRunOnVersionUpgrade: true`. +* As variáveis de ambiente `APPLICATION_ID`, `APP_ACCESS_TOKEN` e `API_URL` estão disponíveis dentro do manipulador (assim como em qualquer outra função de lógica), então você pode chamar a API da Twenty com um token de acesso de aplicativo com escopo para o seu app. +* É permitida apenas uma função de pós-instalação por app. A geração do manifesto apresentará erro se mais de uma for detectada. +* O `universalIdentifier`, `shouldRunOnVersionUpgrade` e `shouldRunSynchronously` da função são anexados automaticamente ao manifesto do aplicativo no campo `postInstallLogicFunction` durante o build — você não precisa referenciá-los em `defineApplication()`. +* O tempo limite padrão é definido como 300 segundos (5 minutos) para permitir tarefas de configuração mais longas, como o pré-carregamento de dados. +* **Não executado no modo de desenvolvimento**: quando um app é registrado localmente (via `yarn twenty dev`), o servidor pula completamente o fluxo de instalação e sincroniza arquivos diretamente pelo watcher da CLI — portanto, a pós-instalação nunca é executada no modo de desenvolvimento, independentemente de `shouldRunSynchronously`. Use `yarn twenty exec --postInstall` para acioná-lo manualmente em um workspace em execução. + + + + +Uma função de pré-instalação é uma função de lógica que é executada automaticamente durante a instalação, **antes que a migração de metadados do workspace seja aplicada**. Ela compartilha o mesmo formato de payload que a pós-instalação (`InstallPayload`), mas está posicionada mais cedo no fluxo de instalação para poder preparar o estado do qual a próxima migração depende — usos típicos incluem fazer backup de dados, validar a compatibilidade com o novo esquema ou arquivar registros que estão prestes a ser reestruturados ou removidos. + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +Você também pode executar manualmente a função de pré-instalação a qualquer momento usando a CLI: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Pontos-chave: +* Funções de pré-instalação usam `definePreInstallLogicFunction()` — a mesma configuração especializada da pós-instalação, apenas anexada a um ponto diferente do ciclo de vida. +* Os manipuladores de pré e pós-instalação recebem o mesmo tipo `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Importe-o uma vez e reutilize-o para ambos os hooks. +* **Quando o hook é executado**: posicionado imediatamente antes da migração de metadados do workspace (`synchronizeFromManifest`). Antes de executar, o servidor realiza uma "sincronização simplificada" puramente aditiva que registra a função de pré-instalação da **nova** versão nos metadados do workspace — nada mais é alterado — e então a executa. Como essa sincronização é apenas aditiva, os objetos, campos e dados da versão anterior ainda estão intactos quando seu manipulador é executado: você pode ler e fazer backup com segurança do estado pré-migração. +* **Modelo de execução**: a pré-instalação é executada **de forma síncrona** e **bloqueia a instalação**. Se o manipulador lançar uma exceção, a instalação é abortada antes que quaisquer alterações de esquema sejam aplicadas — o workspace permanece na versão anterior em um estado consistente. Isto é intencional: a pré-instalação é sua última chance de recusar uma atualização arriscada. +* Assim como na pós-instalação, é permitida apenas uma função de pré-instalação por app. Ela é anexada ao manifesto do aplicativo sob `preInstallLogicFunction` automaticamente durante o build. +* **Não é executada no modo de desenvolvimento**: igual à pós-instalação — o fluxo de instalação é totalmente ignorado para apps registrados localmente, portanto a pré-instalação nunca é executada com `yarn twenty dev`. Use `yarn twenty exec --preInstall` para acioná-lo manualmente. + + + + +Ambos os hooks fazem parte do mesmo fluxo de instalação e recebem o mesmo `InstallPayload`. A diferença é **quando** eles são executados em relação à migração de metadados do workspace, e isso muda quais dados eles podem manipular com segurança. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ install flow │ +│ │ +│ upload package → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +│ │ +│ old schema visible new schema visible │ +└─────────────────────────────────────────────────────────────┘ +``` + +A pré-instalação é sempre **síncrona** (ela bloqueia a instalação e pode abortá-la). A pós-instalação é **assíncrona por padrão** — enfileirada em um worker com novas tentativas automáticas — mas pode optar por execução síncrona com `shouldRunSynchronously: true`. Veja o acordeão `definePostInstallLogicFunction` acima para saber quando usar cada modo. + +**Use `post-install` para qualquer coisa que precise que o novo esquema exista.** Este é o caso mais comum: + +* Popular dados padrão (criando registros iniciais, visualizações padrão, conteúdo de demonstração) em objetos e campos recém-adicionados. +* Registrar webhooks com serviços de terceiros agora que o app tem suas credenciais. +* Chamar sua própria API para finalizar a configuração que depende dos metadados sincronizados. +* Lógica idempotente de "garantir que isso exista" que deve reconciliar o estado em cada atualização — combine com `shouldRunOnVersionUpgrade: true`. + +Exemplo — popular um registro `PostCard` padrão após a instalação: + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion }: InstallPayload): Promise => { + if (previousVersion) return; // fresh installs only + + const client = createClient(); + await client.postCard.create({ + data: { title: 'Welcome to Postcard', content: 'Your first card!' }, + }); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Seeds a welcome post card after install.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + handler, +}); +``` + +**Use `pre-install` quando uma migração, de outra forma, destruiria ou corromperia dados existentes.** Como a pré-instalação roda contra o esquema *anterior* e sua falha reverte a atualização, é o lugar certo para qualquer coisa arriscada: + +* **Fazer backup de dados que estão prestes a ser removidos ou reestruturados** — por exemplo, você está removendo um campo na v2 e precisa copiar seus valores para outro campo ou exportá-los para um armazenamento antes que a migração seja executada. +* **Arquivar registros que uma nova restrição invalidaria** — por exemplo, um campo está se tornando `NOT NULL` e você precisa excluir ou corrigir linhas com valores nulos primeiro. +* **Validar a compatibilidade e recusar a atualização se os dados atuais não puderem ser migrados de forma limpa** — lance uma exceção no manipulador e a instalação é abortada sem alterações aplicadas. Isto é mais seguro do que descobrir a incompatibilidade no meio da migração. +* **Renomear ou reatribuir chaves de dados** antes de uma alteração de esquema que perderia a associação. + +Exemplo — arquivar registros antes de uma migração destrutiva: + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { + // Only the 1.x → 2.x upgrade drops the legacy `notes` field. + if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { + return; + } + + const client = createClient(); + const legacyRecords = await client.postCard.findMany({ + where: { notes: { isNotNull: true } }, + }); + + if (legacyRecords.length === 0) return; + + // Copy legacy `notes` into the new `description` field before the migration + // drops the `notes` column. If this fails, the upgrade is aborted and the + // workspace stays on v1 with all data intact. + await Promise.all( + legacyRecords.map((record) => + client.postCard.update({ + where: { id: record.id }, + data: { description: record.notes }, + }), + ), + ); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Backs up legacy notes into description before the v2 migration.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +**Regra geral:** + +| You want to... | Usar | +| ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| Popular dados padrão, configurar o workspace, registrar recursos externos | `post-install` | +| Executar processos longos de popular dados ou chamadas a terceiros que não devem bloquear a resposta da instalação | `post-install` (padrão — `shouldRunSynchronously: false`, com novas tentativas do worker) | +| Executar uma configuração rápida da qual o chamador dependerá imediatamente após o retorno da chamada de instalação | `post-install` com `shouldRunSynchronously: true` | +| Ler ou fazer backup de dados que a próxima migração perderia | `pre-install` | +| Rejeitar uma atualização que corromperia dados existentes | `pre-install` (lançar uma exceção no manipulador) | +| Executar reconciliação em cada atualização | `post-install` com `shouldRunOnVersionUpgrade: true` | +| Fazer uma configuração única apenas na primeira instalação | `post-install` com `shouldRunOnVersionUpgrade: false` (padrão) | + + +Em caso de dúvida, use **post-install** como padrão. Recurra à pré-instalação somente quando a própria migração for destrutiva e você precisar interceptar o estado anterior antes que ele desapareça. + + + + + +## Clientes de API tipados (twenty-client-sdk) + +O pacote `twenty-client-sdk` fornece dois clientes GraphQL tipados para interagir com a API do Twenty a partir das suas funções de lógica e componentes de front-end. + +| Cliente | Importar | Endpoint | Gerado? | +| ------------------- | ---------------------------- | -------------------------------------------------------------------- | -------------------------- | +| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — dados do espaço de trabalho (registros, objetos) | Sim, em tempo de dev/build | +| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — configuração do espaço de trabalho, upload de arquivos | Não, vem pré-compilado | + + + + +`CoreApiClient` é o cliente principal para consultar e mutar dados do espaço de trabalho. Ele é **gerado a partir do schema do seu espaço de trabalho** durante `yarn twenty dev` ou `yarn twenty build`, então é totalmente tipado para corresponder aos seus objetos e campos. + +```ts +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const client = new CoreApiClient(); + +// Query records +const { companies } = await client.query({ + companies: { + edges: { + node: { + id: true, + name: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, + }, + }, + }, +}); + +// Create a record +const { createCompany } = await client.mutation({ + createCompany: { + __args: { + data: { + name: 'Acme Corp', + }, + }, + id: true, + name: true, + }, +}); +``` + +O cliente usa uma sintaxe de selection-set: passe `true` para incluir um campo, use `__args` para argumentos e aninhe objetos para relações. Você tem preenchimento automático e verificação de tipos completos com base no schema do seu espaço de trabalho. + + +**CoreApiClient é gerado em tempo de dev/build.** Se você usá-lo sem executar primeiro `yarn twenty dev` ou `yarn twenty build`, ele lançará um erro. A geração ocorre automaticamente — a CLI analisa o schema GraphQL do seu espaço de trabalho e gera um cliente tipado usando `@genql/cli`. + + +#### Usando CoreSchema para anotações de tipo + +`CoreSchema` fornece tipos TypeScript que correspondem aos objetos do seu espaço de trabalho — útil para tipar o estado de componentes ou parâmetros de função: + +```ts +import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; +import { useState } from 'react'; + +const [company, setCompany] = useState< + Pick | undefined +>(undefined); + +const client = new CoreApiClient(); +const result = await client.query({ + company: { + __args: { filter: { position: { eq: 1 } } }, + id: true, + name: true, + }, +}); +setCompany(result.company); +``` + + + + +`MetadataApiClient` é fornecido pré-compilado com o SDK (não é necessário gerar). Ele consulta o endpoint `/metadata` para configuração do espaço de trabalho, aplicativos e upload de arquivos. + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; + +const metadataClient = new MetadataApiClient(); + +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, + }, +}); +``` + +#### Carregamento de arquivos + +`MetadataApiClient` inclui um método `uploadFile` para anexar arquivos a campos do tipo arquivo: + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import * as fs from 'fs'; + +const metadataClient = new MetadataApiClient(); + +const fileBuffer = fs.readFileSync('./invoice.pdf'); + +const uploadedFile = await metadataClient.uploadFile( + fileBuffer, // file contents as a Buffer + 'invoice.pdf', // filename + 'application/pdf', // MIME type + '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier +); + +console.log(uploadedFile); +// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } +``` + +| Parâmetro | Tipo | Descrição | +| ---------------------------------- | -------- | ----------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | O conteúdo bruto do arquivo | +| `filename` | `string` | O nome do arquivo (usado para armazenamento e exibição) | +| `contentType` | `string` | Tipo MIME (padrão para `application/octet-stream` se omitido) | +| `fieldMetadataUniversalIdentifier` | `string` | O `universalIdentifier` do campo do tipo de arquivo no seu objeto | + +Pontos-chave: +* Usa o `universalIdentifier` do campo (não o ID específico do espaço de trabalho), de modo que seu código de upload funcione em qualquer espaço de trabalho onde seu app esteja instalado. +* A `url` retornada é uma URL assinada que você pode usar para acessar o arquivo enviado. + + + + + + Quando seu código é executado no Twenty (funções de lógica ou componentes de front-end), a plataforma injeta credenciais como variáveis de ambiente: + + * `TWENTY_API_URL` — URL base da API do Twenty + * `TWENTY_APP_ACCESS_TOKEN` — Chave de curta duração com escopo para o papel de função padrão do seu aplicativo + + Você **não** precisa passá-las para os clientes — eles leem de `process.env` automaticamente. As permissões da chave de API são determinadas pelo papel referenciado em `defaultRoleUniversalIdentifier` no seu `application-config.ts`. + diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/publishing.mdx index 5b2a792ae37..743967a752f 100644 --- a/packages/twenty-docs/l/pt/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/pt/developers/extend/apps/publishing.mdx @@ -1,12 +1,9 @@ --- title: Publicação +icon: carregar description: Distribua seu aplicativo Twenty no Marketplace ou implante-o internamente. --- - - Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo. - - ## Visão Geral Depois que seu aplicativo estiver [compilado e testado localmente](/l/pt/developers/extend/apps/building), você tem dois caminhos para distribuí-lo: diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/skills-and-agents.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/skills-and-agents.mdx new file mode 100644 index 00000000000..3d489aeb72d --- /dev/null +++ b/packages/twenty-docs/l/pt/developers/extend/apps/skills-and-agents.mdx @@ -0,0 +1,69 @@ +--- +title: Habilidades e agentes +description: Define AI skills and agents for your app. +icon: robot +--- + + + Skills and agents are currently in alpha. O recurso é funcional, mas ainda está evoluindo. + + +Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts. + + + + +As habilidades definem instruções e capacidades reutilizáveis que os agentes de IA podem usar no seu espaço de trabalho. Use `defineSkill()` para definir habilidades com validação integrada: + +```ts src/skills/example-skill.ts +import { defineSkill } from 'twenty-sdk/define'; + +export default defineSkill({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'sales-outreach', + label: 'Sales Outreach', + description: 'Guides the AI agent through a structured sales outreach process', + icon: 'IconBrain', + content: `You are a sales outreach assistant. When reaching out to a prospect: +1. Research the company and recent news +2. Identify the prospect's role and likely pain points +3. Draft a personalized message referencing specific details +4. Keep the tone professional but conversational`, +}); +``` + +Pontos-chave: +* `name` é uma string de identificador exclusivo para a habilidade (recomenda-se kebab-case). +* `label` é o nome de exibição legível por humanos mostrado na UI. +* `content` contém as instruções da habilidade — este é o texto que o agente de IA usa. +* `icon` (opcional) define o ícone exibido na UI. +* `description` (opcional) fornece contexto adicional sobre a finalidade da habilidade. + + + + +Agentes são assistentes de IA que vivem dentro do seu espaço de trabalho. Use `defineAgent()` para criar agentes com um prompt de sistema personalizado: + +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk/define'; + +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +Pontos-chave: +* `name` é a string de identificador exclusiva do agente (recomenda-se kebab-case). +* `label` é o nome de exibição mostrado na UI. +* `prompt` é o prompt do sistema que define o comportamento do agente. +* `description` (opcional) fornece contexto sobre o que o agente faz. +* `icon` (opcional) define o ícone exibido na UI. +* `modelId` (opcional) substitui o modelo de IA padrão usado pelo agente. + + + diff --git a/packages/twenty-docs/l/pt/developers/extend/oauth.mdx b/packages/twenty-docs/l/pt/developers/extend/oauth.mdx new file mode 100644 index 00000000000..079f4e3e9f3 --- /dev/null +++ b/packages/twenty-docs/l/pt/developers/extend/oauth.mdx @@ -0,0 +1,189 @@ +--- +title: OAuth +icon: chave +description: Authorization code flow with PKCE and client credentials for server-to-server access. +--- + +Twenty implements OAuth 2.0 with authorization code + PKCE for user-facing apps and client credentials for server-to-server access. Clients are registered dynamically via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) — no manual setup in a dashboard. + +## When to Use OAuth + +| Cenário | Auth Method | +| --------------------------------------- | -------------------------------------------------------------------------------- | +| Internal scripts, automation | [API Key](/l/pt/developers/extend/api#authentication) | +| External app acting on behalf of a user | **OAuth — Authorization Code** | +| Server-to-server, no user context | **OAuth — Client Credentials** | +| Twenty App with UI extensions | [Apps](/l/pt/developers/extend/apps/getting-started) (OAuth is handled automatically) | + +## Register a Client + +Twenty supports **dynamic client registration** per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). No manual setup needed — register programmatically: + +```bash +POST /oauth/register +Content-Type: application/json + +{ + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"], + "grant_types": ["authorization_code"], + "token_endpoint_auth_method": "client_secret_post" +} +``` + +**Response:** + +```json +{ + "client_id": "abc123", + "client_secret": "secret456", + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"] +} +``` + + +Store the `client_secret` securely — it cannot be retrieved later. + + +## Escopos + +| Scope | Acesso | +| -------- | ---------------------------------------------------- | +| `api` | Full read/write access to the Core and Metadata APIs | +| `perfil` | Read the authenticated user's profile information | + +Request scopes as a space-separated string: `scope=api profile` + +## Authorization Code Flow + +Use this flow when your app acts on behalf of a Twenty user. + +### 1. Redirect the user to authorize + +``` +GET /oauth/authorize? + client_id=YOUR_CLIENT_ID& + response_type=code& + redirect_uri=https://myapp.com/callback& + scope=api& + state=random_state_value& + code_challenge=CHALLENGE& + code_challenge_method=S256 +``` + +| Parâmetro | Obrigatório | Descrição | +| ----------------------- | ----------- | ------------------------------------------------------------ | +| `client_id` | Sim | Your registered client ID | +| `response_type` | Sim | Must be `code` | +| `redirect_uri` | Sim | Must match a registered redirect URI | +| `scope` | Não | Space-separated scopes (defaults to `api`) | +| `estado` | Recomendado | Random string to prevent CSRF attacks | +| `code_challenge` | Recomendado | PKCE challenge (SHA-256 hash of verifier, base64url-encoded) | +| `code_challenge_method` | Recomendado | Must be `S256` when using PKCE | + +The user sees a consent screen and approves or denies access. + +### 2. Handle the callback + +After authorization, Twenty redirects back to your `redirect_uri`: + +``` +https://myapp.com/callback?code=AUTH_CODE&state=random_state_value +``` + +Verify that `state` matches what you sent. + +### 3. Exchange the code for tokens + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=authorization_code& +code=AUTH_CODE& +redirect_uri=https://myapp.com/callback& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +code_verifier=YOUR_PKCE_VERIFIER +``` + +**Response:** + +```json +{ + "access_token": "eyJhbG...", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "dGhpcyBpcyBh..." +} +``` + +### 4. Use the access token + +```bash +GET /rest/companies +Authorization: Bearer ACCESS_TOKEN +``` + +### 5. Refresh when expired + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=refresh_token& +refresh_token=YOUR_REFRESH_TOKEN& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET +``` + +## Client Credentials Flow + +For server-to-server integrations with no user interaction: + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=client_credentials& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +scope=api +``` + +The returned token has workspace-level access, not tied to any specific user. + +## Server Discovery + +Twenty publishes its OAuth configuration at a standard discovery endpoint: + +``` +GET /.well-known/oauth-authorization-server +``` + +This returns all endpoints, supported grant types, scopes, and capabilities — useful for building generic OAuth clients. + +## API Endpoints Summary + +| Endpoint | Finalidade | +| ----------------------------------------- | --------------------------- | +| `/.well-known/oauth-authorization-server` | Server metadata discovery | +| `/oauth/register` | Dynamic client registration | +| `/oauth/authorize` | User authorization | +| `/oauth/token` | Token exchange and refresh | + +| Ambiente | URL base | +| ------------------ | ------------------------ | +| **Nuvem** | `https://api.twenty.com` | +| **Auto-hospedado** | `https://{your-domain}` | + +## OAuth vs API Keys + +| | Chaves API | OAuth | +| ------------------ | ----------------------- | -------------------------------------- | +| **Configuração** | Generate in Settings | Register a client, implement flow | +| **User context** | None (workspace-level) | Specific user's permissions | +| **Melhor para** | Scripts, internal tools | External apps, multi-user integrations | +| **Token rotation** | Manual | Automatic via refresh tokens | +| **Scoped access** | Full API access | Granular via scopes | diff --git a/packages/twenty-docs/l/pt/developers/extend/webhooks.mdx b/packages/twenty-docs/l/pt/developers/extend/webhooks.mdx index 25d669a127b..4583e493716 100644 --- a/packages/twenty-docs/l/pt/developers/extend/webhooks.mdx +++ b/packages/twenty-docs/l/pt/developers/extend/webhooks.mdx @@ -1,11 +1,12 @@ --- title: Webhooks -description: Receba notificações em tempo real quando eventos ocorrerem no seu CRM. +icon: satellite-dish +description: Get notified when records change — HTTP POST to your endpoint on every create, update, or delete. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -Os webhooks enviam dados para seus sistemas em tempo real quando eventos ocorrem no Twenty — sem necessidade de polling. Use-os para manter sistemas externos em sincronia, acionar automações ou enviar alertas. +Twenty sends an HTTP POST to your URL whenever a record is created, updated, or deleted. All object types are covered, including custom objects. ## Criar Webhook diff --git a/packages/twenty-docs/l/pt/developers/introduction.mdx b/packages/twenty-docs/l/pt/developers/introduction.mdx index 03f46561902..15505782f05 100644 --- a/packages/twenty-docs/l/pt/developers/introduction.mdx +++ b/packages/twenty-docs/l/pt/developers/introduction.mdx @@ -1,23 +1,28 @@ --- -title: Primeiros passos -description: Bem-vindo à Documentação para Desenvolvedores da Twenty, seus recursos para estender, auto-hospedar e contribuir para o Twenty. +title: Programadores +description: Build apps, use the API, self-host, or contribute to the codebase. --- import { CardTitle } from "/snippets/card-title.mdx" - - Estender - Crie integrações com APIs, webhooks e aplicativos personalizados. + + Apps + Extend Twenty with custom objects, server-side logic, UI components, and AI agents — all as TypeScript packages. - - Auto-hospedar - Implante e gerencie o Twenty na sua própria infraestrutura. + + API + REST and GraphQL APIs, webhooks, and OAuth. - - Contribuir - Junte-se à nossa comunidade de código aberto e contribua para o Twenty. + + Self-Host + Run Twenty on your own infrastructure. + + + + Contribute + Set up the monorepo locally and submit PRs. diff --git a/packages/twenty-docs/l/pt/developers/self-host/capabilities/cloud-providers.mdx b/packages/twenty-docs/l/pt/developers/self-host/capabilities/cloud-providers.mdx index 8476254c26d..01995029c1d 100644 --- a/packages/twenty-docs/l/pt/developers/self-host/capabilities/cloud-providers.mdx +++ b/packages/twenty-docs/l/pt/developers/self-host/capabilities/cloud-providers.mdx @@ -1,5 +1,6 @@ --- title: Outros métodos +icon: cloud --- diff --git a/packages/twenty-docs/l/pt/developers/self-host/capabilities/docker-compose.mdx b/packages/twenty-docs/l/pt/developers/self-host/capabilities/docker-compose.mdx index 22f54f4e891..4b2b02bfdb9 100644 --- a/packages/twenty-docs/l/pt/developers/self-host/capabilities/docker-compose.mdx +++ b/packages/twenty-docs/l/pt/developers/self-host/capabilities/docker-compose.mdx @@ -1,5 +1,6 @@ --- -title: 1-Clique c/ Docker Compose +title: Docker Compose +icon: docker --- diff --git a/packages/twenty-docs/l/pt/developers/self-host/capabilities/setup.mdx b/packages/twenty-docs/l/pt/developers/self-host/capabilities/setup.mdx index b4033d4df4f..4a1927dcf21 100644 --- a/packages/twenty-docs/l/pt/developers/self-host/capabilities/setup.mdx +++ b/packages/twenty-docs/l/pt/developers/self-host/capabilities/setup.mdx @@ -1,5 +1,6 @@ --- title: Configuração +icon: gear --- # Gestão de Configuração diff --git a/packages/twenty-docs/l/pt/developers/self-host/capabilities/troubleshooting.mdx b/packages/twenty-docs/l/pt/developers/self-host/capabilities/troubleshooting.mdx index e9d17b9e9c4..6524fc7b023 100644 --- a/packages/twenty-docs/l/pt/developers/self-host/capabilities/troubleshooting.mdx +++ b/packages/twenty-docs/l/pt/developers/self-host/capabilities/troubleshooting.mdx @@ -1,5 +1,6 @@ --- title: Resolução de Problemas +icon: wrench --- ## Resolução de Problemas diff --git a/packages/twenty-docs/l/pt/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/pt/developers/self-host/capabilities/upgrade-guide.mdx index 026d552be4a..686b0808ce6 100644 --- a/packages/twenty-docs/l/pt/developers/self-host/capabilities/upgrade-guide.mdx +++ b/packages/twenty-docs/l/pt/developers/self-host/capabilities/upgrade-guide.mdx @@ -1,5 +1,6 @@ --- title: Guia de atualização +icon: arrow-up-right-dots --- ## Diretrizes gerais @@ -16,366 +17,14 @@ Se você usou o Docker Compose, siga estas etapas: 3. Traga o Twenty de volta ao ar com `docker compose up -d` -Se você quiser atualizar sua instância em algumas versões, por exemplo, de v0.33.0 para v0.35.0, você deve atualizar sua instância sequencialmente, neste exemplo de v0.33.0 para v0.34.0, depois de v0.34.0 para v0.35.0. - **Certifique-se de que após cada versão atualizada você tenha um backup não corrompido.** ## Etapas de atualização específicas da versão -## v1.0 +## After v1.21 -Olá Twenty v1.0! 🎉 +We know support sequential upgrades. You don't need to go through each version one by one. -## v0.60 +## Before v1.21 -### Melhorias de Performance - -Todas as interações com a API de metadados foram otimizadas para um melhor desempenho, particularmente para manipulação de metadados de objetos e operações de criação de espaço de trabalho. - -Reformulamos nossa estratégia de cache para priorizar acertos de cache em detrimento de consultas ao banco de dados sempre que possível, melhorando significativamente o desempenho das operações da API de metadados. - -Se você encontrar problemas de execução após a atualização, pode ser necessário limpar seu cache para garantir que esteja sincronizado com as alterações mais recentes. Execute este comando em seu contêiner do twenty-server: - -```bash -yarn command:prod cache:flush -``` - -### v0.55 - -Atualize sua instância do Twenty para usar a imagem v0.55 - -Você não precisa mais executar nenhum comando, a nova imagem cuidará automaticamente de executar todas as migrações necessárias. - -### Erro `User does not have permission` - -Se você encontrar erros de autorização na maioria das solicitações após a atualização, pode ser necessário limpar seu cache para recálculo das permissões mais recentes. - -Em seu contêiner `twenty-server`, execute: - -```bash -yarn command:prod cache:flush -``` - -Este problema é específico para esta versão do Twenty e não deverá ser necessário em futuras atualizações. - -### v0.54 - -Desde a versão `0.53`, nenhuma ação manual é necessária. - -#### Desativação do esquema de metadados - -Mesclamos o esquema `metadata` no `core` para simplificar a recuperação de dados do `TypeORM`. -Mesclamos o passo do comando `migrate` dentro do comando `upgrade`. Não recomendamos a execução manual do `migrate` em nenhum de seus servidores/conteineres de trabalho. - -### Desde v0.53 - -A partir de `0.53`, a atualização é feita programaticamente dentro do `DockerFile`, o que significa que, a partir de agora, você não precisará mais executar nenhum comando manualmente. - -Certifique-se de manter atualizando sua instância sequencialmente, sem pular qualquer versão principal (por exemplo, `0.43.3` para `0.44.0` é permitido, mas `0.43.1` para `0.45.0` não é), caso contrário, pode levar a uma desincronização na versão do espaço de trabalho que pode resultar em erro de tempo de execução e funcionalidade ausente. - -Para verificar se um espaço de trabalho foi migrado corretamente, você pode revisar sua versão no banco de dados na tabela `core.workspace`. - -Deve estar sempre na faixa da versão `major.minor` atual da instância do Twenty, você pode ver a versão de sua instância no painel de administração (em `/settings/admin-panel`, acessível se seu usuário tiver a propriedade `canAccessFullAdminPanel` definida como verdadeira no banco de dados) ou executando `echo $APP_VERSION` em seu contêiner `twenty-server`. - -Para corrigir uma versão de workspace dessincronizada, você terá que atualizar da versão correspondente do twenty seguindo o guia de atualização relacionado sequencialmente e assim por diante até alcançar a versão desejada. - -#### Remoção do `auditLog` - -Removemos o objeto padrão auditLog, o que significa que o tamanho do backup pode ser significativamente reduzido após esta migração. - -### v0.51 para v0.52 - -Atualize sua instância do Twenty para usar a imagem v0.52 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### Tenho um espaço de trabalho bloqueado na versão entre `0.52.0` e `0.52.6` - -Infelizmente, `0.52.0` e `0.52.6` foram completamente removidos do dockerHub. -Você terá que atualizar manualmente a versão do espaço de trabalho para `0.51.0` no banco de dados e atualizar usando a versão twenty `0.52.11` seguindo o guia de atualização logo acima. - -### v0.50 para v0.51 - -Atualize sua instância do Twenty para usar a imagem v0.51 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.44.0 para v0.50.0 - -Atualize sua instância do Twenty para usar a imagem v0.50.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### Mutação docker-compose.yml - -Esta versão inclui uma mutação `docker-compose.yml` para dar ao serviço `worker` acesso ao volume `server-local-data`. -Por favor, atualize seu `docker-compose.yml` local com [v0.50.0 docker-compose.yml](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml) - -### v0.43.0 para v0.44.0 - -Atualize sua instância do Twenty para usar a imagem v0.44.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.42.0 para v0.43.0 - -Atualize sua instância do Twenty para usar a imagem v0.43.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -Nesta versão, também trocamos para a imagem postgres:16 no docker-compose.yml. - -#### (Opção 1) Migração do banco de dados - -Manter a imagem postgres-spilo existente está ok, mas você terá que congelar a versão no seu docker-compose.yml para ser 0.43.0. - -#### (Opção 2) Migração do banco de dados - -Se você quiser migrar seu banco de dados para a nova imagem postgres:16, siga estas etapas: - -1. Faça dump do seu banco de dados do contêiner antigo postgres-spilo - -``` -docker exec -it twenty-db-1 sh -pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql -exit -docker cp twenty-db-1:/home/postgres/databases_backup.sql . -``` - -Certifique-se de que seu arquivo de dump não está vazio. - -2. Atualize seu docker-compose.yml para usar a imagem postgres:16 como no arquivo [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml). - -3. Restaure o banco de dados para o novo contêiner postgres:16 - -``` -docker cp databases_backup.sql twenty-db-1:/databases_backup.sql -docker exec -it twenty-db-1 sh -psql -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} -f databases_backup.sql -exit -``` - -### v0.41.0 para v0.42.0 - -Atualize sua instância do Twenty para usar a imagem v0.42.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.42 -``` - -**Variáveis de Ambiente** - -* Removido: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT` -* Adicionado: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED` - -### v0.40.0 para v0.41.0 - -Atualize sua instância do Twenty para usar a imagem v0.41.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.41 -``` - -**Variáveis de Ambiente** - -* Removido: `AUTH_MICROSOFT_TENANT_ID` - -### v0.35.0 para v0.40.0 - -Atualize sua instância do Twenty para usar a imagem v0.40.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.40 -``` - -**Variáveis de Ambiente** - -* Adicionado: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL` - -### v0.34.0 para v0.35.0 - -Atualize sua instância do Twenty para usar a imagem v0.35.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.35 -``` - -O comando `yarn database:migrate:prod` aplicará as migrações à estrutura do banco de dados (esquemas core e metadata) -O `yarn command:prod upgrade-0.35` cuida da migração de dados de todos os espaços de trabalho. - -**Variáveis de Ambiente** - -* Substituímos `ENABLE_DB_MIGRATIONS` por `DISABLE_DB_MIGRATIONS` (o valor padrão agora é `false`, você provavelmente não precisará definir nada) - -### v0.33.0 para v0.34.0 - -Atualize sua instância do Twenty para usar a imagem v0.34.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.34 -``` - -O comando `yarn database:migrate:prod` aplicará as migrações à estrutura do banco de dados (esquemas core e metadata) -O `yarn command:prod upgrade-0.34` cuida da migração de dados de todos os espaços de trabalho. - -**Variáveis de Ambiente** - -* Removido: `FRONT_BASE_URL` -* Adicionado: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT` - -Atualizamos a forma como lidamos com a URL do frontend. -Agora você pode definir a URL do frontend usando as variáveis `FRONT_DOMAIN`, `FRONT_PROTOCOL` e `FRONT_PORT`. -Se FRONT_DOMAIN não estiver definido, a URL do frontend voltará para `SERVER_URL`. - -### v0.32.0 para v0.33.0 - -Atualize sua instância do Twenty para usar a imagem v0.33.0 - -``` -yarn command:prod cache:flush -yarn database:migrate:prod -yarn command:prod upgrade-0.33 -``` - -O comando `yarn command:prod cache:flush` limpará o cache do Redis. -O comando `yarn database:migrate:prod` aplicará as migrações à estrutura do banco de dados (esquemas core e metadata) -O `yarn command:prod upgrade-0.33` cuida da migração de dados de todos os espaços de trabalho. - -A partir desta versão, a imagem twenty-postgres para DB tornou-se obsoleta e o twenty-postgres-spilo é usado em vez disso. -Se você quiser continuar usando a imagem twenty-postgres, basta substituir `twentycrm/twenty-postgres:${TAG}` por `twentycrm/twenty-postgres` em docker-compose.yml. - -### v0.31.0 para v0.32.0 - -Atualize sua instância do Twenty para usar a imagem v0.32.0 - -**Migração de esquema e dados** - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.32 -``` - -O comando `yarn database:migrate:prod` aplicará as migrações à estrutura do banco de dados (esquemas core e metadata) -O `yarn command:prod upgrade-0.32` cuida da migração de dados de todos os espaços de trabalho. - -**Variáveis de Ambiente** - -Atualizamos a forma como lidamos com a conexão Redis. - -* Removido: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD` -* Adicionado: `REDIS_URL` - -Atualize seu arquivo `.env` para usar a nova variável `REDIS_URL` em vez dos parâmetros de conexão Redis individuais. - -Também simplificamos a forma como lidamos com os tokens JWT. - -* Removido: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET` -* Adicionado: `APP_SECRET` - -Atualize seu arquivo `.env` para usar a nova variável `APP_SECRET` em vez dos segredos dos tokens individuais (você pode usar o mesmo segredo de antes ou gerar uma nova string aleatória) - -**Conta Ligada** - -Se você estiver usando uma conta conectada para sincronizar seus e-mails e calendários do Google, precisará ativar a [API People](https://developers.google.com/people) no console de administração do Google. - -### v0.30.0 para v0.31.0 - -Atualize sua instância do Twenty para usar a imagem v0.31.0 - -**Migração de esquema e dados**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.31 -``` - -O comando `yarn database:migrate:prod` aplicará as migrações à estrutura do banco de dados (esquemas core e metadata) -O `yarn command:prod upgrade-0.31` cuida da migração de dados de todos os espaços de trabalho. - -### v0.24.0 para v0.30.0 - -Atualize sua instância do Twenty para usar a imagem v0.30.0 - -**Mudança radical**: -Para melhorar o desempenho, o Twenty agora requer que o cache redis seja configurado. Atualizamos nosso [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) para refletir isso. -Certifique-se de atualizar sua configuração e suas variáveis de ambiente adequadamente: - -``` -REDIS_HOST={your-redis-host} -REDIS_PORT={your-redis-port} -CACHE_STORAGE_TYPE=redis -``` - -**Migração de esquema e dados**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.30 -``` - -O comando `yarn database:migrate:prod` aplicará as migrações à estrutura do banco de dados (esquemas core e metadata) -O `yarn command:prod upgrade-0.30` cuida da migração de dados de todos os espaços de trabalho. - -### v0.23.0 para v0.24.0 - -Atualize sua instância do Twenty para usar a imagem v0.24.0 - -Execute os seguintes comandos: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.24 -``` - -O comando `yarn database:migrate:prod` aplicará as migrações à estrutura do banco de dados (esquemas core e metadata) -O `yarn command:prod upgrade-0.24` cuida da migração de dados de todos os espaços de trabalho. - -### v0.22.0 para v0.23.0 - -Atualize sua instância do Twenty para usar a imagem v0.23.0 - -Execute os seguintes comandos: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.23 -``` - -O comando `yarn database:migrate:prod` aplicará as migrações ao Banco de Dados. -O `yarn command:prod upgrade-0.23` cuida da migração de dados, incluindo a transferência de atividades para tarefas/notas. - -### v0.21.0 para v0.22.0 - -Atualize sua instância do Twenty para usar a imagem v0.22.0 - -Execute os seguintes comandos: - -``` -yarn database:migrate:prod -yarn command:prod workspace:sync-metadata -f -yarn command:prod upgrade-0.22 -``` - -O comando `yarn database:migrate:prod` aplicará as migrações ao Banco de Dados. -O comando `yarn command:prod workspace:sync-metadata -f` sincronizará a definição de objetos padrão com as tabelas de metadados e aplicará as migrações necessárias aos espaços de trabalho existentes. -O comando `yarn command:prod upgrade-0.22` aplicará transformações de dados específicas para se adaptar às novas opções padrão de requestInstrumentationOptions do objeto. +Make sure to go through every major tagged version when upgrading (upgrade v1.6.x to v.7.y, then v.7.y to v.8.z, etc.). diff --git a/packages/twenty-docs/l/pt/navigation.json b/packages/twenty-docs/l/pt/navigation.json index e7988de6d8d..bc9fcb9b51a 100644 --- a/packages/twenty-docs/l/pt/navigation.json +++ b/packages/twenty-docs/l/pt/navigation.json @@ -1,24 +1,27 @@ { "tabs": { + "gettingStarted": { + "label": "Primeiros passos", + "groups": { + "welcome": { + "label": "Welcome" + }, + "coreConcepts": { + "label": "Core Concepts" + } + } + }, "userGuide": { "label": "User Guide", "groups": { - "discoverTwenty": { - "label": "Discover Twenty", - "groups": { - "gettingStartedCapabilities": { - "label": "Capabilities" - }, - "gettingStartedHowTos": { - "label": "How-Tos" - } - } + "userGuideOverview": { + "label": "Visão Geral" }, "dataModel": { "label": "Modelo de dados", "groups": { - "dataModelCapabilities": { - "label": "Capabilities" + "dataModelReference": { + "label": "Reference" }, "dataModelHowTos": { "label": "How-Tos" @@ -28,8 +31,8 @@ "dataMigration": { "label": "Data Migration", "groups": { - "dataMigrationCapabilities": { - "label": "Capabilities" + "dataMigrationReference": { + "label": "Reference" }, "dataMigrationHowTos": { "label": "How-Tos" @@ -39,8 +42,8 @@ "calendarEmails": { "label": "Calendar & Emails", "groups": { - "calendarEmailsCapabilities": { - "label": "Capabilities" + "calendarEmailsReference": { + "label": "Reference" }, "calendarEmailsHowTos": { "label": "How-Tos" @@ -50,8 +53,8 @@ "workflows": { "label": "Fluxos de trabalho", "groups": { - "workflowsCapabilities": { - "label": "Capabilities" + "workflowsReference": { + "label": "Reference" }, "workflowsHowTos": { "label": "How-Tos", @@ -75,21 +78,26 @@ "ai": { "label": "IA", "groups": { - "aiCapabilities": { - "label": "Capabilities" + "aiReference": { + "label": "Reference" }, "aiHowTos": { "label": "How-Tos" } } }, - "viewsPipelines": { - "label": "Visualizações e pipelines", + "layout": { + "label": "Layout", "groups": { - "viewsPipelinesCapabilities": { - "label": "Capabilities" + "layoutReference": { + "label": "Reference", + "groups": { + "layoutViews": { + "label": "Visualizações" + } + } }, - "viewsPipelinesHowTos": { + "layoutHowTos": { "label": "How-Tos" } } @@ -97,8 +105,8 @@ "dashboards": { "label": "Painéis", "groups": { - "dashboardsCapabilities": { - "label": "Capabilities" + "dashboardsReference": { + "label": "Reference" }, "dashboardsHowTos": { "label": "How-Tos" @@ -108,8 +116,8 @@ "permissionsAccess": { "label": "Permissions & Access", "groups": { - "permissionsAccessCapabilities": { - "label": "Capabilities" + "permissionsAccessReference": { + "label": "Reference" }, "permissionsAccessHowTos": { "label": "How-Tos" @@ -119,8 +127,8 @@ "billing": { "label": "Faturação", "groups": { - "billingCapabilities": { - "label": "Capabilities" + "billingReference": { + "label": "Reference" }, "billingHowTos": { "label": "How-Tos" @@ -130,8 +138,8 @@ "settings": { "label": "Configurações", "groups": { - "settingsCapabilities": { - "label": "Capabilities" + "settingsReference": { + "label": "Reference" }, "settingsHowTos": { "label": "How-Tos" @@ -143,59 +151,20 @@ "developers": { "label": "Programadores", "groups": { - "developersGroup": { - "label": "Programadores" + "developersOverview": { + "label": "Visão Geral" }, - "extend": { - "label": "Extend", - "groups": { - "apps": { - "label": "Aplicativos" - } - } + "apps": { + "label": "Aplicativos" + }, + "api": { + "label": "API" }, "selfHost": { - "label": "Self-Host", - "groups": { - "selfHostCapabilities": { - "label": "Capabilities" - } - } + "label": "Self-Host" }, "contribute": { - "label": "Contribute", - "groups": { - "contributeCapabilities": { - "label": "Capabilities", - "groups": { - "frontendDevelopment": { - "label": "Desenvolvimento Frontend", - "groups": { - "twentyUi": { - "label": "Twenty UI", - "groups": { - "display": { - "label": "Exibir" - }, - "feedback": { - "label": "Feedback" - }, - "input": { - "label": "Entrada" - }, - "navigation": { - "label": "Navegação" - } - } - } - } - }, - "backendDevelopment": { - "label": "Desenvolvimento Backend" - } - } - } - } + "label": "Contribute" } } } diff --git a/packages/twenty-docs/l/pt/twenty-ui/display/app-tooltip.mdx b/packages/twenty-docs/l/pt/twenty-ui/display/app-tooltip.mdx index e1c7760d91b..05f23969c64 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/display/app-tooltip.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/display/app-tooltip.mdx @@ -1,5 +1,6 @@ --- title: Dica do Aplicativo +icon: mensagem --- diff --git a/packages/twenty-docs/l/pt/twenty-ui/display/checkmark.mdx b/packages/twenty-docs/l/pt/twenty-ui/display/checkmark.mdx index 3d4ec74eea9..50582a56eb3 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/display/checkmark.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/display/checkmark.mdx @@ -1,5 +1,6 @@ --- title: Marca de seleção +icon: circle-check --- diff --git a/packages/twenty-docs/l/pt/twenty-ui/display/icons.mdx b/packages/twenty-docs/l/pt/twenty-ui/display/icons.mdx index 925e7df1e97..3f9b1b96f5a 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/display/icons.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/display/icons.mdx @@ -1,5 +1,6 @@ --- title: Ícones +icon: ícones --- diff --git a/packages/twenty-docs/l/pt/twenty-ui/display/soon-pill.mdx b/packages/twenty-docs/l/pt/twenty-ui/display/soon-pill.mdx index 6678b07dd65..1e98627ddbb 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/display/soon-pill.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/display/soon-pill.mdx @@ -2,7 +2,6 @@ title: Selo Em breve --- - Uma pequena insígnia ou "pílula" para indicar que algo está chegando em breve. ```jsx diff --git a/packages/twenty-docs/l/pt/twenty-ui/display/tag.mdx b/packages/twenty-docs/l/pt/twenty-ui/display/tag.mdx index 7f702f1ab41..08f3e49459b 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/display/tag.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/display/tag.mdx @@ -1,8 +1,8 @@ --- title: Etiqueta +icon: etiqueta --- - Componente para categorizar visualmente ou rotular conteúdo. diff --git a/packages/twenty-docs/l/pt/twenty-ui/input/buttons.mdx b/packages/twenty-docs/l/pt/twenty-ui/input/buttons.mdx index 2f3920c9d28..c4275431218 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/input/buttons.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/input/buttons.mdx @@ -1,5 +1,6 @@ --- title: Botões +icon: hand-pointer --- diff --git a/packages/twenty-docs/l/pt/twenty-ui/input/checkbox.mdx b/packages/twenty-docs/l/pt/twenty-ui/input/checkbox.mdx index aa79afadd81..36dc46f1f88 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/input/checkbox.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/input/checkbox.mdx @@ -1,5 +1,6 @@ --- title: Caixa de Seleção +icon: square-check --- diff --git a/packages/twenty-docs/l/pt/twenty-ui/input/color-scheme.mdx b/packages/twenty-docs/l/pt/twenty-ui/input/color-scheme.mdx index daeb485d859..58b4db27576 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/input/color-scheme.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/input/color-scheme.mdx @@ -1,5 +1,6 @@ --- title: Esquema de Cores +icon: paleta --- diff --git a/packages/twenty-docs/l/pt/twenty-ui/input/radio.mdx b/packages/twenty-docs/l/pt/twenty-ui/input/radio.mdx index 9887f2b47f1..ac2ea76bd28 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/input/radio.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/input/radio.mdx @@ -1,5 +1,6 @@ --- title: Rádio +icon: circle-dot --- diff --git a/packages/twenty-docs/l/pt/twenty-ui/input/toggle.mdx b/packages/twenty-docs/l/pt/twenty-ui/input/toggle.mdx index 8ddfdb2a998..59414666d4b 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/input/toggle.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/input/toggle.mdx @@ -1,8 +1,8 @@ --- title: Alternar +icon: toggle-on --- - diff --git a/packages/twenty-docs/l/pt/twenty-ui/introduction.mdx b/packages/twenty-docs/l/pt/twenty-ui/introduction.mdx index 35fd111c0ba..5e6c18e19df 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/introduction.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/introduction.mdx @@ -1,5 +1,6 @@ --- title: Visão Geral +icon: paleta description: Biblioteca de componentes para Twenty CRM --- diff --git a/packages/twenty-docs/l/pt/twenty-ui/navigation.mdx b/packages/twenty-docs/l/pt/twenty-ui/navigation.mdx index 3aa6754ddf9..41b32256979 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/navigation.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/navigation.mdx @@ -1,5 +1,6 @@ --- title: Navegação +icon: compass --- diff --git a/packages/twenty-docs/l/pt/twenty-ui/navigation/links.mdx b/packages/twenty-docs/l/pt/twenty-ui/navigation/links.mdx index 9db5a1918d9..8b4f264edeb 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/navigation/links.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/navigation/links.mdx @@ -1,5 +1,6 @@ --- title: Links +icon: ligação --- diff --git a/packages/twenty-docs/l/pt/twenty-ui/navigation/menu-item.mdx b/packages/twenty-docs/l/pt/twenty-ui/navigation/menu-item.mdx index af36e7feccc..d4914626bd2 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/navigation/menu-item.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/navigation/menu-item.mdx @@ -1,8 +1,8 @@ --- title: Entrada de menu +icon: bars --- - Um item de menu versátil projetado para ser usado em uma lista de menu ou navegação. diff --git a/packages/twenty-docs/l/pt/twenty-ui/navigation/navigation-bar.mdx b/packages/twenty-docs/l/pt/twenty-ui/navigation/navigation-bar.mdx index a4cc7b2627c..6114afade86 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/navigation/navigation-bar.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/navigation/navigation-bar.mdx @@ -1,8 +1,8 @@ --- title: Barra de Navegação +icon: bars --- - Renderiza uma barra de navegação que contém múltiplos componentes `NavigationBarItem`. diff --git a/packages/twenty-docs/l/pt/twenty-ui/progress-bar.mdx b/packages/twenty-docs/l/pt/twenty-ui/progress-bar.mdx index 186815e9f33..ef6cea09eac 100644 --- a/packages/twenty-docs/l/pt/twenty-ui/progress-bar.mdx +++ b/packages/twenty-docs/l/pt/twenty-ui/progress-bar.mdx @@ -2,7 +2,6 @@ title: Feedback --- - Indica progresso ou contagem regressiva e move-se da direita para a esquerda. diff --git a/packages/twenty-docs/l/pt/user-guide/billing/overview.mdx b/packages/twenty-docs/l/pt/user-guide/billing/overview.mdx index 12540890c51..aa651524ae7 100644 --- a/packages/twenty-docs/l/pt/user-guide/billing/overview.mdx +++ b/packages/twenty-docs/l/pt/user-guide/billing/overview.mdx @@ -3,7 +3,6 @@ title: Faturação description: Entenda os preços da Twenty e gerencie sua assinatura. --- - A Twenty oferece planos de preços flexíveis para atender às necessidades da sua equipe. Gerencie sua assinatura, acompanhe créditos de fluxos de trabalho e acesse faturas, tudo em **Configurações → Cobrança**. ## O que há nesta seção diff --git a/packages/twenty-docs/l/pt/user-guide/calendar-emails/overview.mdx b/packages/twenty-docs/l/pt/user-guide/calendar-emails/overview.mdx index a005c2730ea..9fc45ca0fe2 100644 --- a/packages/twenty-docs/l/pt/user-guide/calendar-emails/overview.mdx +++ b/packages/twenty-docs/l/pt/user-guide/calendar-emails/overview.mdx @@ -3,7 +3,6 @@ title: Calendário e emails description: Conecte as suas contas de email e calendário ao Twenty. --- - ## Opções de Conexão ### Conta do Google (Gmail & Google Calendar) diff --git a/packages/twenty-docs/l/pt/user-guide/dashboards/overview.mdx b/packages/twenty-docs/l/pt/user-guide/dashboards/overview.mdx index f94cefc4da8..3cd7cca9e93 100644 --- a/packages/twenty-docs/l/pt/user-guide/dashboards/overview.mdx +++ b/packages/twenty-docs/l/pt/user-guide/dashboards/overview.mdx @@ -3,7 +3,6 @@ title: Painéis description: Aprenda o básico sobre relatórios e painéis no Twenty. --- - Os painéis estão atualmente em versão beta. Ative-os em **Configurações → Atualizações → Acesso antecipado**. diff --git a/packages/twenty-docs/l/pt/user-guide/data-migration/overview.mdx b/packages/twenty-docs/l/pt/user-guide/data-migration/overview.mdx index bdebe379cca..f2fa08ca2e5 100644 --- a/packages/twenty-docs/l/pt/user-guide/data-migration/overview.mdx +++ b/packages/twenty-docs/l/pt/user-guide/data-migration/overview.mdx @@ -5,7 +5,6 @@ description: Importe e exporte os seus dados de CRM através de ficheiros CSV ou import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## Métodos de importação A Twenty suporta dois métodos principais para importar dados: diff --git a/packages/twenty-docs/l/pt/user-guide/data-model/overview.mdx b/packages/twenty-docs/l/pt/user-guide/data-model/overview.mdx index 2386139128b..5c2373d6dd7 100644 --- a/packages/twenty-docs/l/pt/user-guide/data-model/overview.mdx +++ b/packages/twenty-docs/l/pt/user-guide/data-model/overview.mdx @@ -3,7 +3,6 @@ title: Modelo de dados description: Saiba o que é um modelo de dados e como projetar um que se ajuste ao seu negócio. --- - ## O que é um modelo de dados? Um modelo de dados é a estrutura que define como as informações são organizadas em seu CRM. Pense nisso como a **planta** dos dados dos seus clientes — você o projeta uma vez e depois o preenche com seus dados reais. diff --git a/packages/twenty-docs/l/pt/user-guide/introduction.mdx b/packages/twenty-docs/l/pt/user-guide/introduction.mdx index f882b9acb84..3ac9959e973 100644 --- a/packages/twenty-docs/l/pt/user-guide/introduction.mdx +++ b/packages/twenty-docs/l/pt/user-guide/introduction.mdx @@ -1,16 +1,11 @@ --- -title: Descubra o Twenty +title: User Guide description: Bem-vindo ao Guia do Usuário do Twenty, sua fonte para configurações avançadas e melhores práticas. --- import { CardTitle } from "/snippets/card-title.mdx" - - Descubra o Twenty - Saiba o que é o Twenty e como ele pode ajudar sua empresa. - - Modelo de Dados Personalize seu modelo de dados para se adequar aos processos da sua empresa. @@ -36,9 +31,9 @@ import { CardTitle } from "/snippets/card-title.mdx" Aprimore sua equipe com agentes de IA. - - Visualizações & Pipelines - Organize seus dados com visualizações e pipelines acionáveis. + + Layout + Navigation, views, and record page customization. diff --git a/packages/twenty-docs/l/pt/user-guide/layout/capabilities/navigation.mdx b/packages/twenty-docs/l/pt/user-guide/layout/capabilities/navigation.mdx new file mode 100644 index 00000000000..418eb28d094 --- /dev/null +++ b/packages/twenty-docs/l/pt/user-guide/layout/capabilities/navigation.mdx @@ -0,0 +1,32 @@ +--- +title: Navegação +description: Customize the left sidebar to match how your team works. +--- + +The left sidebar is your primary way to move around Twenty. It's fully customizable — you can reorganize it to match your workflow without touching any settings page. + +## Reordering items + +Drag and drop any item in the sidebar to change its position. The order is saved per user, so each team member can arrange their own sidebar. + +## Pastas + +Group related items into folders. For example, you might create a "Sales" folder containing your pipeline views, a "Support" folder for tickets, or an "Operations" folder for internal objects. + +To create a folder, right-click in the sidebar or use the `+` button. + +## Hiding objects + +Objects you don't use can be hidden from the sidebar. They're not deleted — they're just out of the way. You can show them again anytime from Settings > Data Model. + +## Favoritos + +Pin views, records, or searches to the Favorites section at the top of the sidebar for one-click access. Favorites are personal — each user manages their own. + +## Custom links + +Add links to external tools directly in the sidebar. Useful for linking to your wiki, dashboards in other tools, or any URL your team uses regularly. + +## Command menu + +Press `Cmd+K` (or `Ctrl+K`) to open the command menu — a quick-access search bar for jumping to any record, view, or action without navigating the sidebar. diff --git a/packages/twenty-docs/l/pt/user-guide/layout/capabilities/record-pages.mdx b/packages/twenty-docs/l/pt/user-guide/layout/capabilities/record-pages.mdx new file mode 100644 index 00000000000..3637f9bc07c --- /dev/null +++ b/packages/twenty-docs/l/pt/user-guide/layout/capabilities/record-pages.mdx @@ -0,0 +1,51 @@ +--- +title: Páginas de Registos +description: Personalize o layout das páginas de detalhe de registos individuais com abas e widgets. +--- + +Ao abrir um registo no Twenty, a página de detalhe é composta por **abas** e **widgets**. Ambos são totalmente personalizáveis por tipo de objeto. + +## Abas + +Cada página de registro pode ter várias abas — como as abas de um navegador. Use-as para organizar diferentes aspectos de um registro. Por exemplo, um registro de Empresa pode ter abas para Visão geral, Comunicação, Tarefas e Arquivos. + +Pode: + +* Adicionar e remover abas +* Renomear abas +* Reordenar abas arrastando +* Definir qual aba é exibida por padrão + +## Widgets + +Os widgets são os componentes básicos dentro de cada aba. Tipos de widget disponíveis: + +| Widget | O que mostra | +| -------------------------- | ------------------------------------------------------ | +| **Campos** | Campos do registro, agrupados ou individualmente | +| **Registros relacionados** | Tabela de registros vinculados por meio de uma relação | +| **Emails** | Histórico de e-mails das contas conectadas | +| **Calendário** | Eventos de calendário associados ao registro | +| **Linha do tempo** | Histórico de atividades e eventos | +| **Tarefas** | Tarefas associadas | +| **Notas** | Notas em texto enriquecido | +| **Arquivos** | Anexos de ficheiro | +| **Gráficos** | Dados visuais de registros relacionados | +| **iFrame** | Conteúdo externo incorporado | +| **Texto enriquecido** | Conteúdo estático ou descrições | + +## Personalizar uma página de registro + +1. Abra qualquer registro +2. Pressione `Cmd+K` e pesquise por "Edit record page layout" +3. Agora você está no modo de personalização: + * **Adicionar widgets** no seletor de widgets + * **Arraste widgets** para reposicioná-los na grade + * **Redimensione os widgets** arrastando suas bordas + * **Configure campos** exibidos em cada widget + * **Gerencie abas** — adicione, remova, renomeie, reordene +4. Salve suas alterações — elas se aplicam a todos os registros desse tipo de objeto + +## Visibilidade de campos + +Em um widget de Campos, você pode controlar quais campos ficam visíveis e em que ordem. Isso permite criar layouts focados — por exemplo, mostrar apenas os campos mais importantes na aba Visão geral e colocar os campos detalhados em uma aba separada. diff --git a/packages/twenty-docs/l/pt/user-guide/layout/overview.mdx b/packages/twenty-docs/l/pt/user-guide/layout/overview.mdx new file mode 100644 index 00000000000..ac1ba1443ae --- /dev/null +++ b/packages/twenty-docs/l/pt/user-guide/layout/overview.mdx @@ -0,0 +1,45 @@ +--- +title: Layout +description: Customize how you navigate, browse, and view records in Twenty. +--- + +Twenty's layout is customizable at three levels: how you navigate the app, how you browse lists of records, and what you see when you open an individual record. + +## Navegação + +The left sidebar is fully customizable. Pode: + +* **Reorder items** by dragging and dropping +* **Create folders** to group related objects and views +* **Hide objects** you don't use +* **Add custom links** to external tools +* **Pin favorites** for quick access to views, records, or searches + +[Navigation reference →](/l/pt/user-guide/layout/capabilities/navigation) + +## Visualizações + +Views control how lists of records are displayed. Twenty supports three view types: + +| Vista | Best for | +| ------------ | ---------------------------------------------------------------------- | +| **Table** | Working with many records at once — spreadsheet-style rows and columns | +| **Kanban** | Pipeline tracking — drag-and-drop cards organized by stage | +| **Calendar** | Time-based planning — records plotted by a date field | + +Each view saves its own filters, sorting, field visibility, and grouping configuration. Views can be shared with the workspace or kept private. + +[Table views →](/l/pt/user-guide/views-pipelines/capabilities/table-views) · [Kanban views →](/l/pt/user-guide/views-pipelines/capabilities/kanban-views) · [Calendar view →](/l/pt/user-guide/views-pipelines/capabilities/calendar-view) + +## Record pages + +When you open a record, the detail page is built from configurable tabs and widgets. Pode: + +* **Add, remove, and reorder tabs** on any record type +* **Configure widgets** — fields, related records, emails, timeline, calendar, tasks, notes, files, charts, iframes, and more +* **Drag and resize widgets** on a grid layout +* **Control field visibility** per widget + +Enter layout customization mode from the command menu (`Cmd+K` → "Edit record page layout"). + +[Record pages reference →](/l/pt/user-guide/layout/capabilities/record-pages) diff --git a/packages/twenty-docs/l/pt/user-guide/permissions-access/overview.mdx b/packages/twenty-docs/l/pt/user-guide/permissions-access/overview.mdx index c8dbc92a4c6..b74f474b21b 100644 --- a/packages/twenty-docs/l/pt/user-guide/permissions-access/overview.mdx +++ b/packages/twenty-docs/l/pt/user-guide/permissions-access/overview.mdx @@ -3,7 +3,6 @@ title: Permissões e Acesso description: Gerencie funções, permissões e o controle de acesso no seu espaço de trabalho. --- - O sistema de permissões do Twenty permite controlar quem pode acessar e modificar dados no seu espaço de trabalho. Crie funções, atribua permissões e configure SSO para um acesso seguro. ## O que há nesta seção diff --git a/packages/twenty-docs/l/pt/user-guide/settings/overview.mdx b/packages/twenty-docs/l/pt/user-guide/settings/overview.mdx index f584f2a059c..91793600210 100644 --- a/packages/twenty-docs/l/pt/user-guide/settings/overview.mdx +++ b/packages/twenty-docs/l/pt/user-guide/settings/overview.mdx @@ -3,7 +3,6 @@ title: Configurações description: Configure seu espaço de trabalho Twenty com configurações essenciais. --- - ## Configuração Inicial Ao criar seu espaço de trabalho pela primeira vez, há várias configurações importantes para definir. diff --git a/packages/twenty-docs/l/pt/user-guide/views-pipelines/overview.mdx b/packages/twenty-docs/l/pt/user-guide/views-pipelines/overview.mdx index e71e58064fd..c197d2e7b30 100644 --- a/packages/twenty-docs/l/pt/user-guide/views-pipelines/overview.mdx +++ b/packages/twenty-docs/l/pt/user-guide/views-pipelines/overview.mdx @@ -5,7 +5,6 @@ description: Aprenda a criar e gerenciar visualizações no Twenty. import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## Compreendendo as visualizações As visualizações são configurações salvas que determinam como seus dados são exibidos. Cada visualização pode ter: diff --git a/packages/twenty-docs/l/pt/user-guide/workflows/overview.mdx b/packages/twenty-docs/l/pt/user-guide/workflows/overview.mdx index 5473595dec6..16750497801 100644 --- a/packages/twenty-docs/l/pt/user-guide/workflows/overview.mdx +++ b/packages/twenty-docs/l/pt/user-guide/workflows/overview.mdx @@ -3,7 +3,6 @@ title: Fluxos de trabalho description: Aprenda a criar automações no Twenty. --- - ## Por que Workflows Importam Twenty foi criado para trazer flexibilidade máxima aos seus usuários. Em vez de forçá-lo a adaptar seus processos de negócios a recursos rígidos e pré-construídos, os workflows permitem que você crie automações que criem o CRM que melhor apoie seus casos de uso de negócios exclusivos. diff --git a/packages/twenty-docs/l/ru/developers/contribute/capabilities/backend-development/server-commands.mdx b/packages/twenty-docs/l/ru/developers/contribute/capabilities/backend-development/server-commands.mdx index d037ead3967..26a0f1b7a64 100644 --- a/packages/twenty-docs/l/ru/developers/contribute/capabilities/backend-development/server-commands.mdx +++ b/packages/twenty-docs/l/ru/developers/contribute/capabilities/backend-development/server-commands.mdx @@ -1,5 +1,6 @@ --- title: Команды бекэнда +icon: terminal --- ## Полезные команды diff --git a/packages/twenty-docs/l/ru/developers/contribute/capabilities/bug-and-requests.mdx b/packages/twenty-docs/l/ru/developers/contribute/capabilities/bug-and-requests.mdx index b0c5993bbc5..0fe580fd308 100644 --- a/packages/twenty-docs/l/ru/developers/contribute/capabilities/bug-and-requests.mdx +++ b/packages/twenty-docs/l/ru/developers/contribute/capabilities/bug-and-requests.mdx @@ -1,5 +1,6 @@ --- title: Ошибки, запросы и PR +icon: bug info: Сообщайте о проблемах, предлагайте новые функции и вносите вклад в код --- diff --git a/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/best-practices-front.mdx b/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/best-practices-front.mdx index 84b303e5f1e..d4337d313ca 100644 --- a/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/best-practices-front.mdx +++ b/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/best-practices-front.mdx @@ -1,5 +1,6 @@ --- title: Лучшие практики +icon: star --- Этот документ описывает лучшие практики, которых следует придерживаться при работе с фронтендом. diff --git a/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx b/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx index e68b0dd55d2..ef4238d4255 100644 --- a/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx +++ b/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx @@ -1,5 +1,6 @@ --- title: Архитектура папок +icon: folder-tree info: Подробное рассмотрение нашей архитектуры папок --- diff --git a/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/frontend-commands.mdx b/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/frontend-commands.mdx index 53927abd3db..980118b79ef 100644 --- a/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/frontend-commands.mdx +++ b/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/frontend-commands.mdx @@ -1,5 +1,6 @@ --- title: Команды фронтенда +icon: terminal --- ## Полезные команды diff --git a/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/style-guide.mdx b/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/style-guide.mdx index 5c973b68d3f..784b38b4a20 100644 --- a/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/style-guide.mdx +++ b/packages/twenty-docs/l/ru/developers/contribute/capabilities/frontend-development/style-guide.mdx @@ -1,5 +1,6 @@ --- title: Руководство по стилю +icon: paintbrush --- Этот документ включает правила, которые нужно соблюдать при написании кода. diff --git a/packages/twenty-docs/l/ru/developers/contribute/capabilities/local-setup.mdx b/packages/twenty-docs/l/ru/developers/contribute/capabilities/local-setup.mdx index 1dffc98a868..04b43f5d002 100644 --- a/packages/twenty-docs/l/ru/developers/contribute/capabilities/local-setup.mdx +++ b/packages/twenty-docs/l/ru/developers/contribute/capabilities/local-setup.mdx @@ -1,5 +1,6 @@ --- title: Локальная настройка +icon: laptop-code description: Руководство для участников (или любопытных разработчиков), которые хотят запускать Twenty локально. --- diff --git a/packages/twenty-docs/l/ru/developers/contribute/commands.mdx b/packages/twenty-docs/l/ru/developers/contribute/commands.mdx new file mode 100644 index 00000000000..750c5996dce --- /dev/null +++ b/packages/twenty-docs/l/ru/developers/contribute/commands.mdx @@ -0,0 +1,77 @@ +--- +title: Commands +icon: terminal +description: Useful commands for developing Twenty. +--- + +Commands can be run from the repository root using `npx nx`. Use `npx nx run {project}:{command}` for explicit targeting. + +## Starting the App + +```bash +npx nx start twenty-front # Frontend dev server (http://localhost:3001) +npx nx start twenty-server # Backend server (http://localhost:3000) +npx nx run twenty-server:worker # Background worker +``` + +## Database + +```bash +npx nx database:reset twenty-server # Reset and seed database +npx nx run twenty-server:database:migrate:prod # Run migrations +npx nx run twenty-server:database:migrate:generate --name --type # Generate a migration +``` + +## Linting + +```bash +npx nx lint:diff-with-main twenty-front # Lint changed files (fastest) +npx nx lint:diff-with-main twenty-server +npx nx lint twenty-front --configuration=fix # Auto-fix +``` + +## Type Checking + +```bash +npx nx typecheck twenty-front +npx nx typecheck twenty-server +``` + +## Тестирование + +```bash +# Frontend +npx nx test twenty-front # Jest unit tests +npx nx storybook:build twenty-front # Build Storybook +npx nx storybook:test twenty-front # Storybook tests + +# Backend +npx nx run twenty-server:test:unit # Unit tests +npx nx run twenty-server:test:integration # Integration tests +npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset + +# Single file (fastest) +npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs +``` + +## GraphQL + +```bash +npx nx run twenty-front:graphql:generate # Regenerate types +npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema +``` + +## Переводы + +```bash +npx nx run twenty-front:lingui:extract # Extract strings +npx nx run twenty-front:lingui:compile # Compile translations +``` + +## Build + +```bash +npx nx build twenty-shared # Must be built first +npx nx build twenty-front +npx nx build twenty-server +``` diff --git a/packages/twenty-docs/l/ru/developers/contribute/style-guide.mdx b/packages/twenty-docs/l/ru/developers/contribute/style-guide.mdx new file mode 100644 index 00000000000..81d0ce68715 --- /dev/null +++ b/packages/twenty-docs/l/ru/developers/contribute/style-guide.mdx @@ -0,0 +1,176 @@ +--- +title: Руководство по стилю +icon: paintbrush +description: Code conventions and best practices for contributing to Twenty. +--- + +## React + +### Functional components only + +Always use TSX functional components with named exports. + +```tsx +// ❌ Bad +const MyComponent = () => { + return
Hello World
; +}; +export default MyComponent; + +// ✅ Good +export function MyComponent() { + return
Hello World
; +}; +``` + +### Свойства + +Create a type named `{ComponentName}Props`. Use destructuring. Don't use `React.FC`. + +```tsx +type MyComponentProps = { + name: string; +}; + +export const MyComponent = ({ name }: MyComponentProps) =>
Hello {name}
; +``` + +### No single-variable prop spreading + +```tsx +// ❌ Bad +const MyComponent = (props: MyComponentProps) => ; + +// ✅ Good +const MyComponent = ({ prop1, prop2 }: MyComponentProps) => ; +``` + +## Управление состоянием + +### Jotai atoms for global state + +```tsx +import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; +import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; + +export const myAtomState = createAtomState({ + key: 'myAtomState', + defaultValue: 'default value', +}); +``` + +* Prefer atoms over prop drilling +* Don't use `useRef` for state — use `useState` or atoms +* Use atom families and selectors for lists + +### Avoid unnecessary re-renders + +* Extract `useEffect` and data fetching into sibling sidecar components +* Prefer event handlers (`handleClick`, `handleChange`) over `useEffect` +* Don't use `React.memo()` — fix the root cause instead +* Limit `useCallback` / `useMemo` usage + +```tsx +// ❌ Bad — useEffect in the same component causes re-renders +export const Page = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return
{data}
; +}; + +// ✅ Good — extract into sibling +export const PageData = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return <>; +}; +export const Page = () => { + const [data] = useAtomState(dataState); + return
{data}
; +}; +``` + +## TypeScript + +* **`type` over `interface`** — more flexible, easier to compose +* **String literals over enums** — except for GraphQL codegen enums and internal library APIs +* **No `any`** — strict TypeScript enforced +* **No type imports** — use regular imports (enforced by Oxlint `typescript/consistent-type-imports`) +* **Use [Zod](https://github.com/colinhacks/zod)** for runtime validation of untyped objects + +## JavaScript + +```tsx +// Use nullish-coalescing (??) instead of || +const value = process.env.MY_VALUE ?? 'default'; + +// Use optional chaining +onClick?.(); +``` + +## Называние + +* **Variables**: camelCase, descriptive (`email` not `value`, `fieldMetadata` not `fm`) +* **Constants**: SCREAMING_SNAKE_CASE +* **Types/Classes**: PascalCase +* **Files/directories**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`) +* **Event handlers**: `handleClick` (not `onClick` for the handler function) +* **Component props**: prefix with component name (`ButtonProps`) +* **Styled components**: prefix with `Styled` (`StyledTitle`) + +## Стилизация + +Use [Linaria](https://github.com/callstack/linaria) styled components. Use theme values — avoid hardcoded `px`, `rem`, or colors. + +```tsx +// ❌ Bad +const StyledButton = styled.button` + color: #333333; + font-size: 1rem; + margin-left: 4px; +`; + +// ✅ Good +const StyledButton = styled.button` + color: ${({ theme }) => theme.font.color.primary}; + font-size: ${({ theme }) => theme.font.size.md}; + margin-left: ${({ theme }) => theme.spacing(1)}; +`; +``` + +## Импорт + +Use aliases instead of relative paths: + +```tsx +// ❌ Bad +import { Foo } from '../../../../../testing/decorators/Foo'; + +// ✅ Good +import { Foo } from '~/testing/decorators/Foo'; +import { Bar } from '@/modules/bar/components/Bar'; +``` + +## Folder Structure + +``` +front +└── modules/ # Feature modules +│ └── module1/ +│ ├── components/ +│ ├── constants/ +│ ├── contexts/ +│ ├── graphql/ (fragments, queries, mutations) +│ ├── hooks/ +│ ├── states/ (atoms, selectors) +│ ├── types/ +│ └── utils/ +└── pages/ # Route-level components +└── ui/ # Reusable UI components (display, input, feedback, ...) +``` + +* Modules can import from other modules, but `ui/` should stay dependency-free +* Use `internal/` subfolders for module-private code +* Components under 300 lines, services under 500 lines diff --git a/packages/twenty-docs/l/ru/developers/extend/api.mdx b/packages/twenty-docs/l/ru/developers/extend/api.mdx index 6364fe68ac0..4959f87b26d 100644 --- a/packages/twenty-docs/l/ru/developers/extend/api.mdx +++ b/packages/twenty-docs/l/ru/developers/extend/api.mdx @@ -1,147 +1,55 @@ --- title: API -description: Запрашивайте и изменяйте данные CRM программно с помощью REST или GraphQL. +icon: plug +description: REST and GraphQL APIs generated from your workspace schema. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -Twenty разработан для удобства разработчиков и предлагает мощные API, которые адаптируются к вашей пользовательской модели данных. Мы предоставляем четыре различных типа API, чтобы удовлетворить различные интеграционные потребности. +## Schema-per-tenant APIs -## Подход, ориентированный на разработчиков +There is no static API reference for Twenty. Each workspace has its own schema — when you add a custom object (say `Invoice`), it immediately gets REST and GraphQL endpoints identical to built-in objects like `Company` or `Person`. The API is generated from the schema, so endpoints use your object and field names directly — no opaque IDs. -Twenty генерирует API специально для вашей модели данных: +Your workspace-specific API documentation is available under **Settings → API & Webhooks** after creating an API key. It includes an interactive playground where you can execute real calls against your data. -* **Длинные ID не требуются**: используйте названия объектов и полей прямо в конечных точках. -* **Стандартные и пользовательские объекты обрабатываются одинаково**: ваши пользовательские объекты получают такой же доступ к API, как и встроенные. -* **Выделенные конечные точки**: каждый объект и поле получают свою собственную конечную точку API. -* **Пользовательская документация**: генерируется специально для модели данных вашего рабочего пространства. +## Two APIs - -Персонализированная документация по вашему API доступна в разделе **Настройки → API и вебхуки** после создания ключа API. Поскольку Twenty генерирует API, соответствующие вашей пользовательской модели данных, документация уникальна для вашего рабочего пространства. - +**Core API** — `/rest/` and `/graphql/` -## Два типа API +CRUD on records: People, Companies, Opportunities, your custom objects. Query, filter, traverse relations. -### Основной API +**Metadata API** — `/rest/metadata/` and `/metadata/` -Доступен на `/rest/` или `/graphql/` +Schema management: create/modify/delete objects, fields, and relations. This is how you programmatically change your data model. -Работайте с реальными **записями** (данными): +Both are available as REST and GraphQL. GraphQL adds batch upserts and the ability to traverse relations in a single query. Same underlying data either way. -* Создавайте, читайте, обновляйте и удаляйте People, Companies, Opportunities и т. д. -* Запрашивайте и фильтруйте данные -* Управление отношениями записей. +## Base URLs -### API метаданных - -Доступен на `/rest/metadata/` или `/metadata/` - -Управляйте своим **рабочим пространством и моделью данных**: - -* Создание, изменение или удаление объектов и полей. -* Настройка параметров рабочего пространства. -* Определяйте связи между объектами - -## REST против GraphQL - -И Core, и Metadata API доступны в форматах REST и GraphQL: - -| Формат | Доступные операции | -| ----------- | ------------------------------------------------------------------------ | -| **REST** | CRUD, пакетные операции, upsert-операции | -| **GraphQL** | То же самое + **пакетные upsert-операции**, запросы связей за один вызов | - -Выбирайте по своим потребностям — оба формата обращаются к одним и тем же данным. - -## Конечные точки API - -| Среда | Базовый URL | -| --------------------------- | ------------------------- | -| **Облако** | `https://api.twenty.com/` | -| **Самостоятельный хостинг** | `https://{your-domain}/` | +| Среда | Базовый URL | +| ----------- | ------------------------- | +| Cloud | `https://api.twenty.com/` | +| Self-Hosted | `https://{your-domain}/` | ## Аутентификация -Каждый запрос к API требует ключ API в заголовке: - ``` Authorization: Bearer YOUR_API_KEY ``` -### Создать ключ API - -1. Перейдите в **Настройки → API и вебхуки** -2. Нажмите **+ Создать ключ** -3. Настройки: - * **Имя**: описательное название для ключа - * **Дата истечения**: когда истекает срок действия ключа -4. Нажмите **Сохранить** -5. **Скопируйте сразу** — ключ показывается только один раз +Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access. - -Ваш ключ API предоставляет доступ к конфиденциальным данным. Не делитесь им с ненадежными сервисами. Если он скомпрометирован, немедленно отключите его и создайте новый. - +For OAuth-based access (external apps acting on behalf of users), see [OAuth](/l/ru/developers/extend/oauth). -### Назначить роль ключу API +## Batch operations -Для повышения безопасности назначьте конкретную роль, чтобы ограничить доступ: +Both REST and GraphQL support batching up to 60 records per request — create, update, or delete. GraphQL also supports batch upsert (create-or-update in one call) using plural names like `CreateCompanies`. -1. Перейдите в **Настройки → Роли** -2. Нажмите на роль, которую хотите назначить -3. Откройте вкладку **Назначение** -4. В разделе **Ключи API** нажмите **+ Назначить ключу API** -5. Выберите ключ API +## Rate limits -Ключ унаследует разрешения этой роли. См. [Разрешения](/l/ru/user-guide/permissions-access/capabilities/permissions) для подробностей. - -### Управление API-ключами - -**Сгенерировать заново**: Настройки → API и вебхуки → Нажмите на ключ → **Сгенерировать заново** - -**Удалить**: Настройки → API и вебхуки → Нажмите ключ → **Удалить** - -## Песочница API - -Тестируйте свои API прямо в браузере с нашей встроенной песочницей — доступной как для **REST**, так и для **GraphQL**. - -### Доступ к песочнице - -1. Перейдите в **Настройки → API и вебхуки** -2. Создайте ключ API (обязательно) -3. Нажмите на **REST API** или **GraphQL API**, чтобы открыть песочницу - -### Что вы получаете - -* **Интерактивная документация**: генерируется для вашей конкретной модели данных -* **Тестирование в реальном времени**: выполняйте реальные вызовы API к вашему рабочему пространству -* **Обозреватель схемы**: просматривайте доступные объекты, поля и связи -* **Конструктор запросов**: создавайте запросы с автодополнением - -Песочница отражает ваши пользовательские объекты и поля, поэтому документация всегда точна для вашего рабочего пространства. - -## Пакетные операции - -И REST, и GraphQL поддерживают пакетные операции: - -* **Размер пакета**: до 60 записей на запрос. -* **Операции**: создание, обновление, удаление нескольких записей - -**Функции только для GraphQL:** - -* **Пакетный upsert**: создание или обновление за один вызов -* Используйте имена объектов во множественном числе (например, `CreateCompanies` вместо `CreateCompany`) - -## Лимиты скорости - -Запросы к API ограничиваются для обеспечения стабильности платформы: - -| Лимит | Значение | -| ----------------- | ------------------------- | -| **Запросы** | 100 запросов в минуту | -| **Размер пакета** | 60 записей за один запрос | - - -Используйте пакетные операции, чтобы максимизировать пропускную способность — обрабатывайте до 60 записей за один запрос API вместо выполнения отдельных запросов. - +| Лимит | Значение | +| ---------- | ------------------------- | +| Requests | 100 per minute | +| Batch size | 60 записей за один запрос | diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/building.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/building.mdx index 30d4a889cc3..3509497699c 100644 --- a/packages/twenty-docs/l/ru/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/ru/developers/extend/apps/building.mdx @@ -1,2062 +1,104 @@ --- -title: Создание приложений -description: Определяйте объекты, функции логики, компоненты фронтенда и многое другое с помощью Twenty SDK. +title: Архитектура +description: How Twenty apps work — sandboxing, lifecycle, and the building blocks. +icon: sitemap --- - - Приложения сейчас проходят альфа-тестирование. Функция работает, но продолжает развиваться. - +Twenty apps are TypeScript packages that extend your workspace with custom objects, logic, UI components, and AI capabilities. They run on the Twenty platform with full sandboxing and permission controls. -Пакет `twenty-sdk` предоставляет типизированные строительные блоки для создания вашего приложения. На этой странице описаны все типы сущностей и клиенты API, доступные в SDK. +## How apps work -## Функции DefineEntity +An app is a collection of **entities** declared using `defineEntity()` functions from the `twenty-sdk` package. The SDK detects these declarations via AST analysis at build time and produces a **manifest** — a complete description of what your app adds to a workspace. -SDK предоставляет функции для определения сущностей вашего приложения. Вы должны использовать `export default defineEntity({...})`, чтобы SDK обнаруживал ваши сущности. Эти функции проверяют вашу конфигурацию на этапе сборки и обеспечивают автодополнение в IDE и безопасность типов. - - - **Организация файлов — на ваше усмотрение.** - Обнаружение сущностей основано на AST — SDK находит вызовы `export default defineEntity(...)` независимо от расположения файла. Группировка файлов по типу (например, `logic-functions/`, `roles/`) — это лишь соглашение, а не требование. - - - - - -Роли инкапсулируют права на объекты и действия вашего рабочего пространства. - -```ts restricted-company-role.ts -import { - defineRole, - PermissionFlag, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -export default defineRole({ - universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', - label: 'My new role', - description: 'A role that can be used in your workspace', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - fieldUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); ``` - - - - -В каждом приложении должен быть ровно один вызов `defineApplication`, который описывает: - -* **Идентификация**: идентификаторы, отображаемое имя и описание. -* **Разрешения**: какую роль используют его функции и фронтенд-компоненты. -* **(Необязательно) Переменные**: пары ключ–значение, доступные вашим функциям как переменные окружения. -* **(Необязательно) Предустановочные / постустановочные функции**: логические функции, которые запускаются до или после установки. - -```ts src/application-config.ts -import { defineApplication } from 'twenty-sdk/define'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Заметки: -* Поля `universalIdentifier` — это детерминированные идентификаторы, которые принадлежат вам. Сгенерируйте их один раз и сохраняйте неизменными между синхронизациями. -* `applicationVariables` становятся переменными окружения для ваших функций и фронтенд-компонентов (например, `DEFAULT_RECIPIENT_NAME` доступна как `process.env.DEFAULT_RECIPIENT_NAME`). -* `defaultRoleUniversalIdentifier` должен ссылаться на роль, определённую с помощью `defineRole()` (см. выше). -* Предустановочные и постустановочные функции обнаруживаются автоматически во время сборки манифеста — вам не нужно указывать их в `defineApplication()`. - -#### Метаданные маркетплейса - -Если вы планируете [опубликовать приложение](/l/ru/developers/extend/apps/publishing), эти необязательные поля определяют, как оно отображается в маркетплейсе: - -| Поле | Описание | -| ------------------ | ------------------------------------------------------------------------------------------------------------------- | -| `author` | Имя автора или название компании | -| `category` | Категория приложения для фильтрации в маркетплейсе | -| `logoUrl` | Путь к логотипу вашего приложения (например, `public/logo.png`) | -| `screenshots` | Массив путей к скриншотам (например, `public/screenshot-1.png`) | -| `aboutDescription` | Расширенное описание в Markdown для вкладки "About". Если опущено, маркетплейс использует `README.md` пакета из npm | -| `websiteUrl` | Ссылка на ваш сайт | -| `termsUrl` | Ссылка на условия предоставления услуг | -| `emailSupport` | Адрес электронной почты поддержки | -| `issueReportUrl` | Ссылка на систему отслеживания проблем | - -#### Роли и разрешения - -Поле `defaultRoleUniversalIdentifier` в `application-config.ts` обозначает роль по умолчанию, используемую логическими функциями и фронтенд-компонентами вашего приложения. Подробности см. в `defineRole` выше. - -* Токен времени выполнения, подставляемый как `TWENTY_APP_ACCESS_TOKEN`, формируется из этой роли. -* Типизированный клиент ограничен правами, предоставленными этой ролью. -* Следуйте принципу наименьших привилегий: создайте отдельную роль только с теми правами, которые нужны вашим функциям. - -##### Роль функции по умолчанию - -Когда вы генерируете новое приложение, CLI создаёт файл роли по умолчанию: - -```ts src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk/define'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: true, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [], - fieldPermissions: [], - permissionFlags: [], -}); -``` - -Значение `universalIdentifier` этой роли указывается в `application-config.ts` как `defaultRoleUniversalIdentifier`: - -* **\*.role.ts** определяет, что может делать роль. -* **application-config.ts** указывает на эту роль, чтобы ваши функции наследовали её права. - -Заметки: -* Начните со сгенерированной роли, затем постепенно ограничивайте её, следуя принципу наименьших привилегий. -* Замените `objectPermissions` и `fieldPermissions` на объекты и поля, которые действительно нужны вашим функциям. -* `permissionFlags` управляют доступом к возможностям на уровне платформы. Сведите их к минимуму. -* См. рабочий пример: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - - - - -Пользовательские объекты описывают как схему, так и поведение записей в вашем рабочем пространстве. Используйте `defineObject()` для определения объектов со встроенной валидацией: - -```ts postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk/define'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Основные моменты: - -* Используйте `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`, чтобы указать, какой объект они расширяют: - -```ts src/fields/company-loyalty-tier.field.ts -import { defineField, FieldType } from 'twenty-sdk/define'; - -export default defineField({ - universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', - objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object - name: 'loyaltyTier', - type: FieldType.SELECT, - label: 'Loyalty Tier', - icon: 'IconStar', - options: [ - { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, - { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, - { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, - ], -}); -``` - -Основные моменты: -* `objectUniversalIdentifier` определяет целевой объект. Для стандартных объектов используйте `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`, экспортируемые из `twenty-sdk`. -* При определении полей непосредственно в `defineObject()` вам не нужен `objectUniversalIdentifier` — он наследуется от родительского объекта. -* `defineField()` — единственный способ добавить поля к объектам, которые вы не создавали с помощью `defineObject()`. - - - - -Отношения связывают объекты между собой. В Twenty отношения всегда двунаправленные — вы определяете обе стороны, и каждая сторона ссылается на другую. - -Существуют два типа отношений: - -| Тип отношения | Описание | Есть внешний ключ? | -| ------------- | --------------------------------------------------------------------- | ---------------------- | -| `MANY_TO_ONE` | Многие записи этого объекта указывают на одну запись целевого объекта | Да (`joinColumnName`) | -| `ONE_TO_MANY` | Одна запись этого объекта имеет много записей целевого объекта | Нет (обратная сторона) | - -#### Как работают отношения - -Каждое отношение требует **двух полей**, которые ссылаются друг на друга: - -1. Сторона **MANY_TO_ONE** — находится в объекте, который содержит внешний ключ -2. Сторона **ONE_TO_MANY** — находится в объекте, которому принадлежит коллекция - -Оба поля используют `FieldType.RELATION` и ссылаются друг на друга через `relationTargetFieldMetadataUniversalIdentifier`. - -#### Пример: Почтовая открытка имеет много получателей - -Предположим, `PostCard` может быть отправлен множству записей `PostCardRecipient`. Каждый получатель относится ровно к одной открытке. - -**Шаг 1: Определите сторону ONE_TO_MANY на PostCard** (сторона "one"): - -```ts src/fields/post-card-recipients-on-post-card.field.ts -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; -// Import from the other side -import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; - -export default defineField({ - universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCardRecipients', - label: 'Post Card Recipients', - icon: 'IconUsers', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); -``` - -**Шаг 2: Определите сторону MANY_TO_ONE на PostCardRecipient** (сторона "many" — содержит внешний ключ): - -```ts src/fields/post-card-on-post-card-recipient.field.ts -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; -// Import from the other side -import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; - -export default defineField({ - universalIdentifier: POST_CARD_FIELD_ID, - objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - icon: 'IconMail', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, -}); +your-app/ +├── src/ +│ ├── application-config.ts ← defineApplication (required, one per app) +│ ├── roles/ ← defineRole +│ ├── objects/ ← defineObject +│ ├── fields/ ← defineField +│ ├── logic-functions/ ← defineLogicFunction +│ ├── front-components/ ← defineFrontComponent +│ ├── skills/ ← defineSkill +│ ├── agents/ ← defineAgent +│ ├── views/ ← defineView +│ ├── navigation-menu-items/ ← defineNavigationMenuItem +│ └── page-layouts/ ← definePageLayout +├── public/ ← Static assets (images, icons) +└── package.json ``` -**Циклические импорты:** Оба поля отношений ссылаются на `universalIdentifier` друг друга. Чтобы избежать проблем с циклическими импортами, экспортируйте идентификаторы полей как именованные константы из каждого файла и импортируйте их в другом файле. Система сборки разрешает это на этапе компиляции. + **File organization is up to you.** Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. The folder structure above is a convention, not a requirement. -#### Связывание со стандартными объектами - -Чтобы создать отношение со встроенным объектом Twenty (Person, Company и т. д.), используйте `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: - -```ts src/fields/person-on-self-hosting-user.field.ts -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; -import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; - -export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; -export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; - -export default defineField({ - universalIdentifier: PERSON_FIELD_ID, - objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - description: 'Person matching with the self hosting user', - isNullable: true, - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'personId', - }, -}); -``` - -#### Свойства поля отношения - -| Свойство | Обязательно | Описание | -| ------------------------------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------- | -| `type` | Да | Должно быть `FieldType.RELATION` | -| `relationTargetObjectMetadataUniversalIdentifier` | Да | `universalIdentifier` целевого объекта | -| `relationTargetFieldMetadataUniversalIdentifier` | Да | `universalIdentifier` соответствующего поля на целевом объекте | -| `universalSettings.relationType` | Да | `RelationType.MANY_TO_ONE` или `RelationType.ONE_TO_MANY` | -| `universalSettings.onDelete` | Только для MANY_TO_ONE | Что происходит при удалении связанной записи: `CASCADE`, `SET_NULL`, `RESTRICT` или `NO_ACTION` | -| `universalSettings.joinColumnName` | Только для MANY_TO_ONE | Имя столбца базы данных для внешнего ключа (например, `postCardId`) | - -#### Встроенные поля отношений в defineObject - -Вы также можете определять поля отношений непосредственно внутри `defineObject()`. В этом случае опустите `objectUniversalIdentifier` — он наследуется от родительского объекта: - -```ts -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCardRecipient', - // ... - fields: [ - { - universalIdentifier: POST_CARD_FIELD_ID, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, - }, - // ... other fields - ], -}); -``` - - - -Каждый файл функции использует `defineLogicFunction()` для экспорта конфигурации с обработчиком и необязательными триггерами. - -```ts src/logic-functions/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - httpRouteTriggerSettings: { - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: true, - }, - /*databaseEventTriggerSettings: { - eventName: 'people.created', - },*/ - /*cronTriggerSettings: { - pattern: '0 0 1 1 *', - },*/ -}); -``` - -Доступные типы триггеров: -* **httpRoute**: Публикует вашу функцию по HTTP-пути и методу **под конечной точкой `/s/`**: -> например, `path: '/post-card/create'` вызывается по адресу `https://your-twenty-server.com/s/post-card/create` -* **cron**: Запускает вашу функцию по расписанию с использованием выражения CRON. -* **databaseEvent**: Запускается при событиях жизненного цикла объектов рабочего пространства. Когда операция события — `updated`, можно указать конкретные поля для отслеживания в массиве `updatedFields`. Если оставить не заданным или пустым, любое обновление будет вызывать функцию. -> например, `person.updated`, `*.created`, `company.*` - - -Вы также можете вручную выполнить функцию с помощью CLI: - -```bash filename="Terminal" -yarn twenty exec -n create-new-post-card -p '{"key": "value"}' -``` - -```bash filename="Terminal" -yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -Вы можете просматривать логи с помощью: - -```bash filename="Terminal" -yarn twenty logs -``` - - -#### Полезная нагрузка триггера маршрута - -Когда триггер маршрута вызывает вашу логическую функцию, она получает объект `RoutePayload`, который соответствует [формату AWS HTTP API v2](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). -Импортируйте тип `RoutePayload` из `twenty-sdk`: - -```ts -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; - -const handler = async (event: RoutePayload) => { - const { headers, queryStringParameters, pathParameters, body } = event; - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -Тип `RoutePayload` имеет следующую структуру: - - | Свойство | Тип | Описание | Пример | - | ---------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------- | - | `headers` | `Record\` | HTTP-заголовки (только перечисленные в `forwardedRequestHeaders`) | см. раздел ниже | - | `queryStringParameters` | `Record\` | Параметры строки запроса (несколько значений объединяются запятыми) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | - | `pathParameters` | `Record\` | Параметры пути, извлечённые из шаблона маршрута | `/users/:id`, `/users/123` -> `{ id: '123' }` | - | `body` | `object \| null` | Разобранное тело запроса (JSON) | `{ id: 1 }` -> `{ id: 1 }` | - | `isBase64Encoded` | `логический тип` | Является ли тело закодированным в base64 | | - | `requestContext.http.method` | `строка` | Метод HTTP (GET, POST, PUT, PATCH, DELETE) | | - | `requestContext.http.path` | `строка` | Необработанный путь запроса | | - - -#### forwardedRequestHeaders - -По умолчанию HTTP-заголовки из входящих запросов **не** передаются в вашу логическую функцию по соображениям безопасности. -Чтобы получить доступ к определённым заголовкам, перечислите их в массиве `forwardedRequestHeaders`: - -```ts -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - httpRouteTriggerSettings: { - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, -}); -``` - -В обработчике обращайтесь к переданным заголовкам следующим образом: - -```ts -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - -Имена заголовков приводятся к нижнему регистру. Обращайтесь к ним, используя ключи в нижнем регистре (например, `event.headers['content-type']`). - - -#### Предоставление функции как инструмента - -Логические функции можно предоставлять как **инструменты** для ИИ-агентов и рабочих процессов. Когда функция помечена как инструмент, она становится доступной для функций ИИ Twenty и может использоваться в автоматизациях рабочих процессов. - -Чтобы пометить логическую функцию как инструмент, установите `isTool: true`: - -```ts src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, -}); -``` - -Основные моменты: - -* Вы можете комбинировать `isTool` с триггерами — функция может одновременно быть инструментом (вызываемым агентами ИИ) и запускаться событиями. -* **`toolInputSchema`** (необязательно): объект JSON Schema, описывающий параметры, которые принимает ваша функция. Схема вычисляется автоматически на основе статического анализа исходного кода, но вы можете задать её явно: - -```ts -export default defineLogicFunction({ - ..., - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - - -**Напишите хорошее описание в поле `description`.** Агенты ИИ опираются на поле `description` функции, чтобы решить, когда использовать инструмент. Чётко опишите, что делает инструмент и когда его следует вызывать. - - - - - -Послеустановочная функция — это функция логики, которая автоматически выполняется после завершения установки вашего приложения в рабочем пространстве. Сервер выполняет её **после** того, как метаданные приложения синхронизированы и клиент SDK сгенерирован, так что рабочее пространство полностью готово к использованию, а новая схема уже применена. Типичные сценарии использования включают предзаполнение данных по умолчанию, создание начальных записей, настройку параметров рабочего пространства или выделение ресурсов в сторонних сервисах. - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - shouldRunSynchronously: false, - handler, -}); -``` - -Вы также можете вручную выполнить постустановочную функцию в любое время с помощью CLI: - -```bash filename="Terminal" -yarn twenty exec --postInstall -``` - -Основные моменты: -* Послеустановочные функции используют `definePostInstallLogicFunction()` — специализированный вариант, который опускает настройки триггеров (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). -* Обработчик получает `InstallPayload` с `{ previousVersion?: string; newVersion: string }` — `newVersion` — это устанавливаемая версия, а `previousVersion` — версия, установленная ранее (или `undefined` при чистой установке). Используйте эти значения, чтобы отличать чистые установки от обновлений и запускать логику миграции, зависящую от версии. -* **Когда запускается хук**: по умолчанию только при чистой установке. Передайте `shouldRunOnVersionUpgrade: true`, если хотите, чтобы он также выполнялся при обновлении приложения с предыдущей версии. Если флаг опущен, по умолчанию он равен `false`, и при обновлении хук пропускается. -* **Модель выполнения — по умолчанию асинхронно, синхронный режим по выбору**: флаг `shouldRunSynchronously` определяет, *как* выполняется post-install. - * `shouldRunSynchronously: false` *(по умолчанию)* — хук **помещается в очередь сообщений** с `retryLimit: 3` и выполняется асинхронно в воркере. Ответ на установку возвращается сразу после постановки задания в очередь, поэтому медленный или дающий сбой обработчик не блокирует вызывающую сторону. Воркер выполнит до трёх повторных попыток. **Используйте это для длительных задач** — наполнение большими наборами данных, вызовы медленных сторонних API, подготовка внешних ресурсов — всего, что может выйти за разумное окно ответа HTTP. - * `shouldRunSynchronously: true` — хук выполняется **непосредственно в процессе установки** (тот же исполнитель, что и для pre-install). Запрос установки блокируется, пока обработчик не завершится, и если он генерирует исключение, вызывающая сторона установки получает `POST_INSTALL_ERROR`. Автоматических повторов нет. **Используйте это для быстрых задач, которые должны завершиться до отправки ответа** — например, выдача ошибки валидации пользователю или быстрая настройка, на которую клиент будет полагаться сразу после возврата вызова установки. Имейте в виду, что к моменту запуска post-install миграция метаданных уже применена, поэтому сбой в синхронном режиме **не** откатывает изменения схемы — он лишь выявляет ошибку. -* Убедитесь, что ваш обработчик идемпотентен. В асинхронном режиме очередь может выполнить до трёх повторных попыток; в любом режиме хук может запускаться снова при обновлениях, когда `shouldRunOnVersionUpgrade: true`. -* Переменные окружения `APPLICATION_ID`, `APP_ACCESS_TOKEN` и `API_URL` доступны внутри обработчика (как и в любой другой логической функции), поэтому вы можете вызывать API Twenty с токеном доступа приложения, ограниченным вашим приложением. -* Для каждого приложения допускается только одна послеустановочная функция. Сборка манифеста завершится ошибкой, если будет обнаружено более одной такой функции. -* Параметры функции `universalIdentifier`, `shouldRunOnVersionUpgrade` и `shouldRunSynchronously` автоматически добавляются в манифест приложения в поле `postInstallLogicFunction` во время сборки — вам не нужно указывать их в `defineApplication()`. -* Тайм-аут по умолчанию установлен на 300 секунд (5 минут), чтобы позволить выполнять более длительные задачи настройки, такие как инициализация данных. -* **Не выполняется в режиме разработки**: когда приложение зарегистрировано локально (через `yarn twenty dev`), сервер полностью пропускает процесс установки и синхронизирует файлы напрямую через наблюдатель CLI — поэтому post-install никогда не запускается в режиме разработки, независимо от `shouldRunSynchronously`. Используйте `yarn twenty exec --postInstall`, чтобы запустить это вручную для запущенного рабочего пространства. - - - - -Функция pre-install — это логическая функция, которая автоматически выполняется во время установки, **до применения миграции метаданных рабочего пространства**. Она использует ту же структуру полезной нагрузки, что и post-install (`InstallPayload`), но находится раньше в процессе установки, чтобы подготовить состояние, от которого зависит предстоящая миграция, — типичные сценарии включают резервное копирование данных, проверку совместимости с новой схемой или архивирование записей, которые будут реструктурированы или удалены. - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -Вы также можете вручную выполнить предустановочную функцию в любое время с помощью CLI: - -```bash filename="Terminal" -yarn twenty exec --preInstall -``` - -Основные моменты: -* Функции pre-install используют `definePreInstallLogicFunction()` — та же специализированная конфигурация, что и у post-install, только привязанная к другому этапу жизненного цикла. -* И обработчики pre-, и post-install получают один и тот же тип `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Импортируйте его один раз и используйте повторно в обоих хуках. -* **Когда запускается хук**: выполняется непосредственно перед миграцией метаданных рабочего пространства (`synchronizeFromManifest`). Перед выполнением сервер запускает чисто добавочную «урезанную синхронизацию», которая регистрирует в метаданных рабочего пространства pre-install функцию **новой** версии — ничего больше не затрагивается — а затем выполняет её. Поскольку эта синхронизация только добавляет, объекты, поля и данные предыдущей версии остаются нетронутыми к моменту запуска вашего обработчика: вы можете безопасно читать и сохранять состояние до миграции. -* **Модель выполнения**: pre-install выполняется **синхронно** и **блокирует установку**. Если обработчик генерирует исключение, установка прерывается до применения каких-либо изменений схемы — рабочее пространство остаётся на предыдущей версии в согласованном состоянии. Это сделано намеренно: pre-install — ваш последний шанс отказать в рискованном обновлении. -* Как и в случае с post-install, для каждого приложения допускается только одна предустановочная функция. Она автоматически добавляется в манифест приложения в поле `preInstallLogicFunction` во время сборки. -* **Не выполняется в режиме разработки**: как и post-install, процесс установки полностью пропускается для локально зарегистрированных приложений, поэтому pre-install никогда не запускается при `yarn twenty dev`. Используйте `yarn twenty exec --preInstall`, чтобы запустить это вручную. - - - - -Оба хука являются частью одного и того же процесса установки и получают один и тот же `InstallPayload`. Разница в том, **когда** они запускаются относительно миграции метаданных рабочего пространства, и это определяет, к каким данным можно безопасно обращаться. +## Entity types + +| Сущность | Назначение | Документация | +| ------------------------ | ----------------------------------------- | ------------------------------------------------------------- | +| **Application** | App identity, permissions, variables | [Data Model](/l/ru/developers/extend/apps/data-model) | +| **Role** | Permission sets for objects and fields | [Data Model](/l/ru/developers/extend/apps/data-model) | +| **Object** | Custom data tables with fields | [Data Model](/l/ru/developers/extend/apps/data-model) | +| **Поле** | Extend existing objects, define relations | [Data Model](/l/ru/developers/extend/apps/data-model) | +| **Logic Function** | Server-side TypeScript with triggers | [Логические функции](/l/ru/developers/extend/apps/logic-functions) | +| **Front Component** | Sandboxed React UI in Twenty's page | [Front Components](/l/ru/developers/extend/apps/front-components) | +| **Skill** | Reusable AI agent instructions | [Skills & Agents](/l/ru/developers/extend/apps/skills-and-agents) | +| **Agent** | AI assistants with custom prompts | [Skills & Agents](/l/ru/developers/extend/apps/skills-and-agents) | +| **View** | Pre-configured record list views | [Layout](/l/ru/developers/extend/apps/layout) | +| **Navigation Menu Item** | Custom sidebar entries | [Layout](/l/ru/developers/extend/apps/layout) | +| **Page Layout** | Custom record page tabs and widgets | [Layout](/l/ru/developers/extend/apps/layout) | + +## Sandboxing + +* **Logic functions** run in isolated Node.js processes on the server. They only access data through the typed API client, scoped to the app's role permissions. +* **Front components** run in Web Workers using Remote DOM — sandboxed from the main page but rendering native DOM elements (not iframes). They communicate with Twenty via a message-passing host API. +* **Permissions** are enforced at the API level. The runtime token (`TWENTY_APP_ACCESS_TOKEN`) is derived from the role defined in `defineApplication()`. + +## App lifecycle ``` -┌─────────────────────────────────────────────────────────────┐ -│ install flow │ -│ │ -│ upload package → [pre-install] → metadata migration → │ -│ generate SDK → [post-install] │ -│ │ -│ old schema visible new schema visible │ -└─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────┐ +│ Development │ +│ npx create-twenty-app → yarn twenty dev (live sync) │ +├─────────────────────────────────────────────────────────┤ +│ Build & Deploy │ +│ yarn twenty build → yarn twenty deploy │ +├─────────────────────────────────────────────────────────┤ +│ Install flow │ +│ upload → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +├─────────────────────────────────────────────────────────┤ +│ Publish │ +│ npm publish → appears in Twenty marketplace │ +└─────────────────────────────────────────────────────────┘ ``` -Pre-install всегда **синхронный** (он блокирует установку и может её прервать). Post-install **по умолчанию асинхронный** — ставится в очередь воркера с автоматическими повторами — но может перейти к синхронному выполнению с `shouldRunSynchronously: true`. См. аккордеон `definePostInstallLogicFunction` выше о том, когда использовать каждый режим. - -**Используйте `post-install` для всего, что требует наличия новой схемы.** Это распространённый случай: - -* Наполнение данными по умолчанию (создание начальных записей, стандартных представлений, демонстрационного контента) для недавно добавленных объектов и полей. -* Регистрация вебхуков в сторонних сервисах теперь, когда у приложения уже есть учётные данные. -* Вызов вашего собственного API для завершения настройки, зависящей от синхронизированных метаданных. -* Идемпотентная логика «убедиться, что это существует», которая должна приводить состояние в соответствие при каждом обновлении — совместите с `shouldRunOnVersionUpgrade: true`. - -Пример — создать запись `PostCard` по умолчанию после установки: - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion }: InstallPayload): Promise => { - if (previousVersion) return; // fresh installs only - - const client = createClient(); - await client.postCard.create({ - data: { title: 'Welcome to Postcard', content: 'Your first card!' }, - }); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Seeds a welcome post card after install.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - handler, -}); -``` - -**Используйте `pre-install`, когда миграция в противном случае уничтожит или повредит существующие данные.** Поскольку pre-install работает с *предыдущей* схемой и при сбое откатывает обновление, это правильное место для всего рискованного: - -* **Резервное копирование данных, которые будут удалены или реструктурированы** — например, вы удаляете поле в v2 и вам нужно скопировать его значения в другое поле или экспортировать их в хранилище до запуска миграции. -* **Архивирование записей, которые новое ограничение сделает недопустимыми** — например, поле становится `NOT NULL`, и вам сначала нужно удалить или исправить строки со значениями null. -* **Проверка совместимости и отказ от обновления, если текущие данные нельзя корректно мигрировать** — выбросьте исключение из обработчика, и установка прервётся без внесения изменений. Это безопаснее, чем обнаружить несовместимость в середине миграции. -* **Переименование или изменение ключей данных** перед изменением схемы, которое привело бы к потере связи. - -Пример — архивировать записи перед разрушительной миграцией: - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { - // Only the 1.x → 2.x upgrade drops the legacy `notes` field. - if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { - return; - } - - const client = createClient(); - const legacyRecords = await client.postCard.findMany({ - where: { notes: { isNotNull: true } }, - }); - - if (legacyRecords.length === 0) return; - - // Copy legacy `notes` into the new `description` field before the migration - // drops the `notes` column. If this fails, the upgrade is aborted and the - // workspace stays on v1 with all data intact. - await Promise.all( - legacyRecords.map((record) => - client.postCard.update({ - where: { id: record.id }, - data: { description: record.notes }, - }), - ), - ); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Backs up legacy notes into description before the v2 migration.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -**Общее правило:** - -| Вы хотите… | Использовать | -| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -| Наполнить данными по умолчанию, настроить рабочее пространство, зарегистрировать внешние ресурсы | `post-install` | -| Выполнить длительное наполнение или сторонние вызовы, которые не должны блокировать ответ установки | `post-install` (по умолчанию — `shouldRunSynchronously: false`, с повторами воркера) | -| Выполнить быструю настройку, на которую вызывающая сторона будет полагаться сразу после возврата вызова установки | `post-install` с `shouldRunSynchronously: true` | -| Прочитать или сохранить данные, которые предстоящая миграция может потерять | `pre-install` | -| Отклонить обновление, которое повредит существующие данные | `pre-install` (бросьте исключение из обработчика) | -| Выполнять согласование при каждом обновлении | `post-install` с `shouldRunOnVersionUpgrade: true` | -| Сделать одноразовую настройку только при первой установке | `post-install` с `shouldRunOnVersionUpgrade: false` (по умолчанию) | - - -Если сомневаетесь, выбирайте по умолчанию **post-install**. Обращайтесь к pre-install только тогда, когда сама миграция разрушительна и вам нужно перехватить предыдущее состояние, прежде чем оно исчезнет. - - - - - -Фронтенд-компоненты — это компоненты React, которые отображаются непосредственно внутри интерфейса Twenty. Они выполняются в изолированном Web Worker с использованием Remote DOM — ваш код изолирован (sandboxed), но рендерится нативно на странице, а не в iframe. - -#### Где можно использовать фронт-компоненты - -Фронт-компоненты могут отображаться в двух местах внутри Twenty: - -* **Боковая панель** — фронт-компоненты с интерфейсом открываются в правой боковой панели. Это поведение по умолчанию, когда фронт-компонент запускается из меню команд. -* **Виджеты (дашборды и страницы записей)** — фронт-компоненты можно встраивать как виджеты в макеты страниц. При настройке дашборда или макета страницы записи пользователи могут добавить виджет фронт-компонента. - -#### Простой пример - -Самый быстрый способ увидеть фронтенд-компонент в действии — зарегистрировать его как **команду**. Добавление поля `command` с `isPinned: true` делает его кнопкой быстрого действия в правом верхнем углу страницы — макет страницы не требуется: - -```tsx src/front-components/hello-world.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; - -const HelloWorld = () => { - return ( -
-

Hello from my app!

-

This component renders inside Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', - name: 'hello-world', - description: 'A simple front component', - component: HelloWorld, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', - shortLabel: 'Hello', - label: 'Hello World', - icon: 'IconBolt', - isPinned: true, - availabilityType: 'GLOBAL', - }, -}); -``` - -После синхронизации с помощью `yarn twenty dev` (или однократного запуска `yarn twenty dev --once`) быстрое действие появится в правом верхнем углу страницы: - -
- Кнопка быстрого действия в правом верхнем углу -
- -Нажмите её, чтобы отобразить компонент инлайн. - -{/* TODO: add screenshot of the rendered front component */} - -#### Поля конфигурации - -| Поле | Обязательно | Описание | -| --------------------- | ----------- | -------------------------------------------------------------------------------------------------- | -| `universalIdentifier` | Да | Стабильный уникальный идентификатор для этого компонента | -| `component` | Да | Функция компонента React | -| `name` | Нет | Отображаемое имя | -| `description` | Нет | Описание того, что делает компонент | -| `isHeadless` | Нет | Установите значение `true`, если у компонента нет видимого пользовательского интерфейса (см. ниже) | -| `command` | Нет | Зарегистрируйте компонент как команду (см. [параметры команды](#command-options) ниже) | - -#### Размещение фронт-компонента на странице - -Помимо команд, вы можете встроить фронт-компонент непосредственно на страницу записи, добавив его как виджет в **макет страницы**. См. раздел [definePageLayout](#definepagelayout) для подробностей. - -#### Headless и non-headless - -Фронт-компоненты поддерживают два режима отображения, управляемых опцией `isHeadless`: - -**Non-headless (по умолчанию)** — компонент отображает видимый интерфейс. При запуске из меню команд он открывается в боковой панели. Это поведение по умолчанию, когда `isHeadless` имеет значение `false` или опущен. - -**Headless (`isHeadless: true`)** — компонент монтируется невидимо в фоновом режиме. Он не открывает боковую панель. Компоненты headless предназначены для действий, которые выполняют логику и затем размонтируются — например, запуск асинхронной задачи, переход на страницу или показ модального окна подтверждения. Они естественно сочетаются с компонентами SDK Command, описанными ниже. - -```tsx src/front-components/sync-tracker.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; -import { useEffect } from 'react'; - -const SyncTracker = () => { - const recordId = useRecordId(); - - useEffect(() => { - enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); - }, [recordId]); - - return null; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'sync-tracker', - description: 'Tracks record views silently', - isHeadless: true, - component: SyncTracker, -}); -``` - -Поскольку компонент возвращает `null`, Twenty пропускает рендеринг контейнера для него — в макете не появляется пустое место. Компонент по-прежнему имеет доступ ко всем хукам и API взаимодействия с хостом. - -#### Компоненты SDK Command - -Пакет `twenty-sdk` предоставляет четыре вспомогательных компонента Command, предназначенных для headless фронт-компонентов. Каждый компонент выполняет действие при монтировании, обрабатывает ошибки, показывая уведомление snackbar, и автоматически размонтирует фронт-компонент по завершении. - -Импортируйте их из `twenty-sdk/command`: - -* **`Command`** — запускает асинхронный колбэк через проп `execute`. -* **`CommandLink`** — переходит по пути внутри приложения. Пропы: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — открывает модальное окно подтверждения. Если пользователь подтвердит, выполняет колбэк `execute`. Пропы: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — открывает конкретную страницу боковой панели. Пропы: `page`, `pageTitle`, `pageIcon`. - -Полный пример headless фронт-компонента, использующего `Command` для запуска действия из меню команд: - -```tsx src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -А также пример с использованием `CommandModal` для запроса подтверждения перед выполнением: - -```tsx src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Доступ к контексту времени выполнения - -Внутри вашего компонента используйте хуки SDK для доступа к текущему пользователю, записи и экземпляру компонента: - -```tsx src/front-components/record-info.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - useUserId, - useRecordId, - useFrontComponentId, -} from 'twenty-sdk/front-component'; - -const RecordInfo = () => { - const userId = useUserId(); - const recordId = useRecordId(); - const componentId = useFrontComponentId(); - - return ( -
-

User: {userId}

-

Record: {recordId ?? 'No record context'}

-

Component: {componentId}

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', - name: 'record-info', - component: RecordInfo, -}); -``` - -Доступные хуки: - -| Хук | Возвращает | Описание | -| --------------------------------------------- | ------------------- | ----------------------------------------------------------------- | -| `useUserId()` | `string` или `null` | ID текущего пользователя | -| `useRecordId()` | `string` или `null` | ID текущей записи (при размещении на странице записи) | -| `useFrontComponentId()` | `строка` | ID этого экземпляра компонента | -| `useFrontComponentExecutionContext(selector)` | различается | Доступ к полному контексту выполнения с помощью функции-селектора | - -#### API взаимодействия с хостом - -Компоненты фронтенда могут вызывать навигацию, модальные окна и уведомления с помощью функций из `twenty-sdk`: - -| Функция | Описание | -| ----------------------------------------------- | -------------------------------- | -| `navigate(to, params?, queryParams?, options?)` | Перейти на страницу в приложении | -| `openSidePanelPage(params)` | Открыть боковую панель | -| `closeSidePanel()` | Закрыть боковую панель | -| `openCommandConfirmationModal(params)` | Показать диалог подтверждения | -| `enqueueSnackbar(params)` | Показать всплывающее уведомление | -| `unmountFrontComponent()` | Размонтировать компонент | -| `updateProgress(progress)` | Обновить индикатор прогресса | - -Пример, который использует API хоста для показа snackbar и закрытия боковой панели после завершения действия: - -```tsx src/front-components/archive-record.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId } from 'twenty-sdk/front-component'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -#### Параметры команды - -Добавление поля `command` в `defineFrontComponent` регистрирует компонент в меню команд (Cmd+K). Если `isPinned` имеет значение `true`, команда также отображается как кнопка быстрого действия в правом верхнем углу страницы. - -| Поле | Обязательно | Описание | -| --------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `universalIdentifier` | Да | Стабильный уникальный идентификатор для команды | -| `label` | Да | Полная метка, отображаемая в меню команд (Cmd+K) | -| `shortLabel` | Нет | Короткая метка, отображаемая на закреплённой кнопке быстрого действия | -| `icon` | Нет | Имя значка, отображаемое рядом с меткой (например, `'IconBolt'`, `'IconSend'`) | -| `isPinned` | Нет | При значении `true` показывает команду как кнопку быстрого действия в правом верхнем углу страницы | -| `availabilityType` | Нет | Определяет, где отображается команда: `'GLOBAL'` (доступна всегда), `'RECORD_SELECTION'` (только при выборе записей) или `'FALLBACK'` (показывается, когда другие команды не подходят) | -| `availabilityObjectUniversalIdentifier` | Нет | Ограничивает команду страницами определённого типа объектов (например, только для записей Company) | -| `conditionalAvailabilityExpression` | Нет | Логическое выражение для динамического управления видимостью команды (см. ниже) | - -#### Выражения условной доступности - -Поле `conditionalAvailabilityExpression` позволяет управлять видимостью команды в зависимости от текущего контекста страницы. Импортируйте типизированные переменные и операторы из `twenty-sdk`, чтобы составлять выражения: - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - pageType, - numberOfSelectedRecords, - objectPermissions, - everyEquals, - isDefined, -} from 'twenty-sdk/front-component'; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'bulk-action', - component: BulkAction, - command: { - universalIdentifier: '...', - label: 'Bulk Update', - availabilityType: 'RECORD_SELECTION', - conditionalAvailabilityExpression: everyEquals( - objectPermissions, - 'canUpdateObjectRecords', - true, - ), - }, -}); -``` - -**Переменные контекста** — представляют текущее состояние страницы: - -| Переменная | Тип | Описание | -| ------------------------------ | --------- | ------------------------------------------------------------------------ | -| `pageType` | `строка` | Текущий тип страницы (например, `'RecordIndexPage'`, `'RecordShowPage'`) | -| `isInSidePanel` | `boolean` | Указывает, рендерится ли компонент в боковой панели | -| `numberOfSelectedRecords` | `number` | Количество выбранных в данный момент записей | -| `isSelectAll` | `boolean` | Активен ли режим "выбрать все" | -| `selectedRecords` | `массив` | Объекты выбранных записей | -| `favoriteRecordIds` | `массив` | ID избранных записей | -| `objectPermissions` | `object` | Разрешения для текущего типа объекта | -| `targetObjectReadPermissions` | `object` | Права на чтение для целевого объекта | -| `targetObjectWritePermissions` | `object` | Права на запись для целевого объекта | -| `featureFlags` | `object` | Активные флаги функций | -| `objectMetadataItem` | `object` | Метаданные текущего типа объекта | -| `hasAnySoftDeleteFilterOnView` | `boolean` | Есть ли у текущего представления фильтр мягкого удаления | - -**Операторы** — комбинируют переменные в логические выражения: - -| Оператор | Описание | -| ----------------------------------- | ------------------------------------------------------------------------- | -| `isDefined(value)` | `true`, если значение не null/undefined | -| `isNonEmptyString(value)` | `true`, если значение — непустая строка | -| `includes(array, value)` | `true`, если массив содержит значение | -| `includesEvery(array, prop, value)` | `true`, если свойство каждого элемента включает значение | -| `every(array, prop)` | `true`, если свойство истинно для каждого элемента | -| `everyDefined(array, prop)` | `true`, если свойство определено у каждого элемента | -| `everyEquals(array, prop, value)` | `true`, если свойство равно значению у каждого элемента | -| `some(array, prop)` | `true`, если свойство истинно хотя бы у одного элемента | -| `someDefined(array, prop)` | `true`, если свойство определено хотя бы у одного элемента | -| `someEquals(array, prop, value)` | `true`, если свойство равно значению хотя бы у одного элемента | -| `someNonEmptyString(array, prop)` | `true`, если свойство является непустой строкой хотя бы у одного элемента | -| `none(array, prop)` | `true`, если свойство ложно для каждого элемента | -| `noneDefined(array, prop)` | `true`, если свойство не определено ни у одного элемента | -| `noneEquals(array, prop, value)` | `true`, если свойство не равно значению ни у одного элемента | - -#### Публичные ресурсы - -Компоненты фронтенда могут получать доступ к файлам из каталога приложения `public/` с помощью `getPublicAssetUrl`: - -```tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -const Logo = () => Logo; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'logo', - component: Logo, -}); -``` - -См. [раздел о публичных ресурсах](#accessing-public-assets-with-getpublicasseturl) для подробностей. - -#### Стилизация - -Компоненты фронтенда поддерживают несколько подходов к стилизации. Вы можете использовать: - -* **Встроенные стили** — `style={{ color: 'red' }}` -* **Компоненты Twenty UI** — импорт из `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar и другие) -* **Emotion** — CSS-in-JS с `@emotion/react` -* **Styled-components** — паттерны `styled.div` -* **Tailwind CSS** — утилитарные классы -* **Любая библиотека CSS-in-JS**, совместимая с React - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Button, Tag, Status } from 'twenty-sdk/ui'; - -const StyledWidget = () => { - return ( -
-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', - name: 'styled-widget', - component: StyledWidget, -}); -``` - -
- - - -Навыки определяют многократно используемые инструкции и возможности, которые агенты ИИ могут использовать в вашем рабочем пространстве. Используйте `defineSkill()` для определения навыков со встроенной валидацией: - -```ts src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk/define'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -Основные моменты: -* `name` — уникальная строка-идентификатор навыка (рекомендуется kebab-case). -* `label` — читаемое человеком отображаемое имя, показываемое в UI. -* `content` содержит инструкции навыка — это текст, который использует агент ИИ. -* `icon` (необязательно) задаёт значок, отображаемый в UI. -* `description` (необязательно) предоставляет дополнительный контекст о назначении навыка. - - - - -Агенты — это ИИ-помощники, работающие в вашем рабочем пространстве. Используйте `defineAgent()` для создания агентов с пользовательским системным промптом: - -```ts src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk/define'; - -export default defineAgent({ - universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'Helps the sales team draft outreach emails and research prospects', - icon: 'IconRobot', - prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', -}); -``` - -Основные моменты: -* `name` — уникальная строка-идентификатор агента (рекомендуется kebab-case). -* `label` — отображаемое имя, показываемое в UI. -* `prompt` — это системный промпт, определяющий поведение агента. -* `description` (необязательно) предоставляет контекст о том, что делает агент. -* `icon` (необязательно) задаёт значок, отображаемый в UI. -* `modelId` (необязательно) переопределяет модель ИИ по умолчанию, используемую агентом. - - - - -Представления — это сохранённые конфигурации отображения записей объекта: какие поля видны, их порядок, а также применённые фильтры и группы. Используйте `defineView()` для поставки преднастроенных представлений вместе с вашим приложением: - -```ts src/views/example-view.ts -import { defineView, ViewKey } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; - -export default defineView({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'All example items', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - icon: 'IconList', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', - fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 200, - }, - ], -}); -``` - -Основные моменты: -* `objectUniversalIdentifier` указывает, к какому объекту применяется это представление. -* `key` определяет тип представления (например, `ViewKey.INDEX` для основного списка). -* `fields` управляет тем, какие столбцы отображаются и в каком порядке. Каждое поле ссылается на `fieldMetadataUniversalIdentifier`. -* Также вы можете определить `filters`, `filterGroups`, `groups` и `fieldGroups` для более продвинутых конфигураций. -* `position` управляет порядком, когда для одного и того же объекта существует несколько представлений. - - - - -Пункты навигационного меню добавляют пользовательские элементы в боковую панель рабочего пространства. Используйте `defineNavigationMenuItem()` для ссылок на представления, внешние URL или объекты: - -```ts src/navigation-menu-items/example-navigation-menu-item.ts -import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; -import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; - -export default defineNavigationMenuItem({ - universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', - name: 'example-navigation-menu-item', - icon: 'IconList', - color: 'blue', - position: 0, - type: NavigationMenuItemType.VIEW, - viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, -}); -``` - -Основные моменты: -* `type` определяет, на что ссылается пункт меню: `NavigationMenuItemType.VIEW` для сохранённого представления или `NavigationMenuItemType.LINK` для внешнего URL. -* Для ссылок на представления укажите `viewUniversalIdentifier`. Для внешних ссылок укажите `link`. -* `position` управляет порядком в боковой панели. -* `icon` и `color` (необязательно) настраивают внешний вид. - - - - -Макеты страниц позволяют настраивать вид страницы с деталями записи: какие вкладки отображаются, какие виджеты внутри каждой вкладки и как они расположены. Используйте `definePageLayout()` для поставки пользовательских макетов вместе с вашим приложением: - -```ts src/page-layouts/example-record-page-layout.ts -import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; - -export default definePageLayout({ - universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', - name: 'Example Record Page', - type: 'RECORD_PAGE', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - tabs: [ - { - universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', - title: 'Hello World', - position: 50, - icon: 'IconWorld', - layoutMode: PageLayoutTabLayoutMode.CANVAS, - widgets: [ - { - universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', - title: 'Hello World', - type: 'FRONT_COMPONENT', - configuration: { - configurationType: 'FRONT_COMPONENT', - frontComponentUniversalIdentifier: - HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, - }, - }, - ], - }, - ], -}); -``` - -Основные моменты: -* `type` обычно равен `'RECORD_PAGE'` для настройки детального представления конкретного объекта. -* `objectUniversalIdentifier` указывает, к какому объекту применяется этот макет. -* Каждая `tab` определяет раздел страницы с `title`, `position` и `layoutMode` (`CANVAS` для свободного макета). -* Каждый `widget` внутри вкладки может отображать компонент фронтенда, список связей или другие встроенные типы виджетов. -* `position` у вкладок управляет их порядком. Используйте большие значения (например, 50), чтобы разместить пользовательские вкладки после встроенных. - - -
- -## Публичные ресурсы (папка `public/`) - -Папка `public/` в корне вашего приложения содержит статические файлы — изображения, значки, шрифты и любые другие ресурсы, необходимые вашему приложению во время выполнения. Эти файлы автоматически включаются в сборки, синхронизируются в режиме разработки и загружаются на сервер. - -Файлы, размещённые в `public/`, являются: - -* **Публично доступными** — после синхронизации с сервером ресурсы доступны по публичному URL. Для доступа к ним аутентификация не требуется. -* **Доступными в компонентах фронтенда** — используйте URL ресурсов для отображения изображений, значков или любого медиа внутри ваших компонентов React. -* **Доступными в логических функциях** — используйте URL ресурсов в письмах, ответах API или любой серверной логике. -* **Используются для метаданных маркетплейса** — поля `logoUrl` и `screenshots` в `defineApplication()` ссылаются на файлы из этой папки (например, `public/logo.png`). Они отображаются в маркетплейсе при публикации вашего приложения. -* **Автосинхронизация в режиме разработки** — когда вы добавляете, обновляете или удаляете файл в `public/`, он автоматически синхронизируется с сервером. Перезапуск не требуется. -* **Включены в сборки** — `yarn twenty build` упаковывает все публичные ресурсы в выходной дистрибутив. - -### Доступ к публичным ресурсам с помощью `getPublicAssetUrl` - -Используйте хелпер `getPublicAssetUrl` из `twenty-sdk`, чтобы получить полный URL файла в каталоге `public/` вашего приложения. Он работает как в **логических функциях**, так и в **компонентах фронтенда**. - -**В логической функции:** - -```ts src/logic-functions/send-invoice.ts -import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; - -const handler = async (): Promise => { - const logoUrl = getPublicAssetUrl('logo.png'); - const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); - - // Fetch the file content (no auth required — public endpoint) - const response = await fetch(invoiceUrl); - const buffer = await response.arrayBuffer(); - - return { logoUrl, size: buffer.byteLength }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'a1b2c3d4-...', - name: 'send-invoice', - description: 'Sends an invoice with the app logo', - timeoutSeconds: 10, - handler, -}); -``` - -**В компоненте фронтенда:** - -```tsx src/front-components/company-card.tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -export default defineFrontComponent(() => { - const logoUrl = getPublicAssetUrl('logo.png'); - - return App logo; -}); -``` - -Аргумент `path` задаётся относительно папки `public/` вашего приложения. И `getPublicAssetUrl('logo.png')`, и `getPublicAssetUrl('public/logo.png')` приводят к одному и тому же URL — префикс `public/`, если он есть, удаляется автоматически. - -## Использование пакетов npm - -Вы можете устанавливать и использовать любые пакеты npm в своём приложении. И логические функции, и компоненты фронтенда собираются с помощью [esbuild](https://esbuild.github.io/), который встраивает все зависимости в выходной файл — каталоги `node_modules` во время выполнения не нужны. - -### Установка пакета - -```bash filename="Terminal" -yarn add axios -``` - -Затем импортируйте его в своём коде: - -```ts src/logic-functions/fetch-data.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import axios from 'axios'; - -const handler = async (): Promise => { - const { data } = await axios.get('https://api.example.com/data'); - - return { data }; -}; - -export default defineLogicFunction({ - universalIdentifier: '...', - name: 'fetch-data', - description: 'Fetches data from an external API', - timeoutSeconds: 10, - handler, -}); -``` - -То же самое работает для компонентов фронтенда: - -```tsx src/front-components/chart.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { format } from 'date-fns'; - -const DateWidget = () => { - return

Today is {format(new Date(), 'MMMM do, yyyy')}

; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Как работает бандлинг - -Этап сборки использует esbuild для создания одного самодостаточного файла на каждую логическую функцию и на каждый компонент фронтенда. Все импортированные пакеты встроены в бандл. - -**Логические функции** выполняются в среде Node.js. Встроенные модули Node (`fs`, `path`, `crypto`, `http` и т. д.) доступны и не требуют установки. - -**Компоненты фронтенда** выполняются в Web Worker. Встроенные модули Node недоступны — доступны только браузерные API и пакеты npm, работающие в браузерной среде. - -В обеих средах доступны как предварительно предоставленные модули `twenty-client-sdk/core` и `twenty-client-sdk/metadata` — они не включаются в бандл, а подставляются сервером во время выполнения. - -## Создание заготовок сущностей с помощью `yarn twenty add` - -Вместо ручного создания файлов сущностей вы можете использовать интерактивный генератор: - -```bash filename="Terminal" -yarn twenty add -``` - -Он предложит выбрать тип сущности и проведёт вас по обязательным полям. Он генерирует готовый к использованию файл со стабильным `universalIdentifier` и корректным вызовом `defineEntity()`. - -Вы также можете передать тип сущности напрямую, чтобы пропустить первый запрос: - -```bash filename="Terminal" -yarn twenty add object -yarn twenty add logicFunction -yarn twenty add frontComponent -``` - -### Доступные типы сущностей - -| Тип сущности | Команда | Сгенерированный файл | -| -------------------- | ------------------------------------ | ------------------------------------------------------- | -| Объект | `yarn twenty add object` | `src/objects/\.ts` | -| Поле | `yarn twenty add field` | `src/fields/\.ts` | -| Логическая функция | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | -| Компонент фронтенда | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | -| Роль | `yarn twenty add role` | `src/roles/\.ts` | -| Навык | `yarn twenty add skill` | `src/skills/\.ts` | -| Агент | `yarn twenty add agent` | `src/agents/\.ts` | -| Представление | `yarn twenty add view` | `src/views/\.ts` | -| Пункт меню навигации | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | -| Макет страницы | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | - -### Что генерирует скэффолдер - -У каждого типа сущности есть свой шаблон. Например, `yarn twenty add object` запрашивает: - -1. **Имя (единственное число)** — например, `invoice` -2. **Имя (множественное число)** — например, `invoices` -3. **Метка (единственное число)** — заполняется автоматически из имени (например, `Invoice`) -4. **Метка (множественное число)** — заполняется автоматически (например, `Invoices`) -5. **Создать представление и пункт навигации?** — если вы ответите «да», скэффолдер также сгенерирует соответствующее представление и ссылку в боковой панели для нового объекта. - -У других типов сущностей подсказки проще — в большинстве случаев запрашивается только имя. - -Тип сущности `field` более детализирован: он запрашивает имя поля, метку, тип (из списка всех доступных типов полей, таких как `TEXT`, `NUMBER`, `SELECT`, `RELATION` и т. д.), а также `universalIdentifier` целевого объекта. - -### Пользовательский путь вывода - -Используйте флаг `--path`, чтобы поместить сгенерированный файл в пользовательское расположение: - -```bash filename="Terminal" -yarn twenty add logicFunction --path src/custom-folder -``` - -## Типизированные клиенты API (twenty-client-sdk) - -Пакет `twenty-client-sdk` предоставляет два типизированных клиента GraphQL для взаимодействия с API Twenty из ваших логических функций и фронт-компонентов. - -| Клиент | Импорт | Конечная точка | Генерируется? | -| ------------------- | ---------------------------- | ----------------------------------------------------------------- | -------------------------------- | -| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — данные рабочего пространства (записи, объекты) | Да, на этапе dev/build | -| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — конфигурация рабочего пространства, загрузка файлов | Нет, поставляется в готовом виде | - - - - -`CoreApiClient` — основной клиент для запросов и изменений данных рабочего пространства. Он **генерируется из схемы вашего рабочего пространства** во время `yarn twenty dev` или `yarn twenty build`, поэтому полностью типизирован в соответствии с вашими объектами и полями. - -```ts -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const client = new CoreApiClient(); - -// Query records -const { companies } = await client.query({ - companies: { - edges: { - node: { - id: true, - name: true, - domainName: { - primaryLinkLabel: true, - primaryLinkUrl: true, - }, - }, - }, - }, -}); - -// Create a record -const { createCompany } = await client.mutation({ - createCompany: { - __args: { - data: { - name: 'Acme Corp', - }, - }, - id: true, - name: true, - }, -}); -``` - -Клиент использует синтаксис selection-set: передайте `true`, чтобы включить поле, используйте `__args` для аргументов и вкладывайте объекты для отношений. Вы получаете полное автодополнение и проверку типов на основе схемы вашего рабочего пространства. - - -**CoreApiClient генерируется на этапе dev/build.** Если вы используете его, не запустив сначала `yarn twenty dev` или `yarn twenty build`, он выбросит ошибку. Генерация происходит автоматически — CLI анализирует GraphQL-схему вашего рабочего пространства и создает типизированный клиент с помощью `@genql/cli`. - - -#### Использование CoreSchema для аннотаций типов - -`CoreSchema` предоставляет типы TypeScript, соответствующие объектам вашего рабочего пространства — это полезно для типизации состояния компонентов или параметров функций: - -```ts -import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; -import { useState } from 'react'; - -const [company, setCompany] = useState< - Pick | undefined ->(undefined); - -const client = new CoreApiClient(); -const result = await client.query({ - company: { - __args: { filter: { position: { eq: 1 } } }, - id: true, - name: true, - }, -}); -setCompany(result.company); -``` - - - - -`MetadataApiClient` поставляется в готовом виде вместе с SDK (генерация не требуется). Он выполняет запросы к эндпоинту `/metadata` для получения конфигурации рабочего пространства, приложений и загрузки файлов. - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const metadataClient = new MetadataApiClient(); - -// List first 10 objects in the workspace -const { objects } = await metadataClient.query({ - objects: { - edges: { - node: { - id: true, - nameSingular: true, - namePlural: true, - labelSingular: true, - isCustom: true, - }, - }, - __args: { - filter: {}, - paging: { first: 10 }, - }, - }, -}); -``` - -#### Загрузка файлов - -`MetadataApiClient` включает метод `uploadFile` для прикрепления файлов к полям типа файла: - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -| Параметр | Тип | Описание | -| ---------------------------------- | -------- | ------------------------------------------------------------------ | -| `fileBuffer` | `Buffer` | Необработанное содержимое файла | -| `filename` | `строка` | Имя файла (используется для хранения и отображения) | -| `contentType` | `string` | Тип MIME (по умолчанию `application/octet-stream`, если не указан) | -| `fieldMetadataUniversalIdentifier` | `string` | Значение `universalIdentifier` для поля типа файла в вашем объекте | - -Основные моменты: -* Он использует `universalIdentifier` поля (а не его идентификатор, специфичный для рабочего пространства), поэтому ваш код загрузки будет работать в любом рабочем пространстве, где установлено ваше приложение. -* Возвращаемый `url` — это подписанный URL, который можно использовать для доступа к загруженному файлу. - - - - - - Когда ваш код выполняется на Twenty (логические функции или фронт-компоненты), платформа предоставляет учётные данные в виде переменных окружения: - - * `TWENTY_API_URL` — базовый URL API Twenty - * `TWENTY_APP_ACCESS_TOKEN` — краткоживущий ключ, ограниченный ролью функции по умолчанию вашего приложения - - Вам не нужно передавать их клиентам — они автоматически читаются из `process.env`. Права ключа API определяются ролью, указанной в `defaultRoleUniversalIdentifier` в вашем `application-config.ts`. - - -## Тестирование вашего приложения - -SDK предоставляет программные API, которые позволяют собирать, разворачивать, устанавливать и удалять ваше приложение из тестового кода. В сочетании с [Vitest](https://vitest.dev/) и типизированными клиентами API вы можете писать интеграционные тесты, которые проверяют, что ваше приложение работает сквозным образом на реальном сервере Twenty. - -### Настройка - -Приложение, созданное скэффолдером, уже включает Vitest. Если вы настраиваете его вручную, установите зависимости: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Создайте `vitest.config.ts` в корне вашего приложения: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Создайте файл инициализации, который проверяет доступность сервера перед запуском тестов: - -```ts src/__tests__/setup-test.ts -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { beforeAll } from 'vitest'; - -const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; -const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### Программные API SDK - -Подпуть `twenty-sdk/cli` экспортирует функции, которые можно вызывать напрямую из тестового кода: - -| Функция | Описание | -| -------------- | ---------------------------------------------------------- | -| `appBuild` | Собрать приложение и при необходимости упаковать tar-архив | -| `appDeploy` | Загрузить tar-архив на сервер | -| `appInstall` | Установить приложение в активное рабочее пространство | -| `appUninstall` | Удалить приложение из активного рабочего пространства | - -Каждая функция возвращает объект результата с `success: boolean` и либо `data`, либо `error`. - -### Написание интеграционного теста - -Полный пример, который собирает, разворачивает и устанавливает приложение, а затем проверяет, что оно появляется в рабочем пространстве: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Запуск тестов - -Убедитесь, что ваш локальный сервер Twenty запущен, затем: - -```bash filename="Terminal" -yarn test -``` - -Или в режиме наблюдения во время разработки: - -```bash filename="Terminal" -yarn test:watch -``` - -### Проверка типов - -Вы также можете запустить проверку типов для своего приложения без запуска тестов: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Это запускает `tsc --noEmit` и сообщает о любых ошибках типов. - -## Справочник по CLI - -Помимо `dev`, `build`, `add` и `typecheck`, CLI предоставляет команды для выполнения функций, просмотра логов и управления установками приложений. - -### Выполнение функций (`yarn twenty exec`) - -Запустите функцию логики вручную, не вызывая ее через HTTP, cron или событие базы данных: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Просмотр логов функций (`yarn twenty logs`) - -Потоковая передача журналов выполнения функций логики вашего приложения: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - - -Это отличается от `yarn twenty server logs`, который показывает логи контейнера Docker. `yarn twenty logs` показывает журналы выполнения функций вашего приложения с сервера Twenty. - - -### Удаление приложения (`yarn twenty uninstall`) - -Удалите свое приложение из активного рабочего пространства: - -```bash filename="Terminal" -yarn twenty uninstall - -# Skip the confirmation prompt -yarn twenty uninstall --yes -``` - -## Управление удалёнными серверами - -**Remote** — это сервер Twenty, к которому подключается ваше приложение. Во время настройки скэффолдер автоматически создаст его для вас. Вы можете в любой момент добавлять новые удалённые серверы или переключаться между ними. - -```bash filename="Terminal" -# Add a new remote (opens a browser for OAuth login) -yarn twenty remote add - -# Connect to a local Twenty server (auto-detects port 2020 or 3000) -yarn twenty remote add --local - -# Add a remote non-interactively (useful for CI) -yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote - -# List all configured remotes -yarn twenty remote list - -# Switch the active remote -yarn twenty remote switch -``` - -Ваши учётные данные хранятся в `~/.twenty/config.json`. - -## CI с GitHub Actions - -Скэффолдер генерирует готовый к использованию workflow GitHub Actions в `.github/workflows/ci.yml`. Он автоматически запускает ваши интеграционные тесты при каждом пуше в `main` и в pull request'ах. - -Рабочий процесс: - -1. Извлекает ваш код -2. Поднимает временный сервер Twenty с помощью экшена `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` -3. Устанавливает зависимости с помощью `yarn install --immutable` -4. Запускает `yarn test` с `TWENTY_API_URL` и `TWENTY_API_KEY`, переданными из выходных данных экшена - -```yaml .github/workflows/ci.yml -name: CI - -on: - push: - branches: - - main - pull_request: {} - -env: - TWENTY_VERSION: latest - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Spawn Twenty instance - id: twenty - uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main - with: - twenty-version: ${{ env.TWENTY_VERSION }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Enable Corepack - run: corepack enable - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - - name: Install dependencies - run: yarn install --immutable - - - name: Run integration tests - run: yarn test - env: - TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} - TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} -``` - -Вам не нужно настраивать секреты — экшен `spawn-twenty-docker-image` запускает эфемерный сервер Twenty прямо в раннере и выводит данные для подключения. Секрет `GITHUB_TOKEN` предоставляется GitHub автоматически. - -Чтобы закрепить конкретную версию Twenty вместо `latest`, измените переменную окружения `TWENTY_VERSION` в начале workflow. +* **`yarn twenty dev`** — watches your source files and live-syncs changes to a connected Twenty server. The typed API client is regenerated automatically when the schema changes. +* **`yarn twenty build`** — compiles TypeScript, bundles logic functions and front components with esbuild, and produces a manifest. +* **Pre/post-install hooks** — optional logic functions that run during installation. See [Logic Functions](/l/ru/developers/extend/apps/logic-functions) for details. + +## Следующие шаги + + + + Define objects, fields, roles, and relations. + + + Server-side functions with HTTP, cron, and event triggers. + + + Sandboxed React components inside Twenty's UI. + + + Views, navigation items, and record page layouts. + + + AI skills and agents with custom prompts. + + + CLI commands, testing, assets, remotes, and CI. + + + Deploy to a server or publish to the marketplace. + + diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/cli-and-testing.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/cli-and-testing.mdx new file mode 100644 index 00000000000..ad29a74eb83 --- /dev/null +++ b/packages/twenty-docs/l/ru/developers/extend/apps/cli-and-testing.mdx @@ -0,0 +1,434 @@ +--- +title: CLI & Testing +description: CLI commands, testing setup, public assets, npm packages, remotes, and CI configuration. +icon: terminal +--- + +## Публичные ресурсы (папка `public/`) + +Папка `public/` в корне вашего приложения содержит статические файлы — изображения, значки, шрифты и любые другие ресурсы, необходимые вашему приложению во время выполнения. Эти файлы автоматически включаются в сборки, синхронизируются в режиме разработки и загружаются на сервер. + +Файлы, размещённые в `public/`, являются: + +* **Публично доступными** — после синхронизации с сервером ресурсы доступны по публичному URL. Для доступа к ним аутентификация не требуется. +* **Доступными в компонентах фронтенда** — используйте URL ресурсов для отображения изображений, значков или любого медиа внутри ваших компонентов React. +* **Доступными в логических функциях** — используйте URL ресурсов в письмах, ответах API или любой серверной логике. +* **Используются для метаданных маркетплейса** — поля `logoUrl` и `screenshots` в `defineApplication()` ссылаются на файлы из этой папки (например, `public/logo.png`). Они отображаются в маркетплейсе при публикации вашего приложения. +* **Автосинхронизация в режиме разработки** — когда вы добавляете, обновляете или удаляете файл в `public/`, он автоматически синхронизируется с сервером. Перезапуск не требуется. +* **Включены в сборки** — `yarn twenty build` упаковывает все публичные ресурсы в выходной дистрибутив. + +### Доступ к публичным ресурсам с помощью `getPublicAssetUrl` + +Используйте хелпер `getPublicAssetUrl` из `twenty-sdk`, чтобы получить полный URL файла в каталоге `public/` вашего приложения. Он работает как в **логических функциях**, так и в **компонентах фронтенда**. + +**В логической функции:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; + +const handler = async (): Promise => { + const logoUrl = getPublicAssetUrl('logo.png'); + const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); + + // Fetch the file content (no auth required — public endpoint) + const response = await fetch(invoiceUrl); + const buffer = await response.arrayBuffer(); + + return { logoUrl, size: buffer.byteLength }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'a1b2c3d4-...', + name: 'send-invoice', + description: 'Sends an invoice with the app logo', + timeoutSeconds: 10, + handler, +}); +``` + +**В компоненте фронтенда:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +Аргумент `path` задаётся относительно папки `public/` вашего приложения. И `getPublicAssetUrl('logo.png')`, и `getPublicAssetUrl('public/logo.png')` приводят к одному и тому же URL — префикс `public/`, если он есть, удаляется автоматически. + +## Использование пакетов npm + +Вы можете устанавливать и использовать любые пакеты npm в своём приложении. И логические функции, и компоненты фронтенда собираются с помощью [esbuild](https://esbuild.github.io/), который встраивает все зависимости в выходной файл — каталоги `node_modules` во время выполнения не нужны. + +### Установка пакета + +```bash filename="Terminal" +yarn add axios +``` + +Затем импортируйте его в своём коде: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +То же самое работает для компонентов фронтенда: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### Как работает бандлинг + +Этап сборки использует esbuild для создания одного самодостаточного файла на каждую логическую функцию и на каждый компонент фронтенда. Все импортированные пакеты встроены в бандл. + +**Логические функции** выполняются в среде Node.js. Встроенные модули Node (`fs`, `path`, `crypto`, `http` и т. д.) доступны и не требуют установки. + +**Компоненты фронтенда** выполняются в Web Worker. Встроенные модули Node недоступны — доступны только браузерные API и пакеты npm, работающие в браузерной среде. + +В обеих средах доступны как предварительно предоставленные модули `twenty-client-sdk/core` и `twenty-client-sdk/metadata` — они не включаются в бандл, а подставляются сервером во время выполнения. + +## Тестирование вашего приложения + +SDK предоставляет программные API, которые позволяют собирать, разворачивать, устанавливать и удалять ваше приложение из тестового кода. В сочетании с [Vitest](https://vitest.dev/) и типизированными клиентами API вы можете писать интеграционные тесты, которые проверяют, что ваше приложение работает сквозным образом на реальном сервере Twenty. + +### Настройка + +Приложение, созданное скэффолдером, уже включает Vitest. Если вы настраиваете его вручную, установите зависимости: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Создайте `vitest.config.ts` в корне вашего приложения: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Создайте файл инициализации, который проверяет доступность сервера перед запуском тестов: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### Программные API SDK + +Подпуть `twenty-sdk/cli` экспортирует функции, которые можно вызывать напрямую из тестового кода: + +| Функция | Описание | +| -------------- | ---------------------------------------------------------- | +| `appBuild` | Собрать приложение и при необходимости упаковать tar-архив | +| `appDeploy` | Загрузить tar-архив на сервер | +| `appInstall` | Установить приложение в активное рабочее пространство | +| `appUninstall` | Удалить приложение из активного рабочего пространства | + +Каждая функция возвращает объект результата с `success: boolean` и либо `data`, либо `error`. + +### Написание интеграционного теста + +Полный пример, который собирает, разворачивает и устанавливает приложение, а затем проверяет, что оно появляется в рабочем пространстве: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Запуск тестов + +Убедитесь, что ваш локальный сервер Twenty запущен, затем: + +```bash filename="Terminal" +yarn test +``` + +Или в режиме наблюдения во время разработки: + +```bash filename="Terminal" +yarn test:watch +``` + +### Проверка типов + +Вы также можете запустить проверку типов для своего приложения без запуска тестов: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +Это запускает `tsc --noEmit` и сообщает о любых ошибках типов. + +## Справочник по CLI + +Помимо `dev`, `build`, `add` и `typecheck`, CLI предоставляет команды для выполнения функций, просмотра логов и управления установками приложений. + +### Выполнение функций (`yarn twenty exec`) + +Запустите функцию логики вручную, не вызывая ее через HTTP, cron или событие базы данных: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute the post-install function +yarn twenty exec --postInstall +``` + +### Просмотр логов функций (`yarn twenty logs`) + +Потоковая передача журналов выполнения функций логики вашего приложения: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +Это отличается от `yarn twenty server logs`, который показывает логи контейнера Docker. `yarn twenty logs` показывает журналы выполнения функций вашего приложения с сервера Twenty. + + +### Удаление приложения (`yarn twenty uninstall`) + +Удалите свое приложение из активного рабочего пространства: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` + +## Управление удалёнными серверами + +**Remote** — это сервер Twenty, к которому подключается ваше приложение. Во время настройки скэффолдер автоматически создаст его для вас. Вы можете в любой момент добавлять новые удалённые серверы или переключаться между ними. + +```bash filename="Terminal" +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add + +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local + +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote + +# List all configured remotes +yarn twenty remote list + +# Switch the active remote +yarn twenty remote switch +``` + +Ваши учётные данные хранятся в `~/.twenty/config.json`. + +## CI с GitHub Actions + +Скэффолдер генерирует готовый к использованию workflow GitHub Actions в `.github/workflows/ci.yml`. Он автоматически запускает ваши интеграционные тесты при каждом пуше в `main` и в pull request'ах. + +Рабочий процесс: + +1. Извлекает ваш код +2. Поднимает временный сервер Twenty с помощью экшена `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` +3. Устанавливает зависимости с помощью `yarn install --immutable` +4. Запускает `yarn test` с `TWENTY_API_URL` и `TWENTY_API_KEY`, переданными из выходных данных экшена + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +Вам не нужно настраивать секреты — экшен `spawn-twenty-docker-image` запускает эфемерный сервер Twenty прямо в раннере и выводит данные для подключения. Секрет `GITHUB_TOKEN` предоставляется GitHub автоматически. + +Чтобы закрепить конкретную версию Twenty вместо `latest`, измените переменную окружения `TWENTY_VERSION` в начале workflow. diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/data-model.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/data-model.mdx new file mode 100644 index 00000000000..27e3c41666a --- /dev/null +++ b/packages/twenty-docs/l/ru/developers/extend/apps/data-model.mdx @@ -0,0 +1,494 @@ +--- +title: Модель данных +description: Define objects, fields, roles, and application metadata with the Twenty SDK. +icon: database +--- + +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/`) — это лишь соглашение, а не требование. + + + + + +Роли инкапсулируют права на объекты и действия вашего рабочего пространства. + +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export default defineRole({ + universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', + label: 'My new role', + description: 'A role that can be used in your workspace', + canReadAllObjectRecords: false, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + canReadObjectRecords: true, + canUpdateObjectRecords: true, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + }, + ], + fieldPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + fieldUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, + canReadFieldValue: false, + canUpdateFieldValue: false, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +В каждом приложении должен быть ровно один вызов `defineApplication`, который описывает: + +* **Идентификация**: идентификаторы, отображаемое имя и описание. +* **Разрешения**: какую роль используют его функции и фронтенд-компоненты. +* **(Необязательно) Переменные**: пары ключ–значение, доступные вашим функциям как переменные окружения. +* **(Необязательно) Предустановочные / постустановочные функции**: логические функции, которые запускаются до или после установки. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk/define'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +Заметки: +* Поля `universalIdentifier` — это детерминированные идентификаторы, которые принадлежат вам. Сгенерируйте их один раз и сохраняйте неизменными между синхронизациями. +* `applicationVariables` становятся переменными окружения для ваших функций и фронтенд-компонентов (например, `DEFAULT_RECIPIENT_NAME` доступна как `process.env.DEFAULT_RECIPIENT_NAME`). +* `defaultRoleUniversalIdentifier` должен ссылаться на роль, определённую с помощью `defineRole()` (см. выше). +* Предустановочные и постустановочные функции обнаруживаются автоматически во время сборки манифеста — вам не нужно указывать их в `defineApplication()`. + +#### Метаданные маркетплейса + +Если вы планируете [опубликовать приложение](/l/ru/developers/extend/apps/publishing), эти необязательные поля определяют, как оно отображается в маркетплейсе: + +| Поле | Описание | +| ------------------ | ------------------------------------------------------------------------------------------------------------------- | +| `author` | Имя автора или название компании | +| `category` | Категория приложения для фильтрации в маркетплейсе | +| `logoUrl` | Путь к логотипу вашего приложения (например, `public/logo.png`) | +| `screenshots` | Массив путей к скриншотам (например, `public/screenshot-1.png`) | +| `aboutDescription` | Расширенное описание в Markdown для вкладки "About". Если опущено, маркетплейс использует `README.md` пакета из npm | +| `websiteUrl` | Ссылка на ваш сайт | +| `termsUrl` | Ссылка на условия предоставления услуг | +| `emailSupport` | Адрес электронной почты поддержки | +| `issueReportUrl` | Ссылка на систему отслеживания проблем | + +#### Роли и разрешения + +Поле `defaultRoleUniversalIdentifier` в `application-config.ts` обозначает роль по умолчанию, используемую логическими функциями и фронтенд-компонентами вашего приложения. Подробности см. в `defineRole` выше. + +* Токен времени выполнения, подставляемый как `TWENTY_APP_ACCESS_TOKEN`, формируется из этой роли. +* Типизированный клиент ограничен правами, предоставленными этой ролью. +* Следуйте принципу наименьших привилегий: создайте отдельную роль только с теми правами, которые нужны вашим функциям. + +##### Роль функции по умолчанию + +Когда вы генерируете новое приложение, CLI создаёт файл роли по умолчанию: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk/define'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + label: 'Default function role', + description: 'Default role for function Twenty client', + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [], + fieldPermissions: [], + permissionFlags: [], +}); +``` + +Значение `universalIdentifier` этой роли указывается в `application-config.ts` как `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** определяет, что может делать роль. +* **application-config.ts** указывает на эту роль, чтобы ваши функции наследовали её права. + +Заметки: +* Начните со сгенерированной роли, затем постепенно ограничивайте её, следуя принципу наименьших привилегий. +* Замените `objectPermissions` и `fieldPermissions` на объекты и поля, которые действительно нужны вашим функциям. +* `permissionFlags` управляют доступом к возможностям на уровне платформы. Сведите их к минимуму. +* См. рабочий пример: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + + +Пользовательские объекты описывают как схему, так и поведение записей в вашем рабочем пространстве. Используйте `defineObject()` для определения объектов со встроенной валидацией: + +```ts postCard.object.ts +import { defineObject, FieldType } from 'twenty-sdk/define'; + +enum PostCardStatus { + DRAFT = 'DRAFT', + SENT = 'SENT', + DELIVERED = 'DELIVERED', + RETURNED = 'RETURNED', +} + +export default defineObject({ + universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', + nameSingular: 'postCard', + namePlural: 'postCards', + labelSingular: 'Post Card', + labelPlural: 'Post Cards', + description: 'A post card object', + icon: 'IconMail', + fields: [ + { + universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', + name: 'content', + type: FieldType.TEXT, + label: 'Content', + description: "Postcard's content", + icon: 'IconAbc', + }, + { + universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', + name: 'recipientName', + type: FieldType.FULL_NAME, + label: 'Recipient name', + icon: 'IconUser', + }, + { + universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', + name: 'recipientAddress', + type: FieldType.ADDRESS, + label: 'Recipient address', + icon: 'IconHome', + }, + { + universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', + name: 'status', + type: FieldType.SELECT, + label: 'Status', + icon: 'IconSend', + defaultValue: `'${PostCardStatus.DRAFT}'`, + options: [ + { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, + { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, + { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, + { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, + ], + }, + { + universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', + name: 'deliveredAt', + type: FieldType.DATE_TIME, + label: 'Delivered at', + icon: 'IconCheck', + isNullable: true, + defaultValue: null, + }, + ], +}); +``` + +Основные моменты: + +* Используйте `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`, чтобы указать, какой объект они расширяют: + +```ts src/fields/company-loyalty-tier.field.ts +import { defineField, FieldType } from 'twenty-sdk/define'; + +export default defineField({ + universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', + objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object + name: 'loyaltyTier', + type: FieldType.SELECT, + label: 'Loyalty Tier', + icon: 'IconStar', + options: [ + { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, + { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, + { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, + ], +}); +``` + +Основные моменты: +* `objectUniversalIdentifier` определяет целевой объект. Для стандартных объектов используйте `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`, экспортируемые из `twenty-sdk`. +* При определении полей непосредственно в `defineObject()` вам не нужен `objectUniversalIdentifier` — он наследуется от родительского объекта. +* `defineField()` — единственный способ добавить поля к объектам, которые вы не создавали с помощью `defineObject()`. + + + + +Отношения связывают объекты между собой. В Twenty отношения всегда двунаправленные — вы определяете обе стороны, и каждая сторона ссылается на другую. + +Существуют два типа отношений: + +| Тип отношения | Описание | Есть внешний ключ? | +| ------------- | --------------------------------------------------------------------- | ---------------------- | +| `MANY_TO_ONE` | Многие записи этого объекта указывают на одну запись целевого объекта | Да (`joinColumnName`) | +| `ONE_TO_MANY` | Одна запись этого объекта имеет много записей целевого объекта | Нет (обратная сторона) | + +#### Как работают отношения + +Каждое отношение требует **двух полей**, которые ссылаются друг на друга: + +1. Сторона **MANY_TO_ONE** — находится в объекте, который содержит внешний ключ +2. Сторона **ONE_TO_MANY** — находится в объекте, которому принадлежит коллекция + +Оба поля используют `FieldType.RELATION` и ссылаются друг на друга через `relationTargetFieldMetadataUniversalIdentifier`. + +#### Пример: Почтовая открытка имеет много получателей + +Предположим, `PostCard` может быть отправлен множству записей `PostCardRecipient`. Каждый получатель относится ровно к одной открытке. + +**Шаг 1: Определите сторону ONE_TO_MANY на PostCard** (сторона "one"): + +```ts src/fields/post-card-recipients-on-post-card.field.ts +import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; +// Import from the other side +import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; + +export default defineField({ + universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCardRecipients', + label: 'Post Card Recipients', + icon: 'IconUsers', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); +``` + +**Шаг 2: Определите сторону MANY_TO_ONE на PostCardRecipient** (сторона "many" — содержит внешний ключ): + +```ts src/fields/post-card-on-post-card-recipient.field.ts +import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; +// Import from the other side +import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; + +export default defineField({ + universalIdentifier: POST_CARD_FIELD_ID, + objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + icon: 'IconMail', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, +}); +``` + + +**Циклические импорты:** Оба поля отношений ссылаются на `universalIdentifier` друг друга. Чтобы избежать проблем с циклическими импортами, экспортируйте идентификаторы полей как именованные константы из каждого файла и импортируйте их в другом файле. Система сборки разрешает это на этапе компиляции. + + +#### Связывание со стандартными объектами + +Чтобы создать отношение со встроенным объектом Twenty (Person, Company и т. д.), используйте `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: + +```ts src/fields/person-on-self-hosting-user.field.ts +import { + defineField, + FieldType, + RelationType, + OnDeleteAction, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; +import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; + +export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; +export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; + +export default defineField({ + universalIdentifier: PERSON_FIELD_ID, + objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'person', + label: 'Person', + description: 'Person matching with the self hosting user', + isNullable: true, + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'personId', + }, +}); +``` + +#### Свойства поля отношения + +| Свойство | Обязательно | Описание | +| ------------------------------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------- | +| `type` | Да | Должно быть `FieldType.RELATION` | +| `relationTargetObjectMetadataUniversalIdentifier` | Да | `universalIdentifier` целевого объекта | +| `relationTargetFieldMetadataUniversalIdentifier` | Да | `universalIdentifier` соответствующего поля на целевом объекте | +| `universalSettings.relationType` | Да | `RelationType.MANY_TO_ONE` или `RelationType.ONE_TO_MANY` | +| `universalSettings.onDelete` | Только для MANY_TO_ONE | Что происходит при удалении связанной записи: `CASCADE`, `SET_NULL`, `RESTRICT` или `NO_ACTION` | +| `universalSettings.joinColumnName` | Только для MANY_TO_ONE | Имя столбца базы данных для внешнего ключа (например, `postCardId`) | + +#### Встроенные поля отношений в defineObject + +Вы также можете определять поля отношений непосредственно внутри `defineObject()`. В этом случае опустите `objectUniversalIdentifier` — он наследуется от родительского объекта: + +```ts +export default defineObject({ + universalIdentifier: '...', + nameSingular: 'postCardRecipient', + // ... + fields: [ + { + universalIdentifier: POST_CARD_FIELD_ID, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, + }, + // ... other fields + ], +}); +``` + + + +## Создание заготовок сущностей с помощью `yarn twenty add` + +Вместо ручного создания файлов сущностей вы можете использовать интерактивный генератор: + +```bash filename="Terminal" +yarn twenty add +``` + +Он предложит выбрать тип сущности и проведёт вас по обязательным полям. Он генерирует готовый к использованию файл со стабильным `universalIdentifier` и корректным вызовом `defineEntity()`. + +Вы также можете передать тип сущности напрямую, чтобы пропустить первый запрос: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Доступные типы сущностей + +| Тип сущности | Команда | Сгенерированный файл | +| -------------------- | ------------------------------------ | ------------------------------------------------------- | +| Объект | `yarn twenty add object` | `src/objects/\.ts` | +| Поле | `yarn twenty add field` | `src/fields/\.ts` | +| Логическая функция | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | +| Компонент фронтенда | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | +| Роль | `yarn twenty add role` | `src/roles/\.ts` | +| Навык | `yarn twenty add skill` | `src/skills/\.ts` | +| Агент | `yarn twenty add agent` | `src/agents/\.ts` | +| Представление | `yarn twenty add view` | `src/views/\.ts` | +| Пункт меню навигации | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | +| Макет страницы | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | + +### Что генерирует скэффолдер + +У каждого типа сущности есть свой шаблон. Например, `yarn twenty add object` запрашивает: + +1. **Имя (единственное число)** — например, `invoice` +2. **Имя (множественное число)** — например, `invoices` +3. **Метка (единственное число)** — заполняется автоматически из имени (например, `Invoice`) +4. **Метка (множественное число)** — заполняется автоматически (например, `Invoices`) +5. **Создать представление и пункт навигации?** — если вы ответите «да», скэффолдер также сгенерирует соответствующее представление и ссылку в боковой панели для нового объекта. + +У других типов сущностей подсказки проще — в большинстве случаев запрашивается только имя. + +Тип сущности `field` более детализирован: он запрашивает имя поля, метку, тип (из списка всех доступных типов полей, таких как `TEXT`, `NUMBER`, `SELECT`, `RELATION` и т. д.), а также `universalIdentifier` целевого объекта. + +### Пользовательский путь вывода + +Используйте флаг `--path`, чтобы поместить сгенерированный файл в пользовательское расположение: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/front-components.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/front-components.mdx new file mode 100644 index 00000000000..56d1f08975e --- /dev/null +++ b/packages/twenty-docs/l/ru/developers/extend/apps/front-components.mdx @@ -0,0 +1,419 @@ +--- +title: Компоненты фронтенда +description: Build React components that render inside Twenty's UI with sandboxed isolation. +icon: window-maximize +--- + +Фронтенд-компоненты — это компоненты React, которые отображаются непосредственно внутри интерфейса Twenty. Они выполняются в изолированном Web Worker с использованием Remote DOM — ваш код изолирован (sandboxed), но рендерится нативно на странице, а не в iframe. + +## Где можно использовать фронт-компоненты + +Фронт-компоненты могут отображаться в двух местах внутри Twenty: + +* **Боковая панель** — фронт-компоненты с интерфейсом открываются в правой боковой панели. Это поведение по умолчанию, когда фронт-компонент запускается из меню команд. +* **Виджеты (дашборды и страницы записей)** — фронт-компоненты можно встраивать как виджеты в макеты страниц. При настройке дашборда или макета страницы записи пользователи могут добавить виджет фронт-компонента. + +## Простой пример + +Самый быстрый способ увидеть фронтенд-компонент в действии — зарегистрировать его как **команду**. Добавление поля `command` с `isPinned: true` делает его кнопкой быстрого действия в правом верхнем углу страницы — макет страницы не требуется: + +```tsx src/front-components/hello-world.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; + +const HelloWorld = () => { + return ( +
+

Hello from my app!

+

This component renders inside Twenty.

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, +}); +``` + +После синхронизации с помощью `yarn twenty dev` (или однократного запуска `yarn twenty dev --once`) быстрое действие появится в правом верхнем углу страницы: + +
+ Кнопка быстрого действия в правом верхнем углу +
+ +Нажмите её, чтобы отобразить компонент инлайн. + +## Поля конфигурации + +| Поле | Обязательно | Описание | +| --------------------- | ----------- | -------------------------------------------------------------------------------------------------- | +| `universalIdentifier` | Да | Стабильный уникальный идентификатор для этого компонента | +| `component` | Да | Функция компонента React | +| `name` | Нет | Отображаемое имя | +| `description` | Нет | Описание того, что делает компонент | +| `isHeadless` | Нет | Установите значение `true`, если у компонента нет видимого пользовательского интерфейса (см. ниже) | +| `command` | Нет | Зарегистрируйте компонент как команду (см. [параметры команды](#command-options) ниже) | + +## Размещение фронт-компонента на странице + +Помимо команд, вы можете встроить фронт-компонент непосредственно на страницу записи, добавив его как виджет в **макет страницы**. См. раздел [definePageLayout](/l/ru/developers/extend/apps/skills-and-agents#definepagelayout) для подробностей. + +## Headless и non-headless + +Фронт-компоненты поддерживают два режима отображения, управляемых опцией `isHeadless`: + +**Non-headless (по умолчанию)** — компонент отображает видимый интерфейс. При запуске из меню команд он открывается в боковой панели. Это поведение по умолчанию, когда `isHeadless` имеет значение `false` или опущен. + +**Headless (`isHeadless: true`)** — компонент монтируется невидимо в фоновом режиме. Он не открывает боковую панель. Компоненты headless предназначены для действий, которые выполняют логику и затем размонтируются — например, запуск асинхронной задачи, переход на страницу или показ модального окна подтверждения. Они естественно сочетаются с компонентами SDK Command, описанными ниже. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Поскольку компонент возвращает `null`, Twenty пропускает рендеринг контейнера для него — в макете не появляется пустое место. Компонент по-прежнему имеет доступ ко всем хукам и API взаимодействия с хостом. + +## Компоненты SDK Command + +Пакет `twenty-sdk` предоставляет четыре вспомогательных компонента Command, предназначенных для headless фронт-компонентов. Каждый компонент выполняет действие при монтировании, обрабатывает ошибки, показывая уведомление snackbar, и автоматически размонтирует фронт-компонент по завершении. + +Импортируйте их из `twenty-sdk/command`: + +* **`Command`** — запускает асинхронный колбэк через проп `execute`. +* **`CommandLink`** — переходит по пути внутри приложения. Пропы: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — открывает модальное окно подтверждения. Если пользователь подтвердит, выполняет колбэк `execute`. Пропы: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — открывает конкретную страницу боковой панели. Пропы: `page`, `pageTitle`, `pageIcon`. + +Полный пример headless фронт-компонента, использующего `Command` для запуска действия из меню команд: + +```tsx src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +А также пример с использованием `CommandModal` для запроса подтверждения перед выполнением: + +```tsx src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +## Доступ к контексту времени выполнения + +Внутри вашего компонента используйте хуки SDK для доступа к текущему пользователю, записи и экземпляру компонента: + +```tsx src/front-components/record-info.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk/front-component'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Доступные хуки: + +| Хук | Возвращает | Описание | +| --------------------------------------------- | ------------------- | ----------------------------------------------------------------- | +| `useUserId()` | `string` или `null` | ID текущего пользователя | +| `useRecordId()` | `string` или `null` | ID текущей записи (при размещении на странице записи) | +| `useFrontComponentId()` | `string` | ID этого экземпляра компонента | +| `useFrontComponentExecutionContext(selector)` | различается | Доступ к полному контексту выполнения с помощью функции-селектора | + +## API взаимодействия с хостом + +Компоненты фронтенда могут вызывать навигацию, модальные окна и уведомления с помощью функций из `twenty-sdk`: + +| Функция | Описание | +| ----------------------------------------------- | -------------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Перейти на страницу в приложении | +| `openSidePanelPage(params)` | Открыть боковую панель | +| `closeSidePanel()` | Закрыть боковую панель | +| `openCommandConfirmationModal(params)` | Показать диалог подтверждения | +| `enqueueSnackbar(params)` | Показать всплывающее уведомление | +| `unmountFrontComponent()` | Размонтировать компонент | +| `updateProgress(progress)` | Обновить индикатор прогресса | + +Пример, который использует API хоста для показа snackbar и закрытия боковой панели после завершения действия: + +```tsx src/front-components/archive-record.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId } from 'twenty-sdk/front-component'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + +## Параметры команды + +Добавление поля `command` в `defineFrontComponent` регистрирует компонент в меню команд (Cmd+K). Если `isPinned` имеет значение `true`, команда также отображается как кнопка быстрого действия в правом верхнем углу страницы. + +| Поле | Обязательно | Описание | +| --------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `universalIdentifier` | Да | Стабильный уникальный идентификатор для команды | +| `label` | Да | Полная метка, отображаемая в меню команд (Cmd+K) | +| `shortLabel` | Нет | Короткая метка, отображаемая на закреплённой кнопке быстрого действия | +| `icon` | Нет | Имя значка, отображаемое рядом с меткой (например, `'IconBolt'`, `'IconSend'`) | +| `isPinned` | Нет | При значении `true` показывает команду как кнопку быстрого действия в правом верхнем углу страницы | +| `availabilityType` | Нет | Определяет, где отображается команда: `'GLOBAL'` (доступна всегда), `'RECORD_SELECTION'` (только при выборе записей) или `'FALLBACK'` (показывается, когда другие команды не подходят) | +| `availabilityObjectUniversalIdentifier` | Нет | Ограничивает команду страницами определённого типа объектов (например, только для записей Company) | +| `conditionalAvailabilityExpression` | Нет | Логическое выражение для динамического управления видимостью команды (см. ниже) | + +## Выражения условной доступности + +Поле `conditionalAvailabilityExpression` позволяет управлять видимостью команды в зависимости от текущего контекста страницы. Импортируйте типизированные переменные и операторы из `twenty-sdk`, чтобы составлять выражения: + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk/front-component'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Переменные контекста** — представляют текущее состояние страницы: + +| Переменная | Тип | Описание | +| ------------------------------ | --------- | ------------------------------------------------------------------------ | +| `pageType` | `string` | Текущий тип страницы (например, `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Указывает, рендерится ли компонент в боковой панели | +| `numberOfSelectedRecords` | `number` | Количество выбранных в данный момент записей | +| `isSelectAll` | `boolean` | Активен ли режим "выбрать все" | +| `selectedRecords` | `массив` | Объекты выбранных записей | +| `favoriteRecordIds` | `массив` | ID избранных записей | +| `objectPermissions` | `object` | Разрешения для текущего типа объекта | +| `targetObjectReadPermissions` | `object` | Права на чтение для целевого объекта | +| `targetObjectWritePermissions` | `object` | Права на запись для целевого объекта | +| `featureFlags` | `object` | Активные флаги функций | +| `objectMetadataItem` | `object` | Метаданные текущего типа объекта | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Есть ли у текущего представления фильтр мягкого удаления | + +**Операторы** — комбинируют переменные в логические выражения: + +| Оператор | Описание | +| ----------------------------------- | ------------------------------------------------------------------------- | +| `isDefined(value)` | `true`, если значение не null/undefined | +| `isNonEmptyString(value)` | `true`, если значение — непустая строка | +| `includes(array, value)` | `true`, если массив содержит значение | +| `includesEvery(array, prop, value)` | `true`, если свойство каждого элемента включает значение | +| `every(array, prop)` | `true`, если свойство истинно для каждого элемента | +| `everyDefined(array, prop)` | `true`, если свойство определено у каждого элемента | +| `everyEquals(array, prop, value)` | `true`, если свойство равно значению у каждого элемента | +| `some(array, prop)` | `true`, если свойство истинно хотя бы у одного элемента | +| `someDefined(array, prop)` | `true`, если свойство определено хотя бы у одного элемента | +| `someEquals(array, prop, value)` | `true`, если свойство равно значению хотя бы у одного элемента | +| `someNonEmptyString(array, prop)` | `true`, если свойство является непустой строкой хотя бы у одного элемента | +| `none(array, prop)` | `true`, если свойство ложно для каждого элемента | +| `noneDefined(array, prop)` | `true`, если свойство не определено ни у одного элемента | +| `noneEquals(array, prop, value)` | `true`, если свойство не равно значению ни у одного элемента | + +## Публичные ресурсы + +Компоненты фронтенда могут получать доступ к файлам из каталога приложения `public/` с помощью `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +См. [раздел о публичных ресурсах](/l/ru/developers/extend/apps/cli-and-testing#public-assets-public-folder) для подробностей. + +## Стилизация + +Компоненты фронтенда поддерживают несколько подходов к стилизации. Вы можете использовать: + +* **Встроенные стили** — `style={{ color: 'red' }}` +* **Компоненты Twenty UI** — импорт из `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar и другие) +* **Emotion** — CSS-in-JS с `@emotion/react` +* **Styled-components** — паттерны `styled.div` +* **Tailwind CSS** — утилитарные классы +* **Любая библиотека CSS-in-JS**, совместимая с React + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/getting-started.mdx index 1fb1f208f11..2f2f4ff1874 100644 --- a/packages/twenty-docs/l/ru/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/ru/developers/extend/apps/getting-started.mdx @@ -1,12 +1,9 @@ --- title: Начало работы +icon: rocket description: Создайте своё первое приложение Twenty за считанные минуты. --- - -Приложения сейчас проходят альфа-тестирование. Функция работает, но продолжает развиваться. - - ## Что такое приложения? Приложения позволяют расширять Twenty с помощью пользовательских объектов, полей, логических функций, фронтенд-компонентов, навыков ИИ и многого другого — всё это управляется как код. Вместо настройки всего через интерфейс вы определяете модель данных и логику на TypeScript и развёртываете их в одном или нескольких рабочих пространствах. diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/layout.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/layout.mdx new file mode 100644 index 00000000000..b16b1728dec --- /dev/null +++ b/packages/twenty-docs/l/ru/developers/extend/apps/layout.mdx @@ -0,0 +1,131 @@ +--- +title: Макет +description: Define views, navigation menu items, and page layouts to shape how your app appears in Twenty. +icon: table-columns +--- + +Layout entities control how your app surfaces inside Twenty's UI — what lives in the sidebar, which saved views ship with the app, and how a record detail page is arranged. + +## Layout concepts + +| Concept | What it controls | Сущность | +| ------------------------ | --------------------------------------------------------------------------------- | -------------------------- | +| **View** | A saved list configuration for an object — visible fields, order, filters, groups | `defineView` | +| **Navigation Menu Item** | An entry in the left sidebar that links to a view or an external URL | `defineNavigationMenuItem` | +| **Page Layout** | The tabs and widgets that make up a record's detail page | `definePageLayout` | + +Views, navigation items, and page layouts reference each other by `universalIdentifier`: + +* A **navigation menu item** of type `VIEW` points at a `defineView` identifier, so the sidebar link opens that saved view. +* A **page layout** of type `RECORD_PAGE` targets an object and can embed [front components](/l/ru/developers/extend/apps/front-components) inside its tabs as widgets. + + + + +Представления — это сохранённые конфигурации отображения записей объекта: какие поля видны, их порядок, а также применённые фильтры и группы. Используйте `defineView()` для поставки преднастроенных представлений вместе с вашим приложением: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +Основные моменты: +* `objectUniversalIdentifier` указывает, к какому объекту применяется это представление. +* `key` определяет тип представления (например, `ViewKey.INDEX` для основного списка). +* `fields` управляет тем, какие столбцы отображаются и в каком порядке. Каждое поле ссылается на `fieldMetadataUniversalIdentifier`. +* Также вы можете определить `filters`, `filterGroups`, `groups` и `fieldGroups` для более продвинутых конфигураций. +* `position` управляет порядком, когда для одного и того же объекта существует несколько представлений. + + + + +Пункты навигационного меню добавляют пользовательские элементы в боковую панель рабочего пространства. Используйте `defineNavigationMenuItem()` для ссылок на представления, внешние URL или объекты: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Основные моменты: +* `type` определяет, на что ссылается пункт меню: `NavigationMenuItemType.VIEW` для сохранённого представления или `NavigationMenuItemType.LINK` для внешнего URL. +* Для ссылок на представления укажите `viewUniversalIdentifier`. Для внешних ссылок укажите `link`. +* `position` управляет порядком в боковой панели. +* `icon` и `color` (необязательно) настраивают внешний вид. + + + + +Макеты страниц позволяют настраивать вид страницы с деталями записи: какие вкладки отображаются, какие виджеты внутри каждой вкладки и как они расположены. Используйте `definePageLayout()` для поставки пользовательских макетов вместе с вашим приложением: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Основные моменты: +* `type` обычно равен `'RECORD_PAGE'` для настройки детального представления конкретного объекта. +* `objectUniversalIdentifier` указывает, к какому объекту применяется этот макет. +* Каждая `tab` определяет раздел страницы с `title`, `position` и `layoutMode` (`CANVAS` для свободного макета). +* Каждый `widget` внутри вкладки может отображать компонент фронтенда, список связей или другие встроенные типы виджетов. +* `position` у вкладок управляет их порядком. Используйте большие значения (например, 50), чтобы разместить пользовательские вкладки после встроенных. + + + diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/logic-functions.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/logic-functions.mdx new file mode 100644 index 00000000000..8d437ad1ff6 --- /dev/null +++ b/packages/twenty-docs/l/ru/developers/extend/apps/logic-functions.mdx @@ -0,0 +1,559 @@ +--- +title: Логические функции +description: Define server-side TypeScript functions with HTTP, cron, and database event triggers. +icon: bolt +--- + +Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents. + + + + +Каждый файл функции использует `defineLogicFunction()` для экспорта конфигурации с обработчиком и необязательными триггерами. + +```ts src/logic-functions/createPostCard.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; + +const handler = async (params: RoutePayload) => { + const client = new CoreApiClient(); + const name = 'name' in params.queryStringParameters + ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' + : 'Hello world'; + + const result = await client.mutation({ + createPostCard: { + __args: { data: { name } }, + id: true, + name: true, + }, + }); + return result; +}; + +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'create-new-post-card', + timeoutSeconds: 2, + handler, + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: true, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ +}); +``` + +Доступные типы триггеров: +* **httpRoute**: Публикует вашу функцию по HTTP-пути и методу **под конечной точкой `/s/`**: +> например, `path: '/post-card/create'` вызывается по адресу `https://your-twenty-server.com/s/post-card/create` +* **cron**: Запускает вашу функцию по расписанию с использованием выражения CRON. +* **databaseEvent**: Запускается при событиях жизненного цикла объектов рабочего пространства. Когда операция события — `updated`, можно указать конкретные поля для отслеживания в массиве `updatedFields`. Если оставить не заданным или пустым, любое обновление будет вызывать функцию. +> например, `person.updated`, `*.created`, `company.*` + + +Вы также можете вручную выполнить функцию с помощью CLI: + +```bash filename="Terminal" +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' +``` + +```bash filename="Terminal" +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + +Вы можете просматривать логи с помощью: + +```bash filename="Terminal" +yarn twenty logs +``` + + +#### Полезная нагрузка триггера маршрута + +Когда триггер маршрута вызывает вашу логическую функцию, она получает объект `RoutePayload`, который соответствует [формату AWS HTTP API v2](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html). +Импортируйте тип `RoutePayload` из `twenty-sdk`: + +```ts +import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; + +const handler = async (event: RoutePayload) => { + const { headers, queryStringParameters, pathParameters, body } = event; + const { method, path } = event.requestContext.http; + + return { message: 'Success' }; +}; +``` + +Тип `RoutePayload` имеет следующую структуру: + + | Свойство | Тип | Описание | Пример | + | ---------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record\` | HTTP-заголовки (только перечисленные в `forwardedRequestHeaders`) | см. раздел ниже | + | `queryStringParameters` | `Record\` | Параметры строки запроса (несколько значений объединяются запятыми) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record\` | Параметры пути, извлечённые из шаблона маршрута | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Разобранное тело запроса (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | Является ли тело закодированным в base64 | | + | `requestContext.http.method` | `string` | Метод HTTP (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | Необработанный путь запроса | | + + +#### forwardedRequestHeaders + +По умолчанию HTTP-заголовки из входящих запросов **не** передаются в вашу логическую функцию по соображениям безопасности. +Чтобы получить доступ к определённым заголовкам, перечислите их в массиве `forwardedRequestHeaders`: + +```ts +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'webhook-handler', + handler, + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, +}); +``` + +В обработчике обращайтесь к переданным заголовкам следующим образом: + +```ts +const handler = async (event: RoutePayload) => { + const signature = event.headers['x-webhook-signature']; + const contentType = event.headers['content-type']; + + // Validate webhook signature... + return { received: true }; +}; +``` + + +Имена заголовков приводятся к нижнему регистру. Обращайтесь к ним, используя ключи в нижнем регистре (например, `event.headers['content-type']`). + + +#### Предоставление функции как инструмента + +Логические функции можно предоставлять как **инструменты** для ИИ-агентов и рабочих процессов. Когда функция помечена как инструмент, она становится доступной для функций ИИ Twenty и может использоваться в автоматизациях рабочих процессов. + +Чтобы пометить логическую функцию как инструмент, установите `isTool: true`: + +```ts src/logic-functions/enrich-company.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const handler = async (params: { companyName: string; domain?: string }) => { + const client = new CoreApiClient(); + + const result = await client.mutation({ + createTask: { + __args: { + data: { + title: `Enrich data for ${params.companyName}`, + body: `Domain: ${params.domain ?? 'unknown'}`, + }, + }, + id: true, + }, + }); + + return { taskId: result.createTask.id }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + name: 'enrich-company', + description: 'Enrich a company record with external data', + timeoutSeconds: 10, + handler, + isTool: true, +}); +``` + +Основные моменты: + +* Вы можете комбинировать `isTool` с триггерами — функция может одновременно быть инструментом (вызываемым агентами ИИ) и запускаться событиями. +* **`toolInputSchema`** (необязательно): объект JSON Schema, описывающий параметры, которые принимает ваша функция. Схема вычисляется автоматически на основе статического анализа исходного кода, но вы можете задать её явно: + +```ts +export default defineLogicFunction({ + ..., + toolInputSchema: { + type: 'object', + properties: { + companyName: { + type: 'string', + description: 'The name of the company to enrich', + }, + domain: { + type: 'string', + description: 'The company website domain (optional)', + }, + }, + required: ['companyName'], + }, +}); +``` + + +**Напишите хорошее описание в поле `description`.** Агенты ИИ опираются на поле `description` функции, чтобы решить, когда использовать инструмент. Чётко опишите, что делает инструмент и когда его следует вызывать. + + + + + +Послеустановочная функция — это функция логики, которая автоматически выполняется после завершения установки вашего приложения в рабочем пространстве. Сервер выполняет её **после** того, как метаданные приложения синхронизированы и клиент SDK сгенерирован, так что рабочее пространство полностью готово к использованию, а новая схема уже применена. Типичные сценарии использования включают предзаполнение данных по умолчанию, создание начальных записей, настройку параметров рабочего пространства или выделение ресурсов в сторонних сервисах. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + shouldRunSynchronously: false, + handler, +}); +``` + +Вы также можете вручную выполнить постустановочную функцию в любое время с помощью CLI: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Основные моменты: +* Послеустановочные функции используют `definePostInstallLogicFunction()` — специализированный вариант, который опускает настройки триггеров (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`). +* Обработчик получает `InstallPayload` с `{ previousVersion?: string; newVersion: string }` — `newVersion` — это устанавливаемая версия, а `previousVersion` — версия, установленная ранее (или `undefined` при чистой установке). Используйте эти значения, чтобы отличать чистые установки от обновлений и запускать логику миграции, зависящую от версии. +* **Когда запускается хук**: по умолчанию только при чистой установке. Передайте `shouldRunOnVersionUpgrade: true`, если хотите, чтобы он также выполнялся при обновлении приложения с предыдущей версии. Если флаг опущен, по умолчанию он равен `false`, и при обновлении хук пропускается. +* **Модель выполнения — по умолчанию асинхронно, синхронный режим по выбору**: флаг `shouldRunSynchronously` определяет, *как* выполняется post-install. + * `shouldRunSynchronously: false` *(по умолчанию)* — хук **помещается в очередь сообщений** с `retryLimit: 3` и выполняется асинхронно в воркере. Ответ на установку возвращается сразу после постановки задания в очередь, поэтому медленный или дающий сбой обработчик не блокирует вызывающую сторону. Воркер выполнит до трёх повторных попыток. **Используйте это для длительных задач** — наполнение большими наборами данных, вызовы медленных сторонних API, подготовка внешних ресурсов — всего, что может выйти за разумное окно ответа HTTP. + * `shouldRunSynchronously: true` — хук выполняется **непосредственно в процессе установки** (тот же исполнитель, что и для pre-install). Запрос установки блокируется, пока обработчик не завершится, и если он генерирует исключение, вызывающая сторона установки получает `POST_INSTALL_ERROR`. Автоматических повторов нет. **Используйте это для быстрых задач, которые должны завершиться до отправки ответа** — например, выдача ошибки валидации пользователю или быстрая настройка, на которую клиент будет полагаться сразу после возврата вызова установки. Имейте в виду, что к моменту запуска post-install миграция метаданных уже применена, поэтому сбой в синхронном режиме **не** откатывает изменения схемы — он лишь выявляет ошибку. +* Убедитесь, что ваш обработчик идемпотентен. В асинхронном режиме очередь может выполнить до трёх повторных попыток; в любом режиме хук может запускаться снова при обновлениях, когда `shouldRunOnVersionUpgrade: true`. +* Переменные окружения `APPLICATION_ID`, `APP_ACCESS_TOKEN` и `API_URL` доступны внутри обработчика (как и в любой другой логической функции), поэтому вы можете вызывать API Twenty с токеном доступа приложения, ограниченным вашим приложением. +* Для каждого приложения допускается только одна послеустановочная функция. Сборка манифеста завершится ошибкой, если будет обнаружено более одной такой функции. +* Параметры функции `universalIdentifier`, `shouldRunOnVersionUpgrade` и `shouldRunSynchronously` автоматически добавляются в манифест приложения в поле `postInstallLogicFunction` во время сборки — вам не нужно указывать их в `defineApplication()`. +* Тайм-аут по умолчанию установлен на 300 секунд (5 минут), чтобы позволить выполнять более длительные задачи настройки, такие как инициализация данных. +* **Не выполняется в режиме разработки**: когда приложение зарегистрировано локально (через `yarn twenty dev`), сервер полностью пропускает процесс установки и синхронизирует файлы напрямую через наблюдатель CLI — поэтому post-install никогда не запускается в режиме разработки, независимо от `shouldRunSynchronously`. Используйте `yarn twenty exec --postInstall`, чтобы запустить это вручную для запущенного рабочего пространства. + + + + +Функция pre-install — это логическая функция, которая автоматически выполняется во время установки, **до применения миграции метаданных рабочего пространства**. Она использует ту же структуру полезной нагрузки, что и post-install (`InstallPayload`), но находится раньше в процессе установки, чтобы подготовить состояние, от которого зависит предстоящая миграция, — типичные сценарии включают резервное копирование данных, проверку совместимости с новой схемой или архивирование записей, которые будут реструктурированы или удалены. + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +Вы также можете вручную выполнить предустановочную функцию в любое время с помощью CLI: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Основные моменты: +* Функции pre-install используют `definePreInstallLogicFunction()` — та же специализированная конфигурация, что и у post-install, только привязанная к другому этапу жизненного цикла. +* И обработчики pre-, и post-install получают один и тот же тип `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Импортируйте его один раз и используйте повторно в обоих хуках. +* **Когда запускается хук**: выполняется непосредственно перед миграцией метаданных рабочего пространства (`synchronizeFromManifest`). Перед выполнением сервер запускает чисто добавочную «урезанную синхронизацию», которая регистрирует в метаданных рабочего пространства pre-install функцию **новой** версии — ничего больше не затрагивается — а затем выполняет её. Поскольку эта синхронизация только добавляет, объекты, поля и данные предыдущей версии остаются нетронутыми к моменту запуска вашего обработчика: вы можете безопасно читать и сохранять состояние до миграции. +* **Модель выполнения**: pre-install выполняется **синхронно** и **блокирует установку**. Если обработчик генерирует исключение, установка прерывается до применения каких-либо изменений схемы — рабочее пространство остаётся на предыдущей версии в согласованном состоянии. Это сделано намеренно: pre-install — ваш последний шанс отказать в рискованном обновлении. +* Как и в случае с post-install, для каждого приложения допускается только одна предустановочная функция. Она автоматически добавляется в манифест приложения в поле `preInstallLogicFunction` во время сборки. +* **Не выполняется в режиме разработки**: как и post-install, процесс установки полностью пропускается для локально зарегистрированных приложений, поэтому pre-install никогда не запускается при `yarn twenty dev`. Используйте `yarn twenty exec --preInstall`, чтобы запустить это вручную. + + + + +Оба хука являются частью одного и того же процесса установки и получают один и тот же `InstallPayload`. Разница в том, **когда** они запускаются относительно миграции метаданных рабочего пространства, и это определяет, к каким данным можно безопасно обращаться. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ install flow │ +│ │ +│ upload package → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +│ │ +│ old schema visible new schema visible │ +└─────────────────────────────────────────────────────────────┘ +``` + +Pre-install всегда **синхронный** (он блокирует установку и может её прервать). Post-install **по умолчанию асинхронный** — ставится в очередь воркера с автоматическими повторами — но может перейти к синхронному выполнению с `shouldRunSynchronously: true`. См. аккордеон `definePostInstallLogicFunction` выше о том, когда использовать каждый режим. + +**Используйте `post-install` для всего, что требует наличия новой схемы.** Это распространённый случай: + +* Наполнение данными по умолчанию (создание начальных записей, стандартных представлений, демонстрационного контента) для недавно добавленных объектов и полей. +* Регистрация вебхуков в сторонних сервисах теперь, когда у приложения уже есть учётные данные. +* Вызов вашего собственного API для завершения настройки, зависящей от синхронизированных метаданных. +* Идемпотентная логика «убедиться, что это существует», которая должна приводить состояние в соответствие при каждом обновлении — совместите с `shouldRunOnVersionUpgrade: true`. + +Пример — создать запись `PostCard` по умолчанию после установки: + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion }: InstallPayload): Promise => { + if (previousVersion) return; // fresh installs only + + const client = createClient(); + await client.postCard.create({ + data: { title: 'Welcome to Postcard', content: 'Your first card!' }, + }); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Seeds a welcome post card after install.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + handler, +}); +``` + +**Используйте `pre-install`, когда миграция в противном случае уничтожит или повредит существующие данные.** Поскольку pre-install работает с *предыдущей* схемой и при сбое откатывает обновление, это правильное место для всего рискованного: + +* **Резервное копирование данных, которые будут удалены или реструктурированы** — например, вы удаляете поле в v2 и вам нужно скопировать его значения в другое поле или экспортировать их в хранилище до запуска миграции. +* **Архивирование записей, которые новое ограничение сделает недопустимыми** — например, поле становится `NOT NULL`, и вам сначала нужно удалить или исправить строки со значениями null. +* **Проверка совместимости и отказ от обновления, если текущие данные нельзя корректно мигрировать** — выбросьте исключение из обработчика, и установка прервётся без внесения изменений. Это безопаснее, чем обнаружить несовместимость в середине миграции. +* **Переименование или изменение ключей данных** перед изменением схемы, которое привело бы к потере связи. + +Пример — архивировать записи перед разрушительной миграцией: + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { + // Only the 1.x → 2.x upgrade drops the legacy `notes` field. + if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { + return; + } + + const client = createClient(); + const legacyRecords = await client.postCard.findMany({ + where: { notes: { isNotNull: true } }, + }); + + if (legacyRecords.length === 0) return; + + // Copy legacy `notes` into the new `description` field before the migration + // drops the `notes` column. If this fails, the upgrade is aborted and the + // workspace stays on v1 with all data intact. + await Promise.all( + legacyRecords.map((record) => + client.postCard.update({ + where: { id: record.id }, + data: { description: record.notes }, + }), + ), + ); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Backs up legacy notes into description before the v2 migration.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +**Общее правило:** + +| You want to... | Использовать | +| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| Наполнить данными по умолчанию, настроить рабочее пространство, зарегистрировать внешние ресурсы | `post-install` | +| Выполнить длительное наполнение или сторонние вызовы, которые не должны блокировать ответ установки | `post-install` (по умолчанию — `shouldRunSynchronously: false`, с повторами воркера) | +| Выполнить быструю настройку, на которую вызывающая сторона будет полагаться сразу после возврата вызова установки | `post-install` с `shouldRunSynchronously: true` | +| Прочитать или сохранить данные, которые предстоящая миграция может потерять | `pre-install` | +| Отклонить обновление, которое повредит существующие данные | `pre-install` (бросьте исключение из обработчика) | +| Выполнять согласование при каждом обновлении | `post-install` с `shouldRunOnVersionUpgrade: true` | +| Сделать одноразовую настройку только при первой установке | `post-install` с `shouldRunOnVersionUpgrade: false` (по умолчанию) | + + +Если сомневаетесь, выбирайте по умолчанию **post-install**. Обращайтесь к pre-install только тогда, когда сама миграция разрушительна и вам нужно перехватить предыдущее состояние, прежде чем оно исчезнет. + + + + + +## Типизированные клиенты API (twenty-client-sdk) + +Пакет `twenty-client-sdk` предоставляет два типизированных клиента GraphQL для взаимодействия с API Twenty из ваших логических функций и фронт-компонентов. + +| Клиент | Импорт | Конечная точка | Генерируется? | +| ------------------- | ---------------------------- | ----------------------------------------------------------------- | -------------------------------- | +| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — данные рабочего пространства (записи, объекты) | Да, на этапе dev/build | +| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — конфигурация рабочего пространства, загрузка файлов | Нет, поставляется в готовом виде | + + + + +`CoreApiClient` — основной клиент для запросов и изменений данных рабочего пространства. Он **генерируется из схемы вашего рабочего пространства** во время `yarn twenty dev` или `yarn twenty build`, поэтому полностью типизирован в соответствии с вашими объектами и полями. + +```ts +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const client = new CoreApiClient(); + +// Query records +const { companies } = await client.query({ + companies: { + edges: { + node: { + id: true, + name: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, + }, + }, + }, +}); + +// Create a record +const { createCompany } = await client.mutation({ + createCompany: { + __args: { + data: { + name: 'Acme Corp', + }, + }, + id: true, + name: true, + }, +}); +``` + +Клиент использует синтаксис selection-set: передайте `true`, чтобы включить поле, используйте `__args` для аргументов и вкладывайте объекты для отношений. Вы получаете полное автодополнение и проверку типов на основе схемы вашего рабочего пространства. + + +**CoreApiClient генерируется на этапе dev/build.** Если вы используете его, не запустив сначала `yarn twenty dev` или `yarn twenty build`, он выбросит ошибку. Генерация происходит автоматически — CLI анализирует GraphQL-схему вашего рабочего пространства и создает типизированный клиент с помощью `@genql/cli`. + + +#### Использование CoreSchema для аннотаций типов + +`CoreSchema` предоставляет типы TypeScript, соответствующие объектам вашего рабочего пространства — это полезно для типизации состояния компонентов или параметров функций: + +```ts +import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; +import { useState } from 'react'; + +const [company, setCompany] = useState< + Pick | undefined +>(undefined); + +const client = new CoreApiClient(); +const result = await client.query({ + company: { + __args: { filter: { position: { eq: 1 } } }, + id: true, + name: true, + }, +}); +setCompany(result.company); +``` + + + + +`MetadataApiClient` поставляется в готовом виде вместе с SDK (генерация не требуется). Он выполняет запросы к эндпоинту `/metadata` для получения конфигурации рабочего пространства, приложений и загрузки файлов. + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; + +const metadataClient = new MetadataApiClient(); + +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, + }, +}); +``` + +#### Загрузка файлов + +`MetadataApiClient` включает метод `uploadFile` для прикрепления файлов к полям типа файла: + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import * as fs from 'fs'; + +const metadataClient = new MetadataApiClient(); + +const fileBuffer = fs.readFileSync('./invoice.pdf'); + +const uploadedFile = await metadataClient.uploadFile( + fileBuffer, // file contents as a Buffer + 'invoice.pdf', // filename + 'application/pdf', // MIME type + '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier +); + +console.log(uploadedFile); +// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } +``` + +| Параметр | Тип | Описание | +| ---------------------------------- | -------- | ------------------------------------------------------------------ | +| `fileBuffer` | `Buffer` | Необработанное содержимое файла | +| `filename` | `string` | Имя файла (используется для хранения и отображения) | +| `contentType` | `string` | Тип MIME (по умолчанию `application/octet-stream`, если не указан) | +| `fieldMetadataUniversalIdentifier` | `string` | Значение `universalIdentifier` для поля типа файла в вашем объекте | + +Основные моменты: +* Он использует `universalIdentifier` поля (а не его идентификатор, специфичный для рабочего пространства), поэтому ваш код загрузки будет работать в любом рабочем пространстве, где установлено ваше приложение. +* Возвращаемый `url` — это подписанный URL, который можно использовать для доступа к загруженному файлу. + + + + + + Когда ваш код выполняется на Twenty (логические функции или фронт-компоненты), платформа предоставляет учётные данные в виде переменных окружения: + + * `TWENTY_API_URL` — базовый URL API Twenty + * `TWENTY_APP_ACCESS_TOKEN` — краткоживущий ключ, ограниченный ролью функции по умолчанию вашего приложения + + Вам не нужно передавать их клиентам — они автоматически читаются из `process.env`. Права ключа API определяются ролью, указанной в `defaultRoleUniversalIdentifier` в вашем `application-config.ts`. + diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/publishing.mdx index a5155b86cc1..1d678415645 100644 --- a/packages/twenty-docs/l/ru/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/ru/developers/extend/apps/publishing.mdx @@ -1,12 +1,9 @@ --- title: Публикация +icon: загрузить description: Распространяйте своё приложение Twenty в маркетплейсе или разверните его для внутреннего использования. --- - - Приложения сейчас проходят альфа-тестирование. Функция работает, но продолжает развиваться. - - ## Обзор После того как ваше приложение [собрано и протестировано локально](/l/ru/developers/extend/apps/building), у вас есть два пути для его распространения: diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/skills-and-agents.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/skills-and-agents.mdx new file mode 100644 index 00000000000..8f450a43846 --- /dev/null +++ b/packages/twenty-docs/l/ru/developers/extend/apps/skills-and-agents.mdx @@ -0,0 +1,69 @@ +--- +title: Навыки и агенты +description: Define AI skills and agents for your app. +icon: robot +--- + + + Skills and agents are currently in alpha. Функция работает, но продолжает развиваться. + + +Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts. + + + + +Навыки определяют многократно используемые инструкции и возможности, которые агенты ИИ могут использовать в вашем рабочем пространстве. Используйте `defineSkill()` для определения навыков со встроенной валидацией: + +```ts src/skills/example-skill.ts +import { defineSkill } from 'twenty-sdk/define'; + +export default defineSkill({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'sales-outreach', + label: 'Sales Outreach', + description: 'Guides the AI agent through a structured sales outreach process', + icon: 'IconBrain', + content: `You are a sales outreach assistant. When reaching out to a prospect: +1. Research the company and recent news +2. Identify the prospect's role and likely pain points +3. Draft a personalized message referencing specific details +4. Keep the tone professional but conversational`, +}); +``` + +Основные моменты: +* `name` — уникальная строка-идентификатор навыка (рекомендуется kebab-case). +* `label` — читаемое человеком отображаемое имя, показываемое в UI. +* `content` содержит инструкции навыка — это текст, который использует агент ИИ. +* `icon` (необязательно) задаёт значок, отображаемый в UI. +* `description` (необязательно) предоставляет дополнительный контекст о назначении навыка. + + + + +Агенты — это ИИ-помощники, работающие в вашем рабочем пространстве. Используйте `defineAgent()` для создания агентов с пользовательским системным промптом: + +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk/define'; + +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +Основные моменты: +* `name` — уникальная строка-идентификатор агента (рекомендуется kebab-case). +* `label` — отображаемое имя, показываемое в UI. +* `prompt` — это системный промпт, определяющий поведение агента. +* `description` (необязательно) предоставляет контекст о том, что делает агент. +* `icon` (необязательно) задаёт значок, отображаемый в UI. +* `modelId` (необязательно) переопределяет модель ИИ по умолчанию, используемую агентом. + + + diff --git a/packages/twenty-docs/l/ru/developers/extend/oauth.mdx b/packages/twenty-docs/l/ru/developers/extend/oauth.mdx new file mode 100644 index 00000000000..ee878866f5e --- /dev/null +++ b/packages/twenty-docs/l/ru/developers/extend/oauth.mdx @@ -0,0 +1,189 @@ +--- +title: OAuth +icon: ключ +description: Authorization code flow with PKCE and client credentials for server-to-server access. +--- + +Twenty implements OAuth 2.0 with authorization code + PKCE for user-facing apps and client credentials for server-to-server access. Clients are registered dynamically via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) — no manual setup in a dashboard. + +## When to Use OAuth + +| Сценарий | Auth Method | +| --------------------------------------- | -------------------------------------------------------------------------------- | +| Internal scripts, automation | [API Key](/l/ru/developers/extend/api#authentication) | +| External app acting on behalf of a user | **OAuth — Authorization Code** | +| Server-to-server, no user context | **OAuth — Client Credentials** | +| Twenty App with UI extensions | [Apps](/l/ru/developers/extend/apps/getting-started) (OAuth is handled automatically) | + +## Register a Client + +Twenty supports **dynamic client registration** per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). No manual setup needed — register programmatically: + +```bash +POST /oauth/register +Content-Type: application/json + +{ + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"], + "grant_types": ["authorization_code"], + "token_endpoint_auth_method": "client_secret_post" +} +``` + +**Response:** + +```json +{ + "client_id": "abc123", + "client_secret": "secret456", + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"] +} +``` + + +Store the `client_secret` securely — it cannot be retrieved later. + + +## Области действия + +| Scope | Доступ | +| --------- | ---------------------------------------------------- | +| `api` | Full read/write access to the Core and Metadata APIs | +| `профиль` | Read the authenticated user's profile information | + +Request scopes as a space-separated string: `scope=api profile` + +## Authorization Code Flow + +Use this flow when your app acts on behalf of a Twenty user. + +### 1. Redirect the user to authorize + +``` +GET /oauth/authorize? + client_id=YOUR_CLIENT_ID& + response_type=code& + redirect_uri=https://myapp.com/callback& + scope=api& + state=random_state_value& + code_challenge=CHALLENGE& + code_challenge_method=S256 +``` + +| Параметр | Обязательно | Описание | +| ----------------------- | ------------- | ------------------------------------------------------------ | +| `client_id` | Да | Your registered client ID | +| `response_type` | Да | Must be `code` | +| `redirect_uri` | Да | Must match a registered redirect URI | +| `scope` | Нет | Space-separated scopes (defaults to `api`) | +| `состояние` | Рекомендуется | Random string to prevent CSRF attacks | +| `code_challenge` | Рекомендуется | PKCE challenge (SHA-256 hash of verifier, base64url-encoded) | +| `code_challenge_method` | Рекомендуется | Must be `S256` when using PKCE | + +The user sees a consent screen and approves or denies access. + +### 2. Handle the callback + +After authorization, Twenty redirects back to your `redirect_uri`: + +``` +https://myapp.com/callback?code=AUTH_CODE&state=random_state_value +``` + +Verify that `state` matches what you sent. + +### 3. Exchange the code for tokens + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=authorization_code& +code=AUTH_CODE& +redirect_uri=https://myapp.com/callback& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +code_verifier=YOUR_PKCE_VERIFIER +``` + +**Response:** + +```json +{ + "access_token": "eyJhbG...", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "dGhpcyBpcyBh..." +} +``` + +### 4. Use the access token + +```bash +GET /rest/companies +Authorization: Bearer ACCESS_TOKEN +``` + +### 5. Refresh when expired + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=refresh_token& +refresh_token=YOUR_REFRESH_TOKEN& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET +``` + +## Client Credentials Flow + +For server-to-server integrations with no user interaction: + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=client_credentials& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +scope=api +``` + +The returned token has workspace-level access, not tied to any specific user. + +## Server Discovery + +Twenty publishes its OAuth configuration at a standard discovery endpoint: + +``` +GET /.well-known/oauth-authorization-server +``` + +This returns all endpoints, supported grant types, scopes, and capabilities — useful for building generic OAuth clients. + +## API Endpoints Summary + +| Конечная точка | Назначение | +| ----------------------------------------- | --------------------------- | +| `/.well-known/oauth-authorization-server` | Server metadata discovery | +| `/oauth/register` | Dynamic client registration | +| `/oauth/authorize` | User authorization | +| `/oauth/token` | Token exchange and refresh | + +| Среда | Базовый URL | +| --------------------------- | ------------------------ | +| **Облако** | `https://api.twenty.com` | +| **Самостоятельный хостинг** | `https://{your-domain}` | + +## OAuth vs API Keys + +| | API ключи | OAuth | +| ---------------------------- | ----------------------- | -------------------------------------- | +| **Настройка** | Generate in Settings | Register a client, implement flow | +| **User context** | None (workspace-level) | Specific user's permissions | +| **Лучше всего подходит для** | Scripts, internal tools | External apps, multi-user integrations | +| **Token rotation** | Вручную | Automatic via refresh tokens | +| **Scoped access** | Full API access | Granular via scopes | diff --git a/packages/twenty-docs/l/ru/developers/extend/webhooks.mdx b/packages/twenty-docs/l/ru/developers/extend/webhooks.mdx index 5312a3c9e48..306fff4481b 100644 --- a/packages/twenty-docs/l/ru/developers/extend/webhooks.mdx +++ b/packages/twenty-docs/l/ru/developers/extend/webhooks.mdx @@ -1,11 +1,12 @@ --- title: Вебхуки -description: Получайте уведомления в реальном времени, когда в вашей CRM происходят события. +icon: satellite-dish +description: Get notified when records change — HTTP POST to your endpoint on every create, update, or delete. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -Вебхуки отправляют данные в ваши системы в реальном времени при возникновении событий в Twenty — опрос не требуется. Используйте их, чтобы поддерживать синхронизацию с внешними системами, запускать автоматизации или отправлять оповещения. +Twenty sends an HTTP POST to your URL whenever a record is created, updated, or deleted. All object types are covered, including custom objects. ## Создать вебхук diff --git a/packages/twenty-docs/l/ru/developers/introduction.mdx b/packages/twenty-docs/l/ru/developers/introduction.mdx index 95ef9178629..6ae5b293005 100644 --- a/packages/twenty-docs/l/ru/developers/introduction.mdx +++ b/packages/twenty-docs/l/ru/developers/introduction.mdx @@ -1,23 +1,28 @@ --- -title: Начало работы -description: Добро пожаловать в документацию для разработчиков Twenty — ваш ресурс для расширения, самостоятельного развертывания и внесения вклада в Twenty. +title: Разработчики +description: Build apps, use the API, self-host, or contribute to the codebase. --- import { CardTitle } from "/snippets/card-title.mdx" - - Расширяйте - Создавайте интеграции с API, вебхуками и пользовательскими приложениями. + + Apps + Extend Twenty with custom objects, server-side logic, UI components, and AI agents — all as TypeScript packages. - - Развертывайте у себя - Развертывайте и управляйте Twenty в собственной инфраструктуре. + + API + REST and GraphQL APIs, webhooks, and OAuth. - - Вносите вклад - Присоединяйтесь к нашему сообществу с открытым исходным кодом и вносите вклад в Twenty. + + Self-Host + Run Twenty on your own infrastructure. + + + + Contribute + Set up the monorepo locally and submit PRs. diff --git a/packages/twenty-docs/l/ru/developers/self-host/capabilities/cloud-providers.mdx b/packages/twenty-docs/l/ru/developers/self-host/capabilities/cloud-providers.mdx index 6a18b004301..41ed5ea47a8 100644 --- a/packages/twenty-docs/l/ru/developers/self-host/capabilities/cloud-providers.mdx +++ b/packages/twenty-docs/l/ru/developers/self-host/capabilities/cloud-providers.mdx @@ -1,5 +1,6 @@ --- title: Другие методы +icon: cloud --- diff --git a/packages/twenty-docs/l/ru/developers/self-host/capabilities/docker-compose.mdx b/packages/twenty-docs/l/ru/developers/self-host/capabilities/docker-compose.mdx index d1e74a87c2f..a1c507004e1 100644 --- a/packages/twenty-docs/l/ru/developers/self-host/capabilities/docker-compose.mdx +++ b/packages/twenty-docs/l/ru/developers/self-host/capabilities/docker-compose.mdx @@ -1,5 +1,6 @@ --- -title: В один клик с Docker Compose +title: Docker Compose +icon: docker --- diff --git a/packages/twenty-docs/l/ru/developers/self-host/capabilities/setup.mdx b/packages/twenty-docs/l/ru/developers/self-host/capabilities/setup.mdx index 24f9e9b75d4..4ccc2712163 100644 --- a/packages/twenty-docs/l/ru/developers/self-host/capabilities/setup.mdx +++ b/packages/twenty-docs/l/ru/developers/self-host/capabilities/setup.mdx @@ -1,5 +1,6 @@ --- title: Настройка +icon: gear --- # Управление конфигурацией diff --git a/packages/twenty-docs/l/ru/developers/self-host/capabilities/troubleshooting.mdx b/packages/twenty-docs/l/ru/developers/self-host/capabilities/troubleshooting.mdx index 98fc4c2c7cb..26f531d6d52 100644 --- a/packages/twenty-docs/l/ru/developers/self-host/capabilities/troubleshooting.mdx +++ b/packages/twenty-docs/l/ru/developers/self-host/capabilities/troubleshooting.mdx @@ -1,5 +1,6 @@ --- title: Устранение неполадок +icon: wrench --- ## Устранение неполадок diff --git a/packages/twenty-docs/l/ru/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/ru/developers/self-host/capabilities/upgrade-guide.mdx index 69d0b267a6c..e5077141f2e 100644 --- a/packages/twenty-docs/l/ru/developers/self-host/capabilities/upgrade-guide.mdx +++ b/packages/twenty-docs/l/ru/developers/self-host/capabilities/upgrade-guide.mdx @@ -1,5 +1,6 @@ --- title: Руководство по обновлению +icon: arrow-up-right-dots --- ## Общие рекомендации @@ -16,366 +17,14 @@ title: Руководство по обновлению 3. Верните Twenty в сеть с `docker compose up -d`. -Если вы хотите обновить свою инстанцию на несколько версий, например с v0.33.0 до v0.35.0, вам потребуется сделать это поэтапно, сначала с v0.33.0 до v0.34.0, затем с v0.34.0 до v0.35.0. - **Убедитесь, что после каждой обновленной версии у вас есть не поврежденная резервная копия.** ## Специфические для версии шаги обновления -## v1.0 +## After v1.21 -Привет, Twenty v1.0! 🎉 +We know support sequential upgrades. You don't need to go through each version one by one. -## v0.60 +## Before v1.21 -### Улучшение производительности - -Все взаимодействия с метаданными API оптимизированы для повышения производительности, особенно для манипуляции метаданными объектов и операций создания рабочих пространств. - -Мы обновили стратегию кэширования, чтобы приоритетом были попадания кэша перед запросами к базе данных, что значительно улучшило производительность операций с метаданными API. - -Если после обновления у вас возникнут проблемы с запуском, возможно, потребуется очистить ваш кэш, чтобы синхронизировать его с последними изменениями. Запустите эту команду в вашем контейнере twenty-server: - -```bash -yarn command:prod cache:flush -``` - -### v0.55 - -Обновите вашу инстанцию Twenty для использования изображения v0.55 - -Вам больше не нужно запускать никакие команды, новое изображение автоматически выполнит все необходимые миграции. - -### Ошибка `User does not have permission` - -Если после обновления вы столкнетесь с ошибками авторизации в большинстве запросов, возможно, потребуется очистить кэш для пересчета последних прав доступа. - -В вашем контейнере `twenty-server`, запустите: - -```bash -yarn command:prod cache:flush -``` - -Эта проблема специфична для данной версии Twenty и не должна требоваться для будущих обновлений. - -### v0.54 - -Начиная с версии `0.53`, ручные действия не требуются. - -#### Отказ от схемы метаданных - -Мы объединили схему `metadata` в `core`, чтобы упростить извлечение данных из `TypeORM`. -Мы объединили этап команды `migrate` в команду `upgrade`. Мы не рекомендуем запускать `migrate` вручную в ваших контейнерах серверов/рабочих. - -### Начиная с v0.53 - -Начиная с `0.53`, обновление программно выполняется внутри `DockerFile`, это означает, что отныне вам больше не нужно запускать команды вручную. - -Убедитесь, что вы обновляете свою инстанцию последовательно, без пропуска основной версии (например, `0.43.3` на `0.44.0` допускается, но `0.43.1` на `0.45.0` нет), иначе это может привести к десинхронизации версии рабочего пространства, что может вызвать ошибку во время выполнения и отсутствовать функциональность. - -Чтобы проверить, правильно ли было мигрировано рабочее пространство, вы можете просмотреть его версию в базе данных в таблице `core.workspace`. - -Она всегда должна находиться в диапазоне текущей версии вашего инстанции Twenty `major.minor`, вы можете просмотреть версию инстанции в админ-панели (на `/settings/admin-panel`, доступна, если свойство `canAccessFullAdminPanel` вашего пользователя установлено на true в базе данных) или запустив `echo $APP_VERSION` в вашем контейнере `twenty-server`. - -Чтобы исправить десинхронизированную версию рабочего пространства, вам потребуется обновить его с соответствующей версии Twenty, следуя последовательному руководству по обновлению и так далее, пока он не достигнет желаемой версии. - -#### Удаление `auditLog` - -Мы удалили стандартный объект auditLog, что означает, что размер вашей резервной копии может значительно уменьшиться после этой миграции. - -### v0.51 до v0.52 - -Обновите вашу инстанцию Twenty для использования изображения v0.52 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### У меня рабочее пространство заблокировано на версии между `0.52.0` и `0.52.6`. - -К сожалению, `0.52.0` и `0.52.6` были полностью удалены из dockerHub. -Вам потребуется вручную обновить версию рабочего пространства до `0.51.0` в базе данных и обновить с использованием версии twenty `0.52.11`, следуя её руководству по обновлению. - -### v0.50 до v0.51 - -Обновите вашу инстанцию Twenty для использования изображения v0.51 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.44.0 до v0.50.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.50.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### Модификация в docker-compose.yml - -Эта версия включает модификацию `docker-compose.yml`, чтобы предоставить сервису `worker` доступ к тому `server-local-data`. -Обновите ваш локальный `docker-compose.yml` с [v0.50.0 docker-compose.yml](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml) - -### v0.43.0 до v0.44.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.44.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.42.0 до v0.43.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.43.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -В этой версии мы также переключились на изображение postgres:16 в docker-compose.yml. - -#### (Вариант 1) Миграция базы данных - -Сохранение существующего изображения postgres-spilo приемлемо, но вам потребуется зафиксировать версию в вашем docker-compose.yml на 0.43.0. - -#### (Вариант 2) Миграция базы данных - -Если вы хотите мигрировать вашу базу данных на новое изображение postgres:16, следуйте этим шагам: - -1. Сделайте дамп вашей базы данных из старого контейнера postgres-spilo - -``` -docker exec -it twenty-db-1 sh -pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql -exit -docker cp twenty-db-1:/home/postgres/databases_backup.sql . -``` - -Убедитесь, что ваш файл дампа не пустой. - -2. Обновите ваш файл docker-compose.yml с использованием изображения postgres:16, как в файле [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml). - -3. Восстановите базу данных в новый контейнер postgres:16. - -``` -docker cp databases_backup.sql twenty-db-1:/databases_backup.sql -docker exec -it twenty-db-1 sh -psql -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} -f databases_backup.sql -exit -``` - -### v0.41.0 до v0.42.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.42.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.42 -``` - -**Переменные окружения** - -* Удалено: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT` -* Добавлено: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED` - -### v0.40.0 до v0.41.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.41.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.41 -``` - -**Переменные окружения** - -* Удалено: `AUTH_MICROSOFT_TENANT_ID` - -### v0.35.0 до v0.40.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.40.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.40 -``` - -**Переменные окружения** - -* Добавлено: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL` - -### v0.34.0 до v0.35.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.35.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.35 -``` - -Команда `yarn database:migrate:prod` применяет миграции к структуре базы данных (схемы core и metadata) -Команда `yarn command:prod upgrade-0.35` заботится о миграции данных всех рабочих пространств. - -**Переменные окружения** - -* Мы заменили `ENABLE_DB_MIGRATIONS` на `DISABLE_DB_MIGRATIONS` (значение по умолчанию теперь `false`, вероятно, вам не потребуется ничего устанавливать) - -### v0.33.0 до v0.34.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.34.0 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.34 -``` - -Команда `yarn database:migrate:prod` применяет миграции к структуре базы данных (схемы core и metadata) -Команда `yarn command:prod upgrade-0.34` заботится о миграции данных всех рабочих пространств. - -**Переменные окружения** - -* Удалено: `FRONT_BASE_URL` -* Добавлено: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT` - -Мы обновили способ обработки URL frontend. -Теперь вы можете установить URL frontend, используя переменные `FRONT_DOMAIN`, `FRONT_PROTOCOL` и `FRONT_PORT`. -Если FRONT_DOMAIN не установлен, URL frontend будет зависеть от `SERVER_URL`. - -### v0.32.0 до v0.33.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.33.0 - -``` -yarn command:prod cache:flush -yarn database:migrate:prod -yarn command:prod upgrade-0.33 -``` - -Команда `yarn command:prod cache:flush` очистит кэш Redis. -Команда `yarn database:migrate:prod` применяет миграции к структуре базы данных (схемы core и metadata) -Команда `yarn command:prod upgrade-0.33` заботится о миграции данных всех рабочих пространств. - -Начиная с этой версии, образ twenty-postgres для DB становится устаревшим, и вместо него используется twenty-postgres-spilo. -Если вы хотите продолжать использовать изображение twenty-postgres, просто замените `twentycrm/twenty-postgres:${TAG}` на `twentycrm/twenty-postgres` в файле docker-compose.yml. - -### v0.31.0 до v0.32.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.32.0 - -**Миграция схемы и данных** - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.32 -``` - -Команда `yarn database:migrate:prod` применяет миграции к структуре базы данных (схемы core и metadata) -Команда `yarn command:prod upgrade-0.32` заботится о миграции данных всех рабочих пространств. - -**Переменные окружения** - -Мы обновили способ обработки соединения с Redis. - -* Удалено: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD` -* Добавлено: `REDIS_URL` - -Обновите ваш файл `.env` для использования новой переменной `REDIS_URL` вместо индивидуальных параметров подключения Redis. - -Мы также упростили способ обработки JWT токенов. - -* Удалено: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET` -* Добавлено: `APP_SECRET` - -Обновите ваш файл `.env` для использования новой переменной `APP_SECRET` вместо индивидуальных секрета токенов (вы можете использовать прежний секрет или сгенерировать новую случайную строку). - -**Подключенный Аккаунт** - -Если вы используете подключенный аккаунт для синхронизации ваших электронных писем и календарей Google, вам потребуется активировать [API людей](https://developers.google.com/people) на вашей консоли администратора Google. - -### v0.30.0 до v0.31.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.31.0 - -**Миграция схемы и данных:** - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.31 -``` - -Команда `yarn database:migrate:prod` применяет миграции к структуре базы данных (схемы core и metadata) -Команда `yarn command:prod upgrade-0.31` заботится о миграции данных всех рабочих пространств. - -### v0.24.0 до v0.30.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.30.0 - -**Критически важное изменение**: -Чтобы повысить производительность, Twenty теперь требует настроить кэш Redis. Мы обновили [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) для этого. -Убедитесь, что вы обновили свою конфигурацию и обновили переменные окружения соответственно: - -``` -REDIS_HOST={your-redis-host} -REDIS_PORT={your-redis-port} -CACHE_STORAGE_TYPE=redis -``` - -**Миграция схемы и данных:** - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.30 -``` - -Команда `yarn database:migrate:prod` применяет миграции к структуре базы данных (схемы core и metadata) -Команда `yarn command:prod upgrade-0.30` заботится о миграции данных всех рабочих пространств. - -### v0.23.0 до v0.24.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.24.0 - -Выполните следующие команды: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.24 -``` - -Команда `yarn database:migrate:prod` применяет миграции к структуре базы данных (схемы core и metadata) -Команда `yarn command:prod upgrade-0.24` заботится о миграции данных всех рабочих пространств. - -### v0.22.0 до v0.23.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.23.0 - -Выполните следующие команды: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.23 -``` - -Команда `yarn database:migrate:prod` применяет миграции к Базе данных. -Команда `yarn command:prod upgrade-0.23` заботится о миграции данных, включая перенос активностей в задачи/заметки. - -### v0.21.0 до v0.22.0 - -Обновите вашу инстанцию Twenty для использования изображения v0.22.0 - -Выполните следующие команды: - -``` -yarn database:migrate:prod -yarn command:prod workspace:sync-metadata -f -yarn command:prod upgrade-0.22 -``` - -Команда `yarn database:migrate:prod` применяет миграции к Базе данных. -Команда `yarn command:prod workspace:sync-metadata -f` синхронизирует определение стандартных объектов с таблицами метаданных и применит необходимые миграции к существующим рабочим пространствам. -Команда `yarn command:prod upgrade-0.22` выполнит преобразование данных для адаптации к новым параметрам по умолчанию объекта defaultRequestInstrumentationOptions. +Make sure to go through every major tagged version when upgrading (upgrade v1.6.x to v.7.y, then v.7.y to v.8.z, etc.). diff --git a/packages/twenty-docs/l/ru/navigation.json b/packages/twenty-docs/l/ru/navigation.json index 143ae3fd099..68702073f65 100644 --- a/packages/twenty-docs/l/ru/navigation.json +++ b/packages/twenty-docs/l/ru/navigation.json @@ -1,24 +1,27 @@ { "tabs": { + "gettingStarted": { + "label": "Начало работы", + "groups": { + "welcome": { + "label": "Welcome" + }, + "coreConcepts": { + "label": "Core Concepts" + } + } + }, "userGuide": { "label": "Руководство пользователя", "groups": { - "discoverTwenty": { - "label": "Откройте Twenty", - "groups": { - "gettingStartedCapabilities": { - "label": "Возможности" - }, - "gettingStartedHowTos": { - "label": "Инструкции" - } - } + "userGuideOverview": { + "label": "Обзор" }, "dataModel": { "label": "Модель данных", "groups": { - "dataModelCapabilities": { - "label": "Возможности" + "dataModelReference": { + "label": "Reference" }, "dataModelHowTos": { "label": "Инструкции" @@ -28,8 +31,8 @@ "dataMigration": { "label": "Миграция данных", "groups": { - "dataMigrationCapabilities": { - "label": "Возможности" + "dataMigrationReference": { + "label": "Reference" }, "dataMigrationHowTos": { "label": "Инструкции" @@ -39,8 +42,8 @@ "calendarEmails": { "label": "Календарь и электронная почта", "groups": { - "calendarEmailsCapabilities": { - "label": "Возможности" + "calendarEmailsReference": { + "label": "Reference" }, "calendarEmailsHowTos": { "label": "Инструкции" @@ -50,8 +53,8 @@ "workflows": { "label": "Рабочие процессы", "groups": { - "workflowsCapabilities": { - "label": "Возможности" + "workflowsReference": { + "label": "Reference" }, "workflowsHowTos": { "label": "Инструкции", @@ -75,21 +78,26 @@ "ai": { "label": "ИИ", "groups": { - "aiCapabilities": { - "label": "Возможности" + "aiReference": { + "label": "Reference" }, "aiHowTos": { "label": "Инструкции" } } }, - "viewsPipelines": { - "label": "Представления и воронки", + "layout": { + "label": "Макет", "groups": { - "viewsPipelinesCapabilities": { - "label": "Возможности" + "layoutReference": { + "label": "Reference", + "groups": { + "layoutViews": { + "label": "Представления" + } + } }, - "viewsPipelinesHowTos": { + "layoutHowTos": { "label": "Инструкции" } } @@ -97,8 +105,8 @@ "dashboards": { "label": "Панели управления", "groups": { - "dashboardsCapabilities": { - "label": "Возможности" + "dashboardsReference": { + "label": "Reference" }, "dashboardsHowTos": { "label": "Инструкции" @@ -108,8 +116,8 @@ "permissionsAccess": { "label": "Разрешения и доступ", "groups": { - "permissionsAccessCapabilities": { - "label": "Возможности" + "permissionsAccessReference": { + "label": "Reference" }, "permissionsAccessHowTos": { "label": "Инструкции" @@ -119,8 +127,8 @@ "billing": { "label": "Биллинг", "groups": { - "billingCapabilities": { - "label": "Возможности" + "billingReference": { + "label": "Reference" }, "billingHowTos": { "label": "Инструкции" @@ -130,8 +138,8 @@ "settings": { "label": "Настройки", "groups": { - "settingsCapabilities": { - "label": "Возможности" + "settingsReference": { + "label": "Reference" }, "settingsHowTos": { "label": "Инструкции" @@ -143,59 +151,20 @@ "developers": { "label": "Разработчики", "groups": { - "developersGroup": { - "label": "Разработчики" + "developersOverview": { + "label": "Обзор" }, - "extend": { - "label": "Расширяйте", - "groups": { - "apps": { - "label": "Приложения" - } - } + "apps": { + "label": "Приложения" + }, + "api": { + "label": "API" }, "selfHost": { - "label": "Самостоятельный хостинг", - "groups": { - "selfHostCapabilities": { - "label": "Возможности" - } - } + "label": "Самостоятельный хостинг" }, "contribute": { - "label": "Внести вклад", - "groups": { - "contributeCapabilities": { - "label": "Возможности", - "groups": { - "frontendDevelopment": { - "label": "Разработка интерфейса", - "groups": { - "twentyUi": { - "label": "Twenty UI", - "groups": { - "display": { - "label": "Отображение" - }, - "feedback": { - "label": "Обратная связь" - }, - "input": { - "label": "Ввод" - }, - "navigation": { - "label": "Навигация" - } - } - } - } - }, - "backendDevelopment": { - "label": "Разработка серверной части" - } - } - } - } + "label": "Внести вклад" } } } diff --git a/packages/twenty-docs/l/ru/twenty-ui/display/app-tooltip.mdx b/packages/twenty-docs/l/ru/twenty-ui/display/app-tooltip.mdx index 9647c3e0d35..8f09a1ebb96 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/display/app-tooltip.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/display/app-tooltip.mdx @@ -1,5 +1,6 @@ --- title: Подсказка приложения +icon: сообщение --- diff --git a/packages/twenty-docs/l/ru/twenty-ui/display/checkmark.mdx b/packages/twenty-docs/l/ru/twenty-ui/display/checkmark.mdx index 7ed039222a6..719584595e0 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/display/checkmark.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/display/checkmark.mdx @@ -1,5 +1,6 @@ --- title: Галочка +icon: circle-check --- diff --git a/packages/twenty-docs/l/ru/twenty-ui/display/icons.mdx b/packages/twenty-docs/l/ru/twenty-ui/display/icons.mdx index 4f8e3df6a41..954978b5e7f 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/display/icons.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/display/icons.mdx @@ -1,5 +1,6 @@ --- title: Иконки +icon: иконки --- diff --git a/packages/twenty-docs/l/ru/twenty-ui/display/soon-pill.mdx b/packages/twenty-docs/l/ru/twenty-ui/display/soon-pill.mdx index 11309e50eff..517c4940cc1 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/display/soon-pill.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/display/soon-pill.mdx @@ -2,7 +2,6 @@ title: Плашка «Скоро» --- - Небольшой значок или «таблетка» для обозначения того, что скоро будет. ```jsx diff --git a/packages/twenty-docs/l/ru/twenty-ui/display/tag.mdx b/packages/twenty-docs/l/ru/twenty-ui/display/tag.mdx index ea5ab2a8070..e7cd06940c0 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/display/tag.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/display/tag.mdx @@ -1,8 +1,8 @@ --- title: Тег +icon: тег --- - Компонент для визуального категорирования или маркировки контента. diff --git a/packages/twenty-docs/l/ru/twenty-ui/input/buttons.mdx b/packages/twenty-docs/l/ru/twenty-ui/input/buttons.mdx index 9cb722a53cc..e57193e5844 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/input/buttons.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/input/buttons.mdx @@ -1,5 +1,6 @@ --- title: Кнопки +icon: hand-pointer --- diff --git a/packages/twenty-docs/l/ru/twenty-ui/input/checkbox.mdx b/packages/twenty-docs/l/ru/twenty-ui/input/checkbox.mdx index 01c17a92e52..b778b9f7843 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/input/checkbox.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/input/checkbox.mdx @@ -1,5 +1,6 @@ --- title: Флажок +icon: square-check --- diff --git a/packages/twenty-docs/l/ru/twenty-ui/input/color-scheme.mdx b/packages/twenty-docs/l/ru/twenty-ui/input/color-scheme.mdx index 97966c55ee9..2a6efcaf72c 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/input/color-scheme.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/input/color-scheme.mdx @@ -1,5 +1,6 @@ --- title: Цветовая Схема +icon: палитра --- diff --git a/packages/twenty-docs/l/ru/twenty-ui/input/radio.mdx b/packages/twenty-docs/l/ru/twenty-ui/input/radio.mdx index f70a667d0a6..654f37b9980 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/input/radio.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/input/radio.mdx @@ -1,5 +1,6 @@ --- title: Радио +icon: circle-dot --- diff --git a/packages/twenty-docs/l/ru/twenty-ui/input/toggle.mdx b/packages/twenty-docs/l/ru/twenty-ui/input/toggle.mdx index 2d16e206fbe..cae32c0fe0a 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/input/toggle.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/input/toggle.mdx @@ -1,8 +1,8 @@ --- title: Переключатель +icon: toggle-on --- - diff --git a/packages/twenty-docs/l/ru/twenty-ui/introduction.mdx b/packages/twenty-docs/l/ru/twenty-ui/introduction.mdx index 98a601d684d..02d941e4ff0 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/introduction.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/introduction.mdx @@ -1,5 +1,6 @@ --- title: Обзор +icon: палитра description: Библиотека компонентов для Twenty CRM --- diff --git a/packages/twenty-docs/l/ru/twenty-ui/navigation.mdx b/packages/twenty-docs/l/ru/twenty-ui/navigation.mdx index a39343bf590..72da715a067 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/navigation.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/navigation.mdx @@ -1,5 +1,6 @@ --- title: Навигация +icon: compass --- diff --git a/packages/twenty-docs/l/ru/twenty-ui/navigation/links.mdx b/packages/twenty-docs/l/ru/twenty-ui/navigation/links.mdx index 005979e5ecd..25e095d9d7a 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/navigation/links.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/navigation/links.mdx @@ -1,5 +1,6 @@ --- title: Ссылки +icon: ссылка --- diff --git a/packages/twenty-docs/l/ru/twenty-ui/navigation/menu-item.mdx b/packages/twenty-docs/l/ru/twenty-ui/navigation/menu-item.mdx index 6fb321ab198..0627565676a 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/navigation/menu-item.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/navigation/menu-item.mdx @@ -1,8 +1,8 @@ --- title: Пункт меню +icon: bars --- - Универсальный пункт меню, предназначенный для использования в меню или списке навигации. diff --git a/packages/twenty-docs/l/ru/twenty-ui/navigation/navigation-bar.mdx b/packages/twenty-docs/l/ru/twenty-ui/navigation/navigation-bar.mdx index 80f5f0d45d3..22634698f1e 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/navigation/navigation-bar.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/navigation/navigation-bar.mdx @@ -1,8 +1,8 @@ --- title: Панель навигации +icon: bars --- - Отображает панель навигации, содержащую несколько компонентов `NavigationBarItem`. diff --git a/packages/twenty-docs/l/ru/twenty-ui/progress-bar.mdx b/packages/twenty-docs/l/ru/twenty-ui/progress-bar.mdx index ddc6a88ae1a..e531f313f02 100644 --- a/packages/twenty-docs/l/ru/twenty-ui/progress-bar.mdx +++ b/packages/twenty-docs/l/ru/twenty-ui/progress-bar.mdx @@ -2,7 +2,6 @@ title: Обратная связь --- - Указывает на прогресс или обратный отсчет и перемещается справа налево. diff --git a/packages/twenty-docs/l/ru/user-guide/billing/overview.mdx b/packages/twenty-docs/l/ru/user-guide/billing/overview.mdx index ca4945edafc..a3d3286bb2a 100644 --- a/packages/twenty-docs/l/ru/user-guide/billing/overview.mdx +++ b/packages/twenty-docs/l/ru/user-guide/billing/overview.mdx @@ -3,7 +3,6 @@ title: Биллинг description: Разберитесь в тарифах Twenty и управляйте своей подпиской. --- - Twenty предлагает гибкие тарифные планы, соответствующие потребностям вашей команды. Управляйте подпиской, отслеживайте кредиты рабочих процессов и получайте доступ к счетам — всё в **Настройки → Выставление счетов**. ## Что в этом разделе diff --git a/packages/twenty-docs/l/ru/user-guide/calendar-emails/overview.mdx b/packages/twenty-docs/l/ru/user-guide/calendar-emails/overview.mdx index a226126ba34..d638b34acd4 100644 --- a/packages/twenty-docs/l/ru/user-guide/calendar-emails/overview.mdx +++ b/packages/twenty-docs/l/ru/user-guide/calendar-emails/overview.mdx @@ -3,7 +3,6 @@ title: Календарь и электронная почта description: Подключите свои учетные записи электронной почты и календаря к Twenty. --- - ## Параметры подключения ### Учетная запись Google (Gmail и Календарь Google) diff --git a/packages/twenty-docs/l/ru/user-guide/dashboards/overview.mdx b/packages/twenty-docs/l/ru/user-guide/dashboards/overview.mdx index 34e03fc3131..b74e3d7de09 100644 --- a/packages/twenty-docs/l/ru/user-guide/dashboards/overview.mdx +++ b/packages/twenty-docs/l/ru/user-guide/dashboards/overview.mdx @@ -3,7 +3,6 @@ title: Панели управления description: Изучите основы отчетности и панелей мониторинга в Twenty. --- - Дашборды сейчас в бета-версии. Активируйте их в разделе **Настройки → Обновления → Ранний доступ**. diff --git a/packages/twenty-docs/l/ru/user-guide/data-migration/overview.mdx b/packages/twenty-docs/l/ru/user-guide/data-migration/overview.mdx index a20b436c2a7..c4f4616944d 100644 --- a/packages/twenty-docs/l/ru/user-guide/data-migration/overview.mdx +++ b/packages/twenty-docs/l/ru/user-guide/data-migration/overview.mdx @@ -5,7 +5,6 @@ description: Импортируйте и экспортируйте данные import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## Способы импорта Twenty поддерживает два основных способа импорта данных: diff --git a/packages/twenty-docs/l/ru/user-guide/data-model/overview.mdx b/packages/twenty-docs/l/ru/user-guide/data-model/overview.mdx index 72372e90014..70c270a6672 100644 --- a/packages/twenty-docs/l/ru/user-guide/data-model/overview.mdx +++ b/packages/twenty-docs/l/ru/user-guide/data-model/overview.mdx @@ -3,7 +3,6 @@ title: Модель данных description: Узнайте, что такое модель данных и как спроектировать её под ваш бизнес. --- - ## Что такое модель данных? Модель данных — это структура, определяющая, как информация организована в вашей CRM. Представьте это как **чертёж** ваших данных о клиентах — вы проектируете его один раз, а затем заполняете фактическими данными. diff --git a/packages/twenty-docs/l/ru/user-guide/introduction.mdx b/packages/twenty-docs/l/ru/user-guide/introduction.mdx index 0aaedf0d423..9eaedb1136d 100644 --- a/packages/twenty-docs/l/ru/user-guide/introduction.mdx +++ b/packages/twenty-docs/l/ru/user-guide/introduction.mdx @@ -1,16 +1,11 @@ --- -title: Откройте Twenty +title: Руководство пользователя description: Добро пожаловать в Руководство пользователя Twenty — ваш ресурс по расширенным настройкам и лучшим практикам. --- import { CardTitle } from "/snippets/card-title.mdx" - - Откройте Twenty - Узнайте, что такое Twenty и как она может помочь вашему бизнесу. - - Модель данных Настройте модель данных под процессы вашего бизнеса. @@ -36,9 +31,9 @@ import { CardTitle } from "/snippets/card-title.mdx" Усильте команду агентами ИИ. - - Представления и воронки - Организуйте данные с помощью рабочих представлений и воронок. + + Layout + Navigation, views, and record page customization. diff --git a/packages/twenty-docs/l/ru/user-guide/layout/capabilities/navigation.mdx b/packages/twenty-docs/l/ru/user-guide/layout/capabilities/navigation.mdx new file mode 100644 index 00000000000..f73a71a86b6 --- /dev/null +++ b/packages/twenty-docs/l/ru/user-guide/layout/capabilities/navigation.mdx @@ -0,0 +1,32 @@ +--- +title: Навигация +description: Customize the left sidebar to match how your team works. +--- + +The left sidebar is your primary way to move around Twenty. It's fully customizable — you can reorganize it to match your workflow without touching any settings page. + +## Reordering items + +Drag and drop any item in the sidebar to change its position. The order is saved per user, so each team member can arrange their own sidebar. + +## Папки + +Group related items into folders. For example, you might create a "Sales" folder containing your pipeline views, a "Support" folder for tickets, or an "Operations" folder for internal objects. + +To create a folder, right-click in the sidebar or use the `+` button. + +## Hiding objects + +Objects you don't use can be hidden from the sidebar. They're not deleted — they're just out of the way. You can show them again anytime from Settings > Data Model. + +## Избранное + +Pin views, records, or searches to the Favorites section at the top of the sidebar for one-click access. Favorites are personal — each user manages their own. + +## Custom links + +Add links to external tools directly in the sidebar. Useful for linking to your wiki, dashboards in other tools, or any URL your team uses regularly. + +## Command menu + +Press `Cmd+K` (or `Ctrl+K`) to open the command menu — a quick-access search bar for jumping to any record, view, or action without navigating the sidebar. diff --git a/packages/twenty-docs/l/ru/user-guide/layout/capabilities/record-pages.mdx b/packages/twenty-docs/l/ru/user-guide/layout/capabilities/record-pages.mdx new file mode 100644 index 00000000000..292a2fa217b --- /dev/null +++ b/packages/twenty-docs/l/ru/user-guide/layout/capabilities/record-pages.mdx @@ -0,0 +1,51 @@ +--- +title: Страницы записей +description: Настройте макет отдельных страниц сведений о записях с вкладками и виджетами. +--- + +Когда вы открываете запись в Twenty, страница сведений состоит из **вкладок** и **виджетов**. Оба полностью настраиваются для каждого типа объекта. + +## Вкладки + +Каждая страница записи может иметь несколько вкладок — аналогично вкладкам в браузере. Используйте их, чтобы упорядочить различные аспекты записи. Например, у записи «Компания» могут быть вкладки «Обзор», «Коммуникации», «Задачи» и «Файлы». + +Вы можете: + +* Добавляйте и удаляйте вкладки +* Переименовывайте вкладки +* Меняйте порядок вкладок перетаскиванием +* Установите, какая вкладка отображается по умолчанию + +## Виджеты + +Виджеты — это строительные блоки внутри каждой вкладки. Доступные типы виджетов: + +| Виджет | Что отображает | +| ------------------------- | --------------------------------------------------------- | +| **Поля** | Поля записи, сгруппированные или по отдельности | +| **Связанные записи** | Таблица записей, связанных через связь | +| **Электронные письма** | История электронных писем из подключенных учетных записей | +| **Календарь** | События календаря, связанные с записью | +| **Временная шкала** | История активности и событий | +| **Задачи** | Связанные задачи | +| **Заметки** | Заметки с форматированным текстом | +| **Файлы** | Файловые вложения | +| **Диаграммы** | Визуальные данные из связанных записей | +| **iFrame** | Встроенный внешний контент | +| **Форматированный текст** | Статический контент или описания | + +## Настройка страницы записи + +1. Откройте любую запись +2. Нажмите `Cmd+K` и найдите "Изменить макет страницы записи" +3. Теперь вы в режиме настройки: + * **Добавляйте виджеты** из списка виджетов + * **Перетаскивайте виджеты**, чтобы изменить их положение на сетке + * **Изменяйте размер виджетов**, перетаскивая их границы + * **Настраивайте поля**, отображаемые в каждом виджете + * **Управляйте вкладками** — добавляйте, удаляйте, переименовывайте, меняйте порядок +4. Сохраните изменения — они применятся ко всем записям этого типа объекта + +## Видимость полей + +В виджете «Поля» вы можете контролировать, какие поля видны и в каком порядке. Это позволяет создавать сфокусированные макеты — например, показывать только самые важные поля на вкладке «Обзор», а подробные поля размещать на отдельной вкладке. diff --git a/packages/twenty-docs/l/ru/user-guide/layout/overview.mdx b/packages/twenty-docs/l/ru/user-guide/layout/overview.mdx new file mode 100644 index 00000000000..fac0c26c924 --- /dev/null +++ b/packages/twenty-docs/l/ru/user-guide/layout/overview.mdx @@ -0,0 +1,45 @@ +--- +title: Макет +description: Customize how you navigate, browse, and view records in Twenty. +--- + +Twenty's layout is customizable at three levels: how you navigate the app, how you browse lists of records, and what you see when you open an individual record. + +## Навигация + +The left sidebar is fully customizable. Вы можете: + +* **Reorder items** by dragging and dropping +* **Create folders** to group related objects and views +* **Hide objects** you don't use +* **Add custom links** to external tools +* **Pin favorites** for quick access to views, records, or searches + +[Navigation reference →](/l/ru/user-guide/layout/capabilities/navigation) + +## Представления + +Views control how lists of records are displayed. Twenty supports three view types: + +| Представление | Best for | +| ------------- | ---------------------------------------------------------------------- | +| **Table** | Working with many records at once — spreadsheet-style rows and columns | +| **Kanban** | Pipeline tracking — drag-and-drop cards organized by stage | +| **Calendar** | Time-based planning — records plotted by a date field | + +Each view saves its own filters, sorting, field visibility, and grouping configuration. Views can be shared with the workspace or kept private. + +[Table views →](/l/ru/user-guide/views-pipelines/capabilities/table-views) · [Kanban views →](/l/ru/user-guide/views-pipelines/capabilities/kanban-views) · [Calendar view →](/l/ru/user-guide/views-pipelines/capabilities/calendar-view) + +## Record pages + +When you open a record, the detail page is built from configurable tabs and widgets. Вы можете: + +* **Add, remove, and reorder tabs** on any record type +* **Configure widgets** — fields, related records, emails, timeline, calendar, tasks, notes, files, charts, iframes, and more +* **Drag and resize widgets** on a grid layout +* **Control field visibility** per widget + +Enter layout customization mode from the command menu (`Cmd+K` → "Edit record page layout"). + +[Record pages reference →](/l/ru/user-guide/layout/capabilities/record-pages) diff --git a/packages/twenty-docs/l/ru/user-guide/permissions-access/overview.mdx b/packages/twenty-docs/l/ru/user-guide/permissions-access/overview.mdx index 29df2beea46..8c506fe27c7 100644 --- a/packages/twenty-docs/l/ru/user-guide/permissions-access/overview.mdx +++ b/packages/twenty-docs/l/ru/user-guide/permissions-access/overview.mdx @@ -3,7 +3,6 @@ title: Permissions & Access description: Управляйте ролями, разрешениями и контролем доступа в вашем рабочем пространстве. --- - Система разрешений Twenty позволяет контролировать, кто может получать доступ и изменять данные в вашем рабочем пространстве. Создавайте роли, назначайте разрешения и настраивайте SSO для безопасного доступа. ## Что в этом разделе diff --git a/packages/twenty-docs/l/ru/user-guide/settings/overview.mdx b/packages/twenty-docs/l/ru/user-guide/settings/overview.mdx index cef90b21cc9..c12274af6fa 100644 --- a/packages/twenty-docs/l/ru/user-guide/settings/overview.mdx +++ b/packages/twenty-docs/l/ru/user-guide/settings/overview.mdx @@ -3,7 +3,6 @@ title: Настройки description: Настройте рабочее пространство Twenty с основными настройками. --- - ## Начальная настройка Когда вы впервые создаете рабочее пространство, необходимо настроить несколько ключевых параметров. diff --git a/packages/twenty-docs/l/ru/user-guide/views-pipelines/overview.mdx b/packages/twenty-docs/l/ru/user-guide/views-pipelines/overview.mdx index 39b19e7157e..03bcac48c78 100644 --- a/packages/twenty-docs/l/ru/user-guide/views-pipelines/overview.mdx +++ b/packages/twenty-docs/l/ru/user-guide/views-pipelines/overview.mdx @@ -5,7 +5,6 @@ description: Узнайте, как создавать и управлять п import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## Понимание представлений Представления — это сохранённые конфигурации, которые определяют, как отображаются ваши данные. У каждого представления могут быть: diff --git a/packages/twenty-docs/l/ru/user-guide/workflows/overview.mdx b/packages/twenty-docs/l/ru/user-guide/workflows/overview.mdx index 93205b840fb..9bebacf74cf 100644 --- a/packages/twenty-docs/l/ru/user-guide/workflows/overview.mdx +++ b/packages/twenty-docs/l/ru/user-guide/workflows/overview.mdx @@ -3,7 +3,6 @@ title: Рабочие процессы description: Узнайте, как создавать автоматизации в Twenty. --- - ## Почему важны Workflows Twenty был создан, чтобы обеспечить максимальную гибкость для своих пользователей. Вместо того чтобы заставлять вас адаптировать бизнес-процессы под жесткие, заранее созданные функции, рабочие процессы позволяют строить автоматизации, формирующие CRM-систему, которая наилучшим образом поддерживает ваши уникальные варианты использования. diff --git a/packages/twenty-docs/l/tr/developers/contribute/capabilities/backend-development/server-commands.mdx b/packages/twenty-docs/l/tr/developers/contribute/capabilities/backend-development/server-commands.mdx index 6b86afdabd2..6bfd01e0327 100644 --- a/packages/twenty-docs/l/tr/developers/contribute/capabilities/backend-development/server-commands.mdx +++ b/packages/twenty-docs/l/tr/developers/contribute/capabilities/backend-development/server-commands.mdx @@ -1,5 +1,6 @@ --- title: Backend Komutları +icon: terminal --- ## Faydalı Komutlar diff --git a/packages/twenty-docs/l/tr/developers/contribute/capabilities/bug-and-requests.mdx b/packages/twenty-docs/l/tr/developers/contribute/capabilities/bug-and-requests.mdx index e3869b7a57f..b24da83a04b 100644 --- a/packages/twenty-docs/l/tr/developers/contribute/capabilities/bug-and-requests.mdx +++ b/packages/twenty-docs/l/tr/developers/contribute/capabilities/bug-and-requests.mdx @@ -1,5 +1,6 @@ --- title: Hatalar, İstekler ve Çekme İstekleri +icon: bug info: Sorunları bildirin, özellik talep edin ve koda katkıda bulunun --- diff --git a/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/best-practices-front.mdx b/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/best-practices-front.mdx index 7c734b2f2bc..3243f322d0f 100644 --- a/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/best-practices-front.mdx +++ b/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/best-practices-front.mdx @@ -1,5 +1,6 @@ --- title: En İyi Uygulamalar +icon: star --- Bu belge, ön yüz üzerinde çalışırken takip etmeniz gereken en iyi uygulamaları özetler. diff --git a/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx b/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx index 5818cfe8288..fe351526e63 100644 --- a/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx +++ b/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx @@ -1,5 +1,6 @@ --- title: Klasör Mimarisi +icon: folder-tree info: Klasör mimarimize detaylı bir bakış --- diff --git a/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/frontend-commands.mdx b/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/frontend-commands.mdx index eee0ec2e185..d0abbeae81c 100644 --- a/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/frontend-commands.mdx +++ b/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/frontend-commands.mdx @@ -1,5 +1,6 @@ --- title: Ön Yüz Komutları +icon: terminal --- ## Faydalı Komutlar diff --git a/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/style-guide.mdx b/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/style-guide.mdx index 5b14e3b2996..1746389edcc 100644 --- a/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/style-guide.mdx +++ b/packages/twenty-docs/l/tr/developers/contribute/capabilities/frontend-development/style-guide.mdx @@ -1,5 +1,6 @@ --- title: Stil Rehberi +icon: paintbrush --- Bu belge, kod yazarken uyulması gereken kuralları içermektedir. diff --git a/packages/twenty-docs/l/tr/developers/contribute/capabilities/local-setup.mdx b/packages/twenty-docs/l/tr/developers/contribute/capabilities/local-setup.mdx index d1db1707784..38674637dbf 100644 --- a/packages/twenty-docs/l/tr/developers/contribute/capabilities/local-setup.mdx +++ b/packages/twenty-docs/l/tr/developers/contribute/capabilities/local-setup.mdx @@ -1,5 +1,6 @@ --- title: Yerel Kurulum +icon: laptop-code description: Twenty'i yerel olarak çalıştırmak isteyen katkıda bulunanlar (veya meraklı geliştiriciler) için kılavuz. --- diff --git a/packages/twenty-docs/l/tr/developers/contribute/commands.mdx b/packages/twenty-docs/l/tr/developers/contribute/commands.mdx new file mode 100644 index 00000000000..782ce8ed285 --- /dev/null +++ b/packages/twenty-docs/l/tr/developers/contribute/commands.mdx @@ -0,0 +1,77 @@ +--- +title: Commands +icon: terminal +description: Useful commands for developing Twenty. +--- + +Commands can be run from the repository root using `npx nx`. Use `npx nx run {project}:{command}` for explicit targeting. + +## Starting the App + +```bash +npx nx start twenty-front # Frontend dev server (http://localhost:3001) +npx nx start twenty-server # Backend server (http://localhost:3000) +npx nx run twenty-server:worker # Background worker +``` + +## Database + +```bash +npx nx database:reset twenty-server # Reset and seed database +npx nx run twenty-server:database:migrate:prod # Run migrations +npx nx run twenty-server:database:migrate:generate --name --type # Generate a migration +``` + +## Linting + +```bash +npx nx lint:diff-with-main twenty-front # Lint changed files (fastest) +npx nx lint:diff-with-main twenty-server +npx nx lint twenty-front --configuration=fix # Auto-fix +``` + +## Type Checking + +```bash +npx nx typecheck twenty-front +npx nx typecheck twenty-server +``` + +## Test + +```bash +# Frontend +npx nx test twenty-front # Jest unit tests +npx nx storybook:build twenty-front # Build Storybook +npx nx storybook:test twenty-front # Storybook tests + +# Backend +npx nx run twenty-server:test:unit # Unit tests +npx nx run twenty-server:test:integration # Integration tests +npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset + +# Single file (fastest) +npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs +``` + +## GraphQL + +```bash +npx nx run twenty-front:graphql:generate # Regenerate types +npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema +``` + +## Çeviriler + +```bash +npx nx run twenty-front:lingui:extract # Extract strings +npx nx run twenty-front:lingui:compile # Compile translations +``` + +## Build + +```bash +npx nx build twenty-shared # Must be built first +npx nx build twenty-front +npx nx build twenty-server +``` diff --git a/packages/twenty-docs/l/tr/developers/contribute/style-guide.mdx b/packages/twenty-docs/l/tr/developers/contribute/style-guide.mdx new file mode 100644 index 00000000000..1146d9ae85e --- /dev/null +++ b/packages/twenty-docs/l/tr/developers/contribute/style-guide.mdx @@ -0,0 +1,176 @@ +--- +title: Stil Rehberi +icon: paintbrush +description: Code conventions and best practices for contributing to Twenty. +--- + +## React + +### Functional components only + +Always use TSX functional components with named exports. + +```tsx +// ❌ Bad +const MyComponent = () => { + return
Hello World
; +}; +export default MyComponent; + +// ✅ Good +export function MyComponent() { + return
Hello World
; +}; +``` + +### Özellikler + +Create a type named `{ComponentName}Props`. Use destructuring. Don't use `React.FC`. + +```tsx +type MyComponentProps = { + name: string; +}; + +export const MyComponent = ({ name }: MyComponentProps) =>
Hello {name}
; +``` + +### No single-variable prop spreading + +```tsx +// ❌ Bad +const MyComponent = (props: MyComponentProps) => ; + +// ✅ Good +const MyComponent = ({ prop1, prop2 }: MyComponentProps) => ; +``` + +## Durum Yönetimi + +### Jotai atoms for global state + +```tsx +import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; +import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; + +export const myAtomState = createAtomState({ + key: 'myAtomState', + defaultValue: 'default value', +}); +``` + +* Prefer atoms over prop drilling +* Don't use `useRef` for state — use `useState` or atoms +* Use atom families and selectors for lists + +### Avoid unnecessary re-renders + +* Extract `useEffect` and data fetching into sibling sidecar components +* Prefer event handlers (`handleClick`, `handleChange`) over `useEffect` +* Don't use `React.memo()` — fix the root cause instead +* Limit `useCallback` / `useMemo` usage + +```tsx +// ❌ Bad — useEffect in the same component causes re-renders +export const Page = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return
{data}
; +}; + +// ✅ Good — extract into sibling +export const PageData = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return <>; +}; +export const Page = () => { + const [data] = useAtomState(dataState); + return
{data}
; +}; +``` + +## TypeScript + +* **`type` over `interface`** — more flexible, easier to compose +* **String literals over enums** — except for GraphQL codegen enums and internal library APIs +* **No `any`** — strict TypeScript enforced +* **No type imports** — use regular imports (enforced by Oxlint `typescript/consistent-type-imports`) +* **Use [Zod](https://github.com/colinhacks/zod)** for runtime validation of untyped objects + +## JavaScript + +```tsx +// Use nullish-coalescing (??) instead of || +const value = process.env.MY_VALUE ?? 'default'; + +// Use optional chaining +onClick?.(); +``` + +## İsimlendirme + +* **Variables**: camelCase, descriptive (`email` not `value`, `fieldMetadata` not `fm`) +* **Constants**: SCREAMING_SNAKE_CASE +* **Types/Classes**: PascalCase +* **Files/directories**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`) +* **Event handlers**: `handleClick` (not `onClick` for the handler function) +* **Component props**: prefix with component name (`ButtonProps`) +* **Styled components**: prefix with `Styled` (`StyledTitle`) + +## Stil + +Use [Linaria](https://github.com/callstack/linaria) styled components. Use theme values — avoid hardcoded `px`, `rem`, or colors. + +```tsx +// ❌ Bad +const StyledButton = styled.button` + color: #333333; + font-size: 1rem; + margin-left: 4px; +`; + +// ✅ Good +const StyledButton = styled.button` + color: ${({ theme }) => theme.font.color.primary}; + font-size: ${({ theme }) => theme.font.size.md}; + margin-left: ${({ theme }) => theme.spacing(1)}; +`; +``` + +## İçe Aktarımlar + +Use aliases instead of relative paths: + +```tsx +// ❌ Bad +import { Foo } from '../../../../../testing/decorators/Foo'; + +// ✅ Good +import { Foo } from '~/testing/decorators/Foo'; +import { Bar } from '@/modules/bar/components/Bar'; +``` + +## Folder Structure + +``` +front +└── modules/ # Feature modules +│ └── module1/ +│ ├── components/ +│ ├── constants/ +│ ├── contexts/ +│ ├── graphql/ (fragments, queries, mutations) +│ ├── hooks/ +│ ├── states/ (atoms, selectors) +│ ├── types/ +│ └── utils/ +└── pages/ # Route-level components +└── ui/ # Reusable UI components (display, input, feedback, ...) +``` + +* Modules can import from other modules, but `ui/` should stay dependency-free +* Use `internal/` subfolders for module-private code +* Components under 300 lines, services under 500 lines diff --git a/packages/twenty-docs/l/tr/developers/extend/api.mdx b/packages/twenty-docs/l/tr/developers/extend/api.mdx index 13103464b2f..9390eab9349 100644 --- a/packages/twenty-docs/l/tr/developers/extend/api.mdx +++ b/packages/twenty-docs/l/tr/developers/extend/api.mdx @@ -1,147 +1,55 @@ --- title: API'ler -description: CRM verilerinizi REST veya GraphQL kullanarak programatik olarak sorgulayın ve değiştirin. +icon: plug +description: REST and GraphQL APIs generated from your workspace schema. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -Twenty, geliştirici dostu olacak şekilde tasarlanmıştır ve özel veri modelinize uyum sağlayan güçlü API'ler sunar. Farklı entegrasyon ihtiyaçlarını karşılamak üzere dört farklı API türü sunuyoruz. +## Schema-per-tenant APIs -## Geliştirici Öncelikli Yaklaşım +There is no static API reference for Twenty. Each workspace has its own schema — when you add a custom object (say `Invoice`), it immediately gets REST and GraphQL endpoints identical to built-in objects like `Company` or `Person`. The API is generated from the schema, so endpoints use your object and field names directly — no opaque IDs. -Twenty, veri modeliniz için özel olarak API'ler oluşturur: +Your workspace-specific API documentation is available under **Settings → API & Webhooks** after creating an API key. It includes an interactive playground where you can execute real calls against your data. -* **Uzun kimlik numaralarına gerek yok**: Uç noktalarda nesne ve alan adlarınızı doğrudan kullanın -* **Standart ve özel nesneler eşit şekilde ele alınır**: Özel nesneleriniz yerleşik olanlarla aynı API muamelesini görür. -* **Özel uç noktalar**: Her nesne ve alan kendi API uç noktasına sahiptir -* **Özel dokümantasyon**: Çalışma alanınızın veri modeli için özel olarak üretilmiştir. +## Two APIs - -Bir API anahtarı oluşturduktan sonra kişiselleştirilmiş API dokümantasyonunuz **Ayarlar → API ve Webhook'lar** altında mevcuttur. Twenty, özel veri modelinize uyan API'ler oluşturduğundan, dokümantasyon çalışma alanınıza özeldir. - +**Core API** — `/rest/` and `/graphql/` -## İki API Türü +CRUD on records: People, Companies, Opportunities, your custom objects. Query, filter, traverse relations. -### Temel API +**Metadata API** — `/rest/metadata/` and `/metadata/` -`/rest/` veya `/graphql/` üzerinden erişilebilir. +Schema management: create/modify/delete objects, fields, and relations. This is how you programmatically change your data model. -Gerçek **kayıtlarınızla** (verilerin kendisiyle) çalışın: +Both are available as REST and GraphQL. GraphQL adds batch upserts and the ability to traverse relations in a single query. Same underlying data either way. -* People, Companies, Opportunities vb. oluşturun, okuyun, güncelleyin, silin. -* Verileri sorgulayın ve filtreleyin -* Kayıt ilişkilerini yönetin +## Base URLs -### Meta Veri API - -`/rest/metadata/` veya `/metadata/` üzerinden erişilebilir. - -**Çalışma alanınızı ve veri modelinizi** yönetin: - -* Nesne ve alanlar oluşturun, değiştirin veya silin -* Çalışma alanı ayarlarını yapılandırın -* Nesneler arasındaki ilişkileri tanımlayın - -## REST ve GraphQL - -Hem Temel hem de Meta Veri API'leri, REST ve GraphQL formatlarında mevcuttur: - -| Biçim | Kullanılabilir İşlemler | -| ----------- | -------------------------------------------------------------------------------- | -| **REST** | CRUD, toplu işlemler, ekleme-güncelleme işlemleri | -| **GraphQL** | Aynısı + **toplu ekleme-güncelleme işlemleri**, tek bir çağrıda ilişki sorguları | - -İhtiyaçlarınıza göre seçin — her iki format da aynı verilere erişir. - -## API Uç Noktaları - -| Ortam | Temel URL | -| ---------------------------- | ------------------------- | -| **Bulut** | `https://api.twenty.com/` | -| **Kendi Kendine Barındırma** | `https://{your-domain}/` | +| Ortam | Temel URL | +| ----------- | ------------------------- | +| Cloud | `https://api.twenty.com/` | +| Self-Hosted | `https://{your-domain}/` | ## Kimlik Doğrulama -Her API isteği, başlıkta bir API anahtarı gerektirir: - ``` Authorization: Bearer YOUR_API_KEY ``` -### Bir API Anahtarı Oluştur - -1. **Ayarlar → API ve Webhook'lar**'a gidin -2. **+ Anahtar oluştur**'a tıklayın -3. Yapılandırın: - * **Ad**: Anahtar için açıklayıcı bir ad - * **Son Kullanma Tarihi**: Anahtarın ne zaman sona ereceği -4. **Kaydet**'e tıklayın -5. **Hemen kopyalayın** — anahtar yalnızca bir kez gösterilir +Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access. - -API anahtarınız hassas verilere erişim sağlar. Güvenilmeyen hizmetlerle paylaşmayın. Tehlikeye girerse, onu hemen devre dışı bırakın ve yenisini oluşturun. - +For OAuth-based access (external apps acting on behalf of users), see [OAuth](/l/tr/developers/extend/oauth). -### Bir API Anahtarına Rol Atama +## Batch operations -Daha iyi güvenlik için, erişimi sınırlamak amacıyla belirli bir rol atayın: +Both REST and GraphQL support batching up to 60 records per request — create, update, or delete. GraphQL also supports batch upsert (create-or-update in one call) using plural names like `CreateCompanies`. -1. **Ayarlar → Roller** bölümüne gidin -2. Atamak istediğiniz role tıklayın -3. **Atama** sekmesini açın -4. **API Anahtarları** altında, **+ API anahtarına ata**'ya tıklayın -5. API anahtarını seçin +## Rate limits -Anahtar, o rolün izinlerini devralacaktır. Ayrıntılar için [İzinler](/l/tr/user-guide/permissions-access/capabilities/permissions) bölümüne bakın. - -### API Anahtarlarını Yönet - -**Yeniden Oluştur**: Ayarlar → API ve Webhook'lar → Anahtara tıklayın → **Yeniden Oluştur** - -**Sil**: Ayarlar → API ve Webhook'lar → Anahtara tıklayın → **Sil** - -## API Oyun Alanı - -Yerleşik oyun alanımız ile API'lerinizi doğrudan tarayıcıda test edin — hem **REST** hem de **GraphQL** için kullanılabilir. - -### Oyun Alanına Erişin - -1. **Ayarlar → API ve Webhook'lar**'a gidin -2. Bir API anahtarı oluşturun (gerekli) -3. Oyun alanını açmak için **REST API** veya **GraphQL API**'ye tıklayın - -### Elde Edecekleriniz - -* **Etkileşimli dokümantasyon**: Belirli veri modeliniz için oluşturulur -* **Canlı test**: Çalışma alanınıza karşı gerçek API çağrılarını gerçekleştirin -* **Şema gezgini**: Kullanılabilir nesnelere, alanlara ve ilişkilere göz atın -* **İstek oluşturucu**: Otomatik tamamlama ile sorgular oluşturun - -Oyun alanı, özel nesnelerinizi ve alanlarınızı yansıtır, bu nedenle dokümantasyon çalışma alanınız için her zaman doğrudur. - -## Toplu İşlemler - -Hem REST hem de GraphQL, toplu işlemleri destekler: - -* **Toplu boyut**: İstek başına 60 kayıt kadar -* **İşlemler**: Birden çok kaydı oluşturma, güncelleme, silme - -**Yalnızca GraphQL Özellikleri:** - -* **Toplu Ekleme-Güncelleme**: Tek bir çağrıda oluşturun veya güncelleyin -* Çoğul nesne adlarını kullanın (ör. `CreateCompany` yerine `CreateCompanies`) - -## Hız Sınırları - -Platform kararlılığını sağlamak için API istekleri kısıtlanır: - -| Sınır | Değer | -| ---------------- | --------------------- | -| **İstekler** | Dakikada 100 çağrı | -| **Toplu boyutu** | Çağrı başına 60 kayıt | - - -Verimi en üst düzeye çıkarmak için toplu işlemleri kullanın — tekil istekler yapmak yerine tek bir API çağrısında 60 kayda kadar işleyin. - +| Sınır | Değer | +| ---------- | --------------------- | +| Requests | 100 per minute | +| Batch size | Çağrı başına 60 kayıt | diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/building.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/building.mdx index 82448888a40..f8b26c5d54b 100644 --- a/packages/twenty-docs/l/tr/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/tr/developers/extend/apps/building.mdx @@ -1,2063 +1,104 @@ --- -title: Uygulama Geliştirme -description: Nesneleri, mantık fonksiyonlarını, ön uç bileşenlerini ve daha fazlasını Twenty SDK ile tanımlayın. +title: Mimari +description: How Twenty apps work — sandboxing, lifecycle, and the building blocks. +icon: sitemap --- - - Uygulamalar şu anda alfa aşamasında. Özellik işlevsel ancak hâlâ gelişmekte. - +Twenty apps are TypeScript packages that extend your workspace with custom objects, logic, UI components, and AI capabilities. They run on the Twenty platform with full sandboxing and permission controls. -`twenty-sdk` paketi, uygulamanızı oluşturmak için türlendirilmiş yapı taşları sağlar. Bu sayfa, SDK'da mevcut olan tüm varlık türlerini ve API istemcilerini kapsar. +## How apps work -## DefineEntity fonksiyonları +An app is a collection of **entities** declared using `defineEntity()` functions from the `twenty-sdk` package. The SDK detects these declarations via AST analysis at build time and produces a **manifest** — a complete description of what your app adds to a workspace. -SDK, uygulama varlıklarınızı tanımlamak için fonksiyonlar sağlar. SDK'nin varlıklarınızı algılayabilmesi için `export default defineEntity({...})` kullanmanız gerekir. Bu fonksiyonlar, derleme zamanında yapılandırmanızı doğrular ve IDE otomatik tamamlama ile tür güvenliği sağlar. - - - **Dosya organizasyonu size kalmış.** - Varlık algılama AST tabanlıdır — dosyanın nerede bulunduğundan bağımsız olarak SDK `export default defineEntity(...)` çağrılarını bulur. Dosyaları türe göre gruplamak (örn. `logic-functions/`, `roles/`) bir gereklilik değil, yalnızca bir gelenektir. - - - - - -Roller, çalışma alanınızdaki nesneler ve eylemler üzerindeki izinleri kapsar. - -```ts restricted-company-role.ts -import { - defineRole, - PermissionFlag, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -export default defineRole({ - universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', - label: 'My new role', - description: 'A role that can be used in your workspace', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - fieldUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); ``` - - - - -Her uygulamanın, şunları tanımlayan tam olarak bir adet `defineApplication` çağrısı olmalıdır: - -* **Kimlik**: tanımlayıcılar, görünen ad ve açıklama. -* **İzinler**: işlevlerinin ve ön bileşenlerinin hangi rolü kullandığı. -* **(İsteğe bağlı) Değişkenler**: fonksiyonlarınıza ortam değişkenleri olarak sunulan anahtar–değer çiftleri. -* **(İsteğe bağlı) Kurulum öncesi / kurulum sonrası fonksiyonlar**: kurulumdan önce veya sonra çalışan mantık fonksiyonları. - -```ts src/application-config.ts -import { defineApplication } from 'twenty-sdk/define'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -Notlar: -* `universalIdentifier` alanları, size ait deterministik kimliklerdir. Bunları bir kez oluşturun ve senkronizasyonlar boyunca kararlı tutun. -* `applicationVariables`, fonksiyonlarınız ve ön bileşenleriniz için ortam değişkenlerine dönüşür (örn. `DEFAULT_RECIPIENT_NAME`, `process.env.DEFAULT_RECIPIENT_NAME` olarak kullanılabilir). -* `defaultRoleUniversalIdentifier`, `defineRole()` ile tanımlanmış bir role referans vermelidir (yukarıya bakın). -* Kurulum öncesi ve kurulum sonrası fonksiyonlar manifest derlemesi sırasında otomatik olarak algılanır — bunlara `defineApplication()` içinde referans vermeniz gerekmez. - -#### Pazaryeri meta verileri - -Eğer [uygulamanızı yayımlamayı](/l/tr/developers/extend/apps/publishing) planlıyorsanız, bu isteğe bağlı alanlar uygulamanızın pazaryerinde nasıl görüneceğini kontrol eder: - -| Alan | Açıklama | -| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | -| `author` | Yazar veya şirket adı | -| `category` | Pazaryerinde filtreleme için uygulama kategorisi | -| `logoUrl` | Uygulamanızın logosuna giden yol (örn. `public/logo.png`) | -| `screenshots` | Ekran görüntüsü yollarının dizisi (örn. `public/screenshot-1.png`) | -| `aboutDescription` | "Hakkında" sekmesi için daha uzun bir markdown açıklaması. Belirtilmezse, pazaryeri npm'deki paketin `README.md` dosyasını kullanır | -| `websiteUrl` | Web sitenize bağlantı | -| `termsUrl` | Hizmet Koşulları'na bağlantı | -| `emailSupport` | Destek e-posta adresi | -| `issueReportUrl` | Sorun izleyicisine bağlantı | - -#### Roller ve izinler - -`application-config.ts` içindeki `defaultRoleUniversalIdentifier`, uygulamanızın mantık fonksiyonları ve ön bileşenleri tarafından kullanılan varsayılan rolü belirtir. Ayrıntılar için yukarıdaki `defineRole` bölümüne bakın. - -* `TWENTY_APP_ACCESS_TOKEN` olarak enjekte edilen çalışma zamanı belirteci bu rolden türetilir. -* Türlendirilmiş istemci, o role tanınan izinlerle sınırlandırılır. -* En az ayrıcalık ilkesini izleyin: Yalnızca fonksiyonlarınızın ihtiyaç duyduğu izinlere sahip özel bir rol oluşturun. - -##### Varsayılan fonksiyon rolü - -Yeni bir uygulama iskeleti oluşturduğunuzda, CLI varsayılan bir rol dosyası oluşturur: - -```ts src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk/define'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: true, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [], - fieldPermissions: [], - permissionFlags: [], -}); -``` - -Bu rolün `universalIdentifier` değeri, `application-config.ts` içinde `defaultRoleUniversalIdentifier` olarak referans verilir: - -* **\*.role.ts**, bir rolün neler yapabileceğini tanımlar. -* **application-config.ts**, fonksiyonlarınızın izinlerini devralması için bu role işaret eder. - -Notlar: -* Oluşturulan rolden başlayın ve en az ayrıcalık ilkesini izleyerek bunu aşamalı olarak kısıtlayın. -* `objectPermissions` ve `fieldPermissions` değerlerini, fonksiyonlarınızın ihtiyaç duyduğu nesneler ve alanlarla değiştirin. -* `permissionFlags`, platform düzeyindeki yeteneklere erişimi kontrol eder. Bunları asgari düzeyde tutun. -* Çalışan bir örnek için bkz.: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). - - - - -Özel nesneler, çalışma alanınızdaki kayıtlar için hem şemayı hem de davranışı tanımlar. Yerleşik doğrulamayla nesneler tanımlamak için `defineObject()` kullanın: - -```ts postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk/define'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -Önemli noktalar: - -* Yerleşik doğrulama ve daha iyi IDE desteği için `defineObject()` kullanın. -* `universalIdentifier` dağıtımlar arasında benzersiz ve kararlı olmalıdır. -* Her alan bir `name`, `type`, `label` ve kendi kararlı `universalIdentifier` değerini gerektirir. -* `fields` dizisi isteğe bağlıdır — özel alanlar olmadan da nesneler tanımlayabilirsiniz. -* `yarn twenty add` kullanarak, adlandırma, alanlar ve ilişkiler konusunda sizi yönlendirerek yeni nesneler oluşturabilirsiniz. - - -**Temel alanlar otomatik olarak oluşturulur.** Özel bir nesne tanımladığınızda Twenty, standart alanları otomatik olarak ekler -örneğin `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` ve `deletedAt`. -Bunları `fields` dizinizde tanımlamanız gerekmez — yalnızca özel alanlarınızı ekleyin. -`fields` dizinizde aynı ada sahip bir alan tanımlayarak varsayılan alanları geçersiz kılabilirsiniz, -ancak bu önerilmez. - - - - - -Sahibi olmadığınız nesnelere alan eklemek için `defineField()` kullanın — standart Twenty nesneleri (Person, Company, vb.) gibi. veya diğer uygulamalardaki nesneler. `defineObject()` içindeki satır içi alanların aksine, bağımsız alanlar hangi nesneyi genişlettiklerini belirtmek için bir `objectUniversalIdentifier` gerektirir: - -```ts src/fields/company-loyalty-tier.field.ts -import { defineField, FieldType } from 'twenty-sdk/define'; - -export default defineField({ - universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', - objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object - name: 'loyaltyTier', - type: FieldType.SELECT, - label: 'Loyalty Tier', - icon: 'IconStar', - options: [ - { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, - { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, - { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, - ], -}); -``` - -Önemli noktalar: -* `objectUniversalIdentifier` hedef nesneyi tanımlar. Standart nesneler için, `twenty-sdk`'den dışa aktarılan `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`'ı kullanın. -* Alanları `defineObject()` içinde satır içi tanımlarken, `objectUniversalIdentifier`'a ihtiyacınız yoktur — üst nesneden devralınır. -* `defineField()`, `defineObject()` ile oluşturmadığınız nesnelere alan eklemenin tek yoludur. - - - - -İlişkiler nesneleri birbirine bağlar. Twenty'de ilişkiler her zaman **çift yönlüdür** — her iki tarafı da tanımlarsınız ve her taraf diğerine başvurur. - -İki ilişki türü vardır: - -| İlişki türü | Açıklama | Yabancı anahtar var mı? | -| ------------- | --------------------------------------------------------- | ----------------------- | -| `MANY_TO_ONE` | Bu nesnenin birçok kaydı, hedefin bir kaydını işaret eder | Evet (`joinColumnName`) | -| `ONE_TO_MANY` | Bu nesnenin bir kaydı, hedefin birçok kaydına sahiptir | Hayır (ters taraf) | - -#### İlişkiler nasıl çalışır - -Her ilişki, birbirine referans veren iki alan gerektirir: - -1. **MANY_TO_ONE** tarafı — yabancı anahtarı tutan nesne üzerinde bulunur -2. **ONE_TO_MANY** tarafı — koleksiyona sahip olan nesne üzerinde bulunur - -Her iki alan da `FieldType.RELATION` kullanır ve `relationTargetFieldMetadataUniversalIdentifier` aracılığıyla birbirine karşılıklı referans verir. - -#### Örnek: Posta Kartı'nın birçok Alıcısı vardır - -Bir `PostCard`'ın birçok `PostCardRecipient` kaydına gönderilebildiğini varsayalım. Her alıcı tam olarak bir posta kartına aittir. - -**Adım 1: PostCard üzerinde ONE_TO_MANY tarafını tanımlayın** ("bir" taraf): - -```ts src/fields/post-card-recipients-on-post-card.field.ts -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; -// Import from the other side -import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; - -export default defineField({ - universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCardRecipients', - label: 'Post Card Recipients', - icon: 'IconUsers', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); -``` - -**Adım 2: PostCardRecipient üzerinde MANY_TO_ONE tarafını tanımlayın** ("çok" taraf — yabancı anahtarı tutar): - -```ts src/fields/post-card-on-post-card-recipient.field.ts -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; -// Import from the other side -import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; - -export default defineField({ - universalIdentifier: POST_CARD_FIELD_ID, - objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - icon: 'IconMail', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, -}); +your-app/ +├── src/ +│ ├── application-config.ts ← defineApplication (required, one per app) +│ ├── roles/ ← defineRole +│ ├── objects/ ← defineObject +│ ├── fields/ ← defineField +│ ├── logic-functions/ ← defineLogicFunction +│ ├── front-components/ ← defineFrontComponent +│ ├── skills/ ← defineSkill +│ ├── agents/ ← defineAgent +│ ├── views/ ← defineView +│ ├── navigation-menu-items/ ← defineNavigationMenuItem +│ └── page-layouts/ ← definePageLayout +├── public/ ← Static assets (images, icons) +└── package.json ``` -**Döngüsel içe aktarmalar:** Her iki ilişki alanı da birbirlerinin `universalIdentifier` değerine referans verir. Döngüsel içe aktarma sorunlarından kaçınmak için, alan kimliklerinizi her dosyadan adlandırılmış sabitler olarak dışa aktarın ve diğer dosyada içe aktarın. Derleme sistemi bunları derleme zamanında çözer. + **File organization is up to you.** Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. The folder structure above is a convention, not a requirement. -#### Standart nesnelerle ilişkilendirme - -Yerleşik bir Twenty nesnesiyle (Person, Company, vb.) ilişki oluşturmak için `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` kullanın: - -```ts src/fields/person-on-self-hosting-user.field.ts -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; -import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; - -export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; -export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; - -export default defineField({ - universalIdentifier: PERSON_FIELD_ID, - objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - description: 'Person matching with the self hosting user', - isNullable: true, - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'personId', - }, -}); -``` - -#### İlişki alanı özellikleri - -| Özellik | Zorunlu | Açıklama | -| ------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- | -| `type` | Evet | `FieldType.RELATION` olmalıdır | -| `relationTargetObjectMetadataUniversalIdentifier` | Evet | Hedef nesnenin `universalIdentifier` değeri | -| `relationTargetFieldMetadataUniversalIdentifier` | Evet | Hedef nesnedeki eşleşen alanın `universalIdentifier` değeri | -| `universalSettings.relationType` | Evet | `RelationType.MANY_TO_ONE` veya `RelationType.ONE_TO_MANY` | -| `universalSettings.onDelete` | Yalnızca MANY_TO_ONE | Başvurulan kayıt silindiğinde ne olacağı: `CASCADE`, `SET_NULL`, `RESTRICT` veya `NO_ACTION` | -| `universalSettings.joinColumnName` | Yalnızca MANY_TO_ONE | Yabancı anahtar için veritabanı sütun adı (örn. `postCardId`) | - -#### defineObject içinde satır içi ilişki alanları - -İlişki alanlarını doğrudan `defineObject()` içinde de tanımlayabilirsiniz. Bu durumda, `objectUniversalIdentifier`'ı atlayın — üst nesneden devralınır: - -```ts -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCardRecipient', - // ... - fields: [ - { - universalIdentifier: POST_CARD_FIELD_ID, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, - }, - // ... other fields - ], -}); -``` - - - -Her fonksiyon dosyası, bir işleyici ve isteğe bağlı tetikleyiciler içeren bir yapılandırmayı dışa aktarmak için `defineLogicFunction()` kullanır. - -```ts src/logic-functions/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - httpRouteTriggerSettings: { - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: true, - }, - /*databaseEventTriggerSettings: { - eventName: 'people.created', - },*/ - /*cronTriggerSettings: { - pattern: '0 0 1 1 *', - },*/ -}); -``` - -Kullanılabilir tetikleyici türleri: -* **httpRoute**: Fonksiyonunuzu bir HTTP yolu ve yöntemiyle **`/s/` uç noktasının altında** kullanıma sunar: -> örn. `path: '/post-card/create'` `https://your-twenty-server.com/s/post-card/create` adresinden çağrılabilir -* **cron**: Bir CRON ifadesi kullanarak fonksiyonunuzu bir zamanlamayla çalıştırır. -* **databaseEvent**: Çalışma alanı nesnesi yaşam döngüsü olaylarında çalışır. Olay işlemi `updated` olduğunda, dinlenecek belirli alanlar `updatedFields` dizisinde belirtilebilir. Tanımsız veya boş bırakılırsa, herhangi bir güncelleme fonksiyonu tetikler. -> örn. `person.updated`, `*.created`, `company.*` - - -Bir fonksiyonu CLI kullanarak manuel olarak da çalıştırabilirsiniz: - -```bash filename="Terminal" -yarn twenty exec -n create-new-post-card -p '{"key": "value"}' -``` - -```bash filename="Terminal" -yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -Günlükleri şu şekilde izleyebilirsiniz: - -```bash filename="Terminal" -yarn twenty logs -``` - - -#### Rota tetikleyicisi yükü - -Bir rota tetikleyicisi mantık fonksiyonunuzu çağırdığında, -[AWS HTTP API v2 formatını](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) izleyen bir `RoutePayload` nesnesi alır. -`RoutePayload` türünü `twenty-sdk` içinden içe aktarın: - -```ts -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; - -const handler = async (event: RoutePayload) => { - const { headers, queryStringParameters, pathParameters, body } = event; - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -`RoutePayload` türünün yapısı şu şekildedir: - - | Özellik | Tür | Açıklama | Örnek | - | ---------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | - | `headers` | `Record\` | HTTP başlıkları (`forwardedRequestHeaders` içinde listelenenlerle sınırlı) | aşağıdaki bölüme bakın | - | `queryStringParameters` | `Record\` | Sorgu dizesi parametreleri (birden çok değer virgülle birleştirilir) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | - | `pathParameters` | `Record\` | Rota deseninden çıkarılan yol parametreleri | `/users/:id`, `/users/123` -> `{ id: '123' }` | - | `body` | `object \| null` | Ayrıştırılmış istek gövdesi (JSON) | `{ id: 1 }` -> `{ id: 1 }` | - | `isBase64Encoded` | `boolean` | Gövdenin base64 ile kodlanıp kodlanmadığı | | - | `requestContext.http.method` | `string` | HTTP yöntemi (GET, POST, PUT, PATCH, DELETE) | | - | `requestContext.http.path` | `string` | Ham istek yolu | | - - -#### forwardedRequestHeaders - -Varsayılan olarak, güvenlik nedenleriyle gelen isteklerden HTTP başlıkları mantık fonksiyonunuza **aktarılmaz**. -Belirli başlıklara erişmek için bunları `forwardedRequestHeaders` dizisinde listeleyin: - -```ts -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - httpRouteTriggerSettings: { - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, -}); -``` - -İşleyicinizde, iletilen başlıklara şu şekilde erişin: - -```ts -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - -Başlık adları küçük harfe normalize edilir. Onlara küçük harfli anahtarlarla erişin (örneğin, `event.headers['content-type']`). - - -#### Bir fonksiyonu araç olarak sunma - -Mantık işlevleri, yapay zeka ajanları ve iş akışları için **araçlar** olarak sunulabilir. Bir fonksiyon bir araç olarak işaretlendiğinde, Twenty'nin yapay zeka özellikleri tarafından keşfedilebilir hâle gelir ve iş akışı otomasyonlarında kullanılabilir. - -Bir mantık fonksiyonunu araç olarak işaretlemek için `isTool: true` olarak ayarlayın: - -```ts src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, -}); -``` - -Önemli noktalar: - -* `isTool` özelliğini tetikleyicilerle birleştirebilirsiniz — bir fonksiyon aynı anda hem bir araç (yapay zeka ajanları tarafından çağrılabilir) olabilir hem de olaylar tarafından tetiklenebilir. -* **`toolInputSchema`** (isteğe bağlı): Fonksiyonunuzun kabul ettiği parametreleri tanımlayan bir JSON Schema nesnesi. Şema, kaynak kodun statik analizinden otomatik olarak oluşturulur, ancak bunu açıkça belirleyebilirsiniz: - -```ts -export default defineLogicFunction({ - ..., - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - - -**İyi bir `description` yazın.** AI ajanları, aracı ne zaman kullanacaklarına karar vermek için işlevin `description` alanına güvenir. Aracın ne yaptığını ve ne zaman çağrılması gerektiğini açıkça belirtin. - - - - - -Kurulum sonrası işlev, uygulamanız bir çalışma alanına yüklendikten sonra otomatik olarak çalışan bir mantık işlevidir. Sunucu, uygulamanın meta verileri senkronize edildikten ve SDK istemcisi oluşturulduktan **sonra** bunu yürütür; böylece çalışma alanı tamamen kullanıma hazırdır ve yeni şema kullanıma alınmıştır. Tipik kullanım örnekleri arasında varsayılan verilerin tohumlanması, başlangıç kayıtlarının oluşturulması, çalışma alanı ayarlarının yapılandırılması veya üçüncü taraf hizmetlerde kaynak sağlanması yer alır. - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - shouldRunSynchronously: false, - handler, -}); -``` - -Ayrıca kurulum sonrası işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz: - -```bash filename="Terminal" -yarn twenty exec --postInstall -``` - -Önemli noktalar: -* Kurulum sonrası işlevler `definePostInstallLogicFunction()` kullanır — tetikleyici ayarlarını atlayan (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) özel bir varyanttır. -* İşleyici, `{ previousVersion?: string; newVersion: string }` içeren bir `InstallPayload` alır — `newVersion`, yüklenen sürümdür; `previousVersion` ise daha önce yüklü olan sürümdür (veya ilk kurulumda `undefined`). Bu değerleri ilk kurulumları yükseltmelerden ayırt etmek ve sürüme özgü geçiş (migration) mantığını çalıştırmak için kullanın. -* **Kanca ne zaman çalışır**: varsayılan olarak yalnızca ilk kurulumlarda. Uygulama önceki bir sürümden yükseltildiğinde de çalışmasını istiyorsanız `shouldRunOnVersionUpgrade: true` geçin. Belirtilmediğinde, bayrak varsayılan olarak `false` olur ve yükseltmeler kancayı atlar. -* **Yürütme modeli — varsayılan olarak eşzamansız, isteğe bağlı senkron**: `shouldRunSynchronously` bayrağı kurulum sonrası işlemin *nasıl* yürütüldüğünü kontrol eder. - * `shouldRunSynchronously: false` *(varsayılan)* — kanca, `retryLimit: 3` ile **mesaj kuyruğuna alınır** ve bir worker içinde eşzamansız çalışır. İş kuyruğa alınır alınmaz kurulum yanıtı döner; dolayısıyla yavaşlayan veya hata veren bir işleyici çağıranı engellemez. Worker en fazla üç kez yeniden deneyecektir. **Bunu uzun süre çalışan işler için kullanın** — büyük veri kümelerini tohumlama, yavaş üçüncü taraf API'lerini çağırma, harici kaynakları sağlama; makul bir HTTP yanıt süresini aşabilecek her şey. - * `shouldRunSynchronously: true` — kanca **kurulum akışı sırasında satır içi** olarak yürütülür (kurulum öncesi ile aynı yürütücü). İşleyici bitene kadar kurulum isteği engellenir; hata fırlatırsa, kurulum çağıranı bir `POST_INSTALL_ERROR` alır. Otomatik yeniden deneme yok. **Bunu, yanıt dönmeden mutlaka tamamlanması gereken hızlı işler için kullanın** — örneğin, kullanıcıya bir doğrulama hatası iletmek veya kurulum çağrısı döner dönmez istemcinin ihtiyaç duyacağı hızlı bir kurulum yapmak. Kurulum sonrası çalıştığında, üstveri (metadata) geçişinin zaten uygulanmış olduğunu unutmayın; bu nedenle, senkron moddaki bir hata şema değişikliklerini **geri almaz** — yalnızca hatayı görünür kılar. -* İşleyicinizin idempotent olduğundan emin olun. Eşzamansız modda kuyruk en fazla üç kez yeniden deneyebilir; her iki modda da `shouldRunOnVersionUpgrade: true` iken yükseltmelerde kanca tekrar çalışabilir. -* Ortam değişkenleri `APPLICATION_ID`, `APP_ACCESS_TOKEN` ve `API_URL` işleyici içinde kullanılabilir (diğer mantık işlevlerinde olduğu gibi), böylece uygulamanıza özel kapsamda bir uygulama erişim belirteciyle Twenty API'sini çağırabilirsiniz. -* Uygulama başına yalnızca bir kurulum sonrası işlevine izin verilir. Birden fazla tespit edilirse manifest oluşturma hataya düşer. -* İşlevin `universalIdentifier`, `shouldRunOnVersionUpgrade` ve `shouldRunSynchronously` değerleri, derleme sırasında uygulama manifestine `postInstallLogicFunction` alanı altında otomatik olarak eklenir — bunlara `defineApplication()` içinde atıfta bulunmanıza gerek yoktur. -* Varsayılan zaman aşımı, veri tohumlama gibi daha uzun kurulum görevlerine izin vermek için 300 saniye (5 dakika) olarak ayarlanmıştır. -* **Geliştirme modunda çalıştırılmaz**: bir uygulama yerel olarak kaydedildiğinde (`yarn twenty dev` aracılığıyla), sunucu kurulum akışını tamamen atlar ve dosyaları doğrudan CLI watcher üzerinden eşitler — bu nedenle, `shouldRunSynchronously` ne olursa olsun, kurulum sonrası geliştirme modunda hiç çalışmaz. Çalışan bir çalışma alanında bunu elle tetiklemek için `yarn twenty exec --postInstall` kullanın. - - - - -Kurulum öncesi işlev, kurulum sırasında otomatik olarak çalışan ve **çalışma alanı üstveri (metadata) geçişi uygulanmadan önce** yürütülen bir mantık işlevidir. Kurulum sonrası ile (`InstallPayload`) aynı yük (payload) biçimini paylaşır, ancak kurulum akışında daha erken konumlandığından yaklaşan geçişin bağlı olduğu durumu hazırlayabilir — tipik kullanımlar arasında verileri yedeklemek, yeni şemayla uyumluluğu doğrulamak veya yeniden yapılandırılacak ya da kaldırılacak kayıtları arşivlemek yer alır. - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; - -const handler = async (payload: InstallPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -Ayrıca kurulum öncesi işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz: - -```bash filename="Terminal" -yarn twenty exec --preInstall -``` - -Önemli noktalar: -* Kurulum öncesi işlevler `definePreInstallLogicFunction()` kullanır — kurulum sonrasıyla aynı özel yapılandırma, sadece yaşam döngüsünde farklı bir yuvaya eklenir. -* Hem kurulum öncesi hem de kurulum sonrası işleyiciler aynı `InstallPayload` türünü alır: `{ previousVersion?: string; newVersion: string }`. Bunu bir kez içe aktarın ve her iki kanca için yeniden kullanın. -* **Kanca ne zaman çalışır**: çalışma alanı üstveri (metadata) geçişinden hemen önce konumlandırılır (`synchronizeFromManifest`). Çalıştırmadan önce, sunucu yalnızca ekleyici bir "indirgenmiş eşitleme" yürütür; bu, çalışma alanı üstverisinde **yeni** sürümün kurulum öncesi işlevini kaydeder — başka hiçbir şeye dokunulmaz — ve ardından bunu yürütür. Bu eşitleme yalnızca ekleyici olduğundan, işleyiciniz çalıştığında önceki sürümün nesneleri, alanları ve verileri hâlâ sağlamdır: geçiş öncesi durumu güvenle okuyabilir ve yedekleyebilirsiniz. -* **Yürütme modeli**: kurulum öncesi **senkron** olarak yürütülür ve **kurulumu bloklar**. İşleyici bir hata fırlatırsa, herhangi bir şema değişikliği uygulanmadan önce kurulum iptal edilir — çalışma alanı, tutarlı bir durumda önceki sürümde kalır. Bu kasıtlıdır: kurulum öncesi, riskli bir yükseltmeyi reddetmek için son şansınızdır. -* Kurulum sonrası ile aynı şekilde, uygulama başına yalnızca bir kurulum öncesi işlevine izin verilir. Derleme sırasında uygulama manifestine `preInstallLogicFunction` altında otomatik olarak eklenir. -* **Geliştirme modunda çalıştırılmaz**: kurulum sonrasında olduğu gibi — yerel olarak kaydedilen uygulamalarda kurulum akışı tamamen atlanır, bu nedenle `yarn twenty dev` altında kurulum öncesi hiç çalışmaz. Bunu elle tetiklemek için `yarn twenty exec --preInstall` kullanın. - - - - -Her iki kanca da aynı kurulum akışının parçasıdır ve aynı `InstallPayload`'ı alır. Fark, çalışma alanı üstveri (metadata) geçişine göre **ne zaman** çalıştıklarıdır ve bu, güvenle erişebilecekleri verileri değiştirir. +## Entity types + +| Varlık | Amaç | Belgeler | +| ------------------------ | ----------------------------------------- | ------------------------------------------------------------- | +| **Application** | App identity, permissions, variables | [Data Model](/l/tr/developers/extend/apps/data-model) | +| **Role** | Permission sets for objects and fields | [Data Model](/l/tr/developers/extend/apps/data-model) | +| **Nesne** | Custom data tables with fields | [Data Model](/l/tr/developers/extend/apps/data-model) | +| **Alan** | Extend existing objects, define relations | [Data Model](/l/tr/developers/extend/apps/data-model) | +| **Logic Function** | Server-side TypeScript with triggers | [Mantıksal İşlevler](/l/tr/developers/extend/apps/logic-functions) | +| **Front Component** | Sandboxed React UI in Twenty's page | [Front Components](/l/tr/developers/extend/apps/front-components) | +| **Skill** | Reusable AI agent instructions | [Skills & Agents](/l/tr/developers/extend/apps/skills-and-agents) | +| **Agent** | AI assistants with custom prompts | [Skills & Agents](/l/tr/developers/extend/apps/skills-and-agents) | +| **View** | Pre-configured record list views | [Layout](/l/tr/developers/extend/apps/layout) | +| **Navigation Menu Item** | Custom sidebar entries | [Layout](/l/tr/developers/extend/apps/layout) | +| **Page Layout** | Custom record page tabs and widgets | [Layout](/l/tr/developers/extend/apps/layout) | + +## Sandboxing + +* **Logic functions** run in isolated Node.js processes on the server. They only access data through the typed API client, scoped to the app's role permissions. +* **Front components** run in Web Workers using Remote DOM — sandboxed from the main page but rendering native DOM elements (not iframes). They communicate with Twenty via a message-passing host API. +* **Permissions** are enforced at the API level. The runtime token (`TWENTY_APP_ACCESS_TOKEN`) is derived from the role defined in `defineApplication()`. + +## App lifecycle ``` -┌─────────────────────────────────────────────────────────────┐ -│ install flow │ -│ │ -│ upload package → [pre-install] → metadata migration → │ -│ generate SDK → [post-install] │ -│ │ -│ old schema visible new schema visible │ -└─────────────────────────────────────────────────────────────┘ +┌─────────────────────────────────────────────────────────┐ +│ Development │ +│ npx create-twenty-app → yarn twenty dev (live sync) │ +├─────────────────────────────────────────────────────────┤ +│ Build & Deploy │ +│ yarn twenty build → yarn twenty deploy │ +├─────────────────────────────────────────────────────────┤ +│ Install flow │ +│ upload → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +├─────────────────────────────────────────────────────────┤ +│ Publish │ +│ npm publish → appears in Twenty marketplace │ +└─────────────────────────────────────────────────────────┘ ``` -Kurulum öncesi her zaman **senkron**dur (kurulumu bloke eder ve iptal edebilir). Kurulum sonrası **varsayılan olarak asenkron**dur — otomatik yeniden denemelerle bir worker üzerinde kuyruğa alınır — ancak `shouldRunSynchronously: true` ile senkron yürütmeye geçebilir. Her modun ne zaman kullanılacağı için yukarıdaki `definePostInstallLogicFunction` akordeonuna bakın. - -**Yeni şemanın mevcut olmasını gerektiren her şey için `post-install` kullanın.** Bu yaygın durumdur: - -* Yeni eklenen nesne ve alanlara karşı varsayılan verileri tohumlama (ilk kayıtları, varsayılan görünümleri, demo içeriği oluşturma). -* Uygulamanın kimlik bilgileri artık mevcut olduğuna göre, üçüncü taraf hizmetlerle webhook'ları kaydetmek. -* Eşitlenmiş üstveriye (metadata) bağlı kurulumu tamamlamak için kendi API'nizi çağırmak. -* Her yükseltmede durumu uzlaştırması gereken idempotent "bu mevcut olsun" mantığı — `shouldRunOnVersionUpgrade: true` ile birleştirin. - -Örnek — kurulumdan sonra varsayılan bir `PostCard` kaydı tohumlama: - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion }: InstallPayload): Promise => { - if (previousVersion) return; // fresh installs only - - const client = createClient(); - await client.postCard.create({ - data: { title: 'Welcome to Postcard', content: 'Your first card!' }, - }); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Seeds a welcome post card after install.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: false, - handler, -}); -``` - -**Bir geçiş mevcut verileri aksi takdirde silecek veya bozacaksa `pre-install` kullanın.** Kurulum öncesi *önceki* şemaya karşı çalıştığı ve hatalandığında yükseltmeyi geri aldığı için, riskli olan her şey için doğru yerdir: - -* **Kaldırılmak veya yeniden yapılandırılmak üzere olan verileri yedekleme** — örn. v2'de bir alanı kaldırıyorsunuz ve geçiş çalışmadan önce değerlerini başka bir alana kopyalamanız veya depolamaya aktarmanız gerekiyor. -* **Yeni bir kısıtın geçersiz kılacağı kayıtları arşivleme** — örn. bir alan `NOT NULL` oluyor ve önce null değerli satırları silmeniz veya düzeltmeniz gerekiyor. -* **Uyumluluğu doğrulama ve mevcut veriler temiz bir şekilde geçirilemiyorsa yükseltmeyi reddetme** — işleyiciden hata fırlatın ve kurulum, herhangi bir değişiklik uygulanmadan iptal edilir. Bu, uyumsuzluğu geçişin ortasında keşfetmekten daha güvenlidir. -* İlişkilendirmeyi kaybettirecek bir şema değişikliğinden önce **verileri yeniden adlandırma veya yeniden anahtarlama**. - -Örnek — yıkıcı bir geçişten önce kayıtları arşivleme: - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; -import { createClient } from './generated/client'; - -const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { - // Only the 1.x → 2.x upgrade drops the legacy `notes` field. - if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { - return; - } - - const client = createClient(); - const legacyRecords = await client.postCard.findMany({ - where: { notes: { isNotNull: true } }, - }); - - if (legacyRecords.length === 0) return; - - // Copy legacy `notes` into the new `description` field before the migration - // drops the `notes` column. If this fails, the upgrade is aborted and the - // workspace stays on v1 with all data intact. - await Promise.all( - legacyRecords.map((record) => - client.postCard.update({ - where: { id: record.id }, - data: { description: record.notes }, - }), - ), - ); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', - name: 'pre-install', - description: 'Backs up legacy notes into description before the v2 migration.', - timeoutSeconds: 300, - shouldRunOnVersionUpgrade: true, - handler, -}); -``` - -**Kural olarak:** - -| Şunu yapmak istiyorsunuz… | Kullan | -| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | -| Varsayılan verileri tohumlamak, çalışma alanını yapılandırmak, harici kaynakları kaydetmek | `post-install` | -| Kurulum yanıtını engellememesi gereken uzun süreli tohumlama veya üçüncü taraf çağrılarını çalıştırmak | `post-install` (varsayılan — `shouldRunSynchronously: false`, worker yeniden denemeleriyle) | -| Kurulum çağrısı döner dönmez çağıranın güveneceği hızlı kurulumu çalıştırmak | `post-install` ile `shouldRunSynchronously: true` | -| Yaklaşan geçişin kaybedeceği verileri okumak veya yedeklemek | `pre-install` | -| Mevcut verileri bozacak bir yükseltmeyi reddetmek | `pre-install` (işleyiciden hata fırlatmak) | -| Her yükseltmede uzlaştırma çalıştırmak | `post-install` ile `shouldRunOnVersionUpgrade: true` | -| Yalnızca ilk kurulumda tek seferlik kurulum yapmak | `post-install` ile `shouldRunOnVersionUpgrade: false` (varsayılan) | - - -Emin değilseniz, varsayılan olarak **kurulum sonrası**nı tercih edin. Yalnızca geçişin kendisi yıkıcıysa ve önceki durum yok olmadan önce onu yakalamanız gerekiyorsa kurulum öncesine başvurun. - - - - - -Ön uç bileşenler, Twenty'nin UI'si içinde doğrudan görüntülenen React bileşenleridir. Remote DOM kullanan izole bir Web Worker içinde çalışırlar — kodunuz izole bir ortamda (sandbox) çalışır ancak bir iframe içinde değil, sayfada yerel olarak işlenir. - -#### Ön bileşenlerin kullanılabileceği yerler - -Ön bileşenler, Twenty içinde iki konumda işlenebilir: - -* **Yan panel** — Headless olmayan ön bileşenler, sağ taraftaki yan panelde açılır. Bir ön bileşen komut menüsünden tetiklendiğinde varsayılan davranış budur. -* **Widget'lar (panolar ve kayıt sayfaları)** — Ön bileşenler, sayfa düzenlerine widget olarak gömülebilir. Bir pano veya kayıt sayfası düzeni yapılandırılırken kullanıcılar bir ön bileşen widget'ı ekleyebilir. - -#### Basit örnek - -Bir ön uç bileşenini çalışırken görmenin en hızlı yolu, onu bir komut olarak kaydetmektir. `isPinned: true` ile bir `command` alanı eklemek, sayfanın sağ üst köşesinde hızlı işlem düğmesi olarak görünmesini sağlar — herhangi bir sayfa düzenine gerek yoktur: - -```tsx src/front-components/hello-world.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; - -const HelloWorld = () => { - return ( -
-

Hello from my app!

-

This component renders inside Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', - name: 'hello-world', - description: 'A simple front component', - component: HelloWorld, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', - shortLabel: 'Hello', - label: 'Hello World', - icon: 'IconBolt', - isPinned: true, - availabilityType: 'GLOBAL', - }, -}); -``` - -`yarn twenty dev` ile senkronize ettikten sonra (veya tek seferlik bir `yarn twenty dev --once` çalıştırdıktan sonra), hızlı işlem sayfanın sağ üst köşesinde görünür: - -
- Sağ üst köşedeki hızlı işlem düğmesi -
- -Bileşeni satır içi işlemek için üzerine tıklayın. - -{/* TODO: add screenshot of the rendered front component */} - -#### Yapılandırma alanları - -| Alan | Zorunlu | Açıklama | -| --------------------- | ------- | ------------------------------------------------------------------------------------------ | -| `universalIdentifier` | Evet | Bu bileşen için kalıcı benzersiz kimlik | -| `component` | Evet | Bir React bileşen fonksiyonu | -| `name` | Hayır | Görünen Ad | -| `description` | Hayır | Bileşenin ne yaptığına dair açıklama | -| `isHeadless` | Hayır | Bileşenin görünür bir kullanıcı arayüzü yoksa `true` olarak ayarlayın (aşağıya bakın) | -| `command` | Hayır | Bileşeni bir komut olarak kaydedin (aşağıda [komut seçeneklerine](#command-options) bakın) | - -#### Bir ön uç bileşenini bir sayfaya yerleştirme - -Komutların ötesinde, bir ön uç bileşenini bir **sayfa düzeninde** widget olarak ekleyerek doğrudan bir kayıt sayfasına gömebilirsiniz. Ayrıntılar için [definePageLayout](#definepagelayout) bölümüne bakın. - -#### Headless ve headless olmayan - -Ön bileşenler, `isHeadless` seçeneğiyle kontrol edilen iki işleme kipiyle gelir: - -**Headless olmayan (varsayılan)** — Bileşen görünür bir kullanıcı arayüzü (UI) oluşturur. Komut menüsünden tetiklendiğinde yan panelde açılır. `isHeadless` `false` olduğunda veya belirtilmediğinde bu varsayılan davranıştır. - -**Headless (`isHeadless: true`)** — Bileşen arka planda görünmez şekilde bağlanır. Yan paneli açmaz. Headless bileşenler, mantığı çalıştırıp ardından kendilerini kaldıran eylemler için tasarlanmıştır — örneğin, bir async görevi çalıştırma, bir sayfaya gitme veya bir onay modalı gösterme. Aşağıda açıklanan SDK Command bileşenleriyle doğal olarak eşleşirler. - -```tsx src/front-components/sync-tracker.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; -import { useEffect } from 'react'; - -const SyncTracker = () => { - const recordId = useRecordId(); - - useEffect(() => { - enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); - }, [recordId]); - - return null; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'sync-tracker', - description: 'Tracks record views silently', - isHeadless: true, - component: SyncTracker, -}); -``` - -Bileşen `null` döndürdüğü için, Twenty bunun için bir kapsayıcı oluşturmayı atlar — düzende boş alan görünmez. Bileşen yine de tüm hook'lara ve host iletişim API'sine erişime sahiptir. - -#### SDK Command bileşenleri - -`twenty-sdk` paketi, headless ön bileşenler için tasarlanmış dört Command yardımcı bileşeni sağlar. Her bileşen bağlandığında bir eylem yürütür, hataları bir snackbar bildirimi göstererek ele alır ve tamamlandığında ön bileşeni otomatik olarak kaldırır. - -Bunları `twenty-sdk/command` içinden içe aktarın: - -* **`Command`** — `execute` prop'u aracılığıyla async bir geri çağrıyı çalıştırır. -* **`CommandLink`** — Bir uygulama yoluna gider. Props: `to`, `params`, `queryParams`, `options`. -* **`CommandModal`** — Bir onay modalı açar. Kullanıcı onaylarsa `execute` geri çağrısını yürütür. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. -* **`CommandOpenSidePanelPage`** — Belirli bir yan panel sayfasını açar. Props: `page`, `pageTitle`, `pageIcon`. - -`Command` kullanarak komut menüsünden bir eylem çalıştıran headless bir ön bileşenin tam örneği: - -```tsx src/front-components/run-action.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Command } from 'twenty-sdk/command'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const RunAction = () => { - const execute = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - createTask: { - __args: { data: { title: 'Created by my app' } }, - id: true, - }, - }); - }; - - return ; -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', - name: 'run-action', - description: 'Creates a task from the command menu', - component: RunAction, - isHeadless: true, - command: { - universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', - label: 'Run my action', - icon: 'IconPlayerPlay', - }, -}); -``` - -Ve yürütmeden önce onay istemek için `CommandModal` kullanan bir örnek: - -```tsx src/front-components/delete-draft.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { CommandModal } from 'twenty-sdk/command'; - -const DeleteDraft = () => { - const execute = async () => { - // perform the deletion - }; - - return ( - - ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', - name: 'delete-draft', - description: 'Deletes a draft with confirmation', - component: DeleteDraft, - isHeadless: true, - command: { - universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', - label: 'Delete draft', - icon: 'IconTrash', - }, -}); -``` - -#### Çalışma zamanı bağlamına erişme - -Bileşeninizin içinde, geçerli kullanıcıya, kayda ve bileşen örneğine erişmek için SDK hook'larını kullanın: - -```tsx src/front-components/record-info.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - useUserId, - useRecordId, - useFrontComponentId, -} from 'twenty-sdk/front-component'; - -const RecordInfo = () => { - const userId = useUserId(); - const recordId = useRecordId(); - const componentId = useFrontComponentId(); - - return ( -
-

User: {userId}

-

Record: {recordId ?? 'No record context'}

-

Component: {componentId}

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', - name: 'record-info', - component: RecordInfo, -}); -``` - -Kullanılabilir hook'lar: - -| Hook | Döndürür | Açıklama | -| --------------------------------------------- | -------------------- | ------------------------------------------------------------- | -| `useUserId()` | `string` veya `null` | Geçerli kullanıcının ID'si | -| `useRecordId()` | `string` veya `null` | Geçerli kaydın ID'si (bir kayıt sayfasına yerleştirildiğinde) | -| `useFrontComponentId()` | `string` | Bu bileşen örneğinin ID'si | -| `useFrontComponentExecutionContext(selector)` | değişir | Bir seçici işlevle tam yürütme bağlamına erişin | - -#### Host iletişim API'si - -Ön uç bileşenleri, `twenty-sdk`'deki işlevleri kullanarak gezinmeyi, modalları ve bildirimleri tetikleyebilir: - -| Fonksiyon | Açıklama | -| ----------------------------------------------- | ---------------------------------- | -| `navigate(to, params?, queryParams?, options?)` | Uygulamada bir sayfaya git | -| `openSidePanelPage(params)` | Bir yan panel aç | -| `closeSidePanel()` | Yan paneli kapat | -| `openCommandConfirmationModal(params)` | Bir onay iletişim kutusu göster | -| `enqueueSnackbar(params)` | Bir toast bildirimi göster | -| `unmountFrontComponent()` | Bileşeni kaldır (unmount) | -| `updateProgress(progress)` | Bir ilerleme göstergesini güncelle | - -Bir eylem tamamlandıktan sonra bir snackbar göstermek ve yan paneli kapatmak için host API'sini kullanan bir örnek: - -```tsx src/front-components/archive-record.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { useRecordId } from 'twenty-sdk/front-component'; -import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; -import { CoreApiClient } from 'twenty-sdk/clients'; - -const ArchiveRecord = () => { - const recordId = useRecordId(); - - const handleArchive = async () => { - const client = new CoreApiClient(); - - await client.mutation({ - updateTask: { - __args: { id: recordId, data: { status: 'ARCHIVED' } }, - id: true, - }, - }); - - await enqueueSnackbar({ - message: 'Record archived', - variant: 'success', - }); - - await closeSidePanel(); - }; - - return ( -
-

Archive this record?

- -
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', - name: 'archive-record', - description: 'Archives the current record', - component: ArchiveRecord, -}); -``` - -#### Komut seçenekleri - -`defineFrontComponent` içine bir `command` alanı eklemek, bileşeni komut menüsüne (Cmd+K) kaydeder. `isPinned` `true` ise, sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak da görünür. - -| Alan | Zorunlu | Açıklama | -| --------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `universalIdentifier` | Evet | Komut için kararlı benzersiz kimlik | -| `label` | Evet | Komut menüsünde (Cmd+K) gösterilen tam etiket | -| `shortLabel` | Hayır | Sabitlenmiş hızlı işlem düğmesinde görüntülenen daha kısa etiket | -| `icon` | Hayır | Etiketin yanında görüntülenen simge adı (örn. `'IconBolt'`, `'IconSend'`) | -| `isPinned` | Hayır | `true` olduğunda, komutu sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak gösterir | -| `availabilityType` | Hayır | Komutun nerede görüneceğini kontrol eder: `'GLOBAL'` (her zaman kullanılabilir), `'RECORD_SELECTION'` (yalnızca kayıtlar seçiliyken) veya `'FALLBACK'` (başka hiçbir komut eşleşmediğinde gösterilir) | -| `availabilityObjectUniversalIdentifier` | Hayır | Komutu belirli bir nesne türünün sayfalarıyla sınırlandırın (örn. yalnızca Company kayıtlarında) | -| `conditionalAvailabilityExpression` | Hayır | Komutun görünür olup olmadığını dinamik olarak kontrol eden bir boolean ifade (aşağıya bakın) | - -#### Koşullu kullanılabilirlik ifadeleri - -`conditionalAvailabilityExpression` alanı, geçerli sayfa bağlamına göre bir komutun ne zaman görünür olacağını kontrol etmenizi sağlar. İfadeler oluşturmak için `twenty-sdk`'den türlendirilmiş değişkenleri ve operatörleri içe aktarın: - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - pageType, - numberOfSelectedRecords, - objectPermissions, - everyEquals, - isDefined, -} from 'twenty-sdk/front-component'; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'bulk-action', - component: BulkAction, - command: { - universalIdentifier: '...', - label: 'Bulk Update', - availabilityType: 'RECORD_SELECTION', - conditionalAvailabilityExpression: everyEquals( - objectPermissions, - 'canUpdateObjectRecords', - true, - ), - }, -}); -``` - -**Bağlam değişkenleri** — bunlar sayfanın mevcut durumunu temsil eder: - -| Değişken | Tür | Açıklama | -| ------------------------------ | --------- | ----------------------------------------------------------------- | -| `pageType` | `string` | Geçerli sayfa türü (örn. `'RecordIndexPage'`, `'RecordShowPage'`) | -| `isInSidePanel` | `boolean` | Bileşenin bir yan panelde oluşturulup oluşturulmadığı | -| `numberOfSelectedRecords` | `number` | Şu anda seçili kayıt sayısı | -| `isSelectAll` | `boolean` | "tümünü seç" seçeneğinin etkin olup olmadığı | -| `selectedRecords` | `array` | Seçili kayıt nesneleri | -| `favoriteRecordIds` | `array` | Favorilere eklenen kayıtların ID'leri | -| `objectPermissions` | `object` | Geçerli nesne türü için izinler | -| `targetObjectReadPermissions` | `object` | Hedef nesne için okuma izinleri | -| `targetObjectWritePermissions` | `object` | Hedef nesne için yazma izinleri | -| `featureFlags` | `object` | Etkin özellik bayrakları | -| `objectMetadataItem` | `object` | Geçerli nesne türünün üst verileri | -| `hasAnySoftDeleteFilterOnView` | `boolean` | Geçerli görünümde soft-delete filtresi olup olmadığı | - -**Operatörler** — değişkenleri boolean ifadelere dönüştürmek için birleştirin: - -| Operatör | Açıklama | -| ----------------------------------- | --------------------------------------------------------- | -| `isDefined(value)` | Değer null/undefined değilse `true` | -| `isNonEmptyString(value)` | Değer boş olmayan bir string ise `true` | -| `includes(array, value)` | Dizi değeri içeriyorsa `true` | -| `includesEvery(array, prop, value)` | Her bir öğenin özelliği değeri içeriyorsa `true` | -| `every(array, prop)` | Özellik her öğede truthy ise `true` | -| `everyDefined(array, prop)` | Özellik her öğede tanımlıysa `true` | -| `everyEquals(array, prop, value)` | Özellik her öğede değere eşitse `true` | -| `some(array, prop)` | Özellik en az bir öğede truthy ise `true` | -| `someDefined(array, prop)` | Özellik en az bir öğede tanımlıysa `true` | -| `someEquals(array, prop, value)` | Özellik en az bir öğede değere eşitse `true` | -| `someNonEmptyString(array, prop)` | Özellik en az bir öğede boş olmayan bir string ise `true` | -| `none(array, prop)` | Özellik her öğede falsy ise `true` | -| `noneDefined(array, prop)` | Özellik her öğede tanımsızsa `true` | -| `noneEquals(array, prop, value)` | Özellik hiçbir öğede değere eşit değilse `true` | - -#### Genel varlıklar - -Ön uç bileşenleri, `getPublicAssetUrl` kullanarak uygulamanın `public/` dizinindeki dosyalara erişebilir: - -```tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -const Logo = () => Logo; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'logo', - component: Logo, -}); -``` - -Ayrıntılar için [genel varlıklar bölümüne](#accessing-public-assets-with-getpublicasseturl) bakın. - -#### Stil - -Ön uç bileşenleri birden fazla biçimlendirme yaklaşımını destekler. Şunları kullanabilirsiniz: - -* **Satır içi stiller** — `style={{ color: 'red' }}` -* **Twenty UI bileşenleri** — `twenty-sdk/ui` içinden içe aktarın (Button, Tag, Status, Chip, Avatar ve daha fazlası) -* **Emotion** — `@emotion/react` ile CSS-in-JS -* **Styled-components** — `styled.div` kalıpları -* **Tailwind CSS** — yardımcı sınıflar -* **React ile uyumlu herhangi bir CSS-in-JS kitaplığı** - -```tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { Button, Tag, Status } from 'twenty-sdk/ui'; - -const StyledWidget = () => { - return ( -
-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', - name: 'styled-widget', - component: StyledWidget, -}); -``` - -
- - - -Yetenekler, yapay zekâ ajanlarının çalışma alanınızda kullanabileceği yeniden kullanılabilir yönergeleri ve kabiliyetleri tanımlar. Yerleşik doğrulamayla yetenekleri tanımlamak için `defineSkill()` kullanın: - -```ts src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk/define'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -Önemli noktalar: -* `name`, yetenek için benzersiz bir tanımlayıcı dizedir (kebab-case önerilir). -* `label`, UI'de gösterilen, insan tarafından okunabilir addır. -* `content`, yetenek yönergelerini içerir — bu, yapay zekâ ajanının kullandığı metindir. -* `icon` (isteğe bağlı), UI'de gösterilen simgeyi ayarlar. -* `description` (isteğe bağlı), yeteneğin amacı hakkında ek bağlam sağlar. - - - - -Ajanlar, çalışma alanınız içinde bulunan yapay zekâ asistanlarıdır. Özel bir sistem istemiyle ajanlar oluşturmak için `defineAgent()` kullanın: - -```ts src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk/define'; - -export default defineAgent({ - universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'Helps the sales team draft outreach emails and research prospects', - icon: 'IconRobot', - prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', -}); -``` - -Önemli noktalar: -* `name`, ajan için benzersiz bir tanımlayıcı dizedir (kebab-case önerilir). -* `label`, UI'de gösterilen görünen addır. -* `prompt`, ajanın davranışını tanımlayan sistem istemidir. -* `description` (isteğe bağlı), ajanın ne yaptığı hakkında bağlam sağlar. -* `icon` (isteğe bağlı), UI'de gösterilen simgeyi ayarlar. -* `modelId` (isteğe bağlı), ajanın kullandığı varsayılan yapay zekâ modelini geçersiz kılar. - - - - -Görünümler, bir nesnenin kayıtlarının nasıl görüntüleneceğine ilişkin kaydedilmiş yapılandırmalardır — hangi alanların görünür olacağını, sıralarını ve uygulanan filtreleri veya grupları içerir. Uygulamanızla önceden yapılandırılmış görünümler sunmak için `defineView()` kullanın: - -```ts src/views/example-view.ts -import { defineView, ViewKey } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; - -export default defineView({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'All example items', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - icon: 'IconList', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', - fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 200, - }, - ], -}); -``` - -Önemli noktalar: -* `objectUniversalIdentifier`, bu görünümün hangi nesneye uygulanacağını belirtir. -* `key`, görünüm türünü belirler (ör. ana liste görünümü için `ViewKey.INDEX`). -* `fields`, hangi sütunların görüneceğini ve sıralarını kontrol eder. Her alan bir `fieldMetadataUniversalIdentifier` öğesine referans verir. -* Daha gelişmiş yapılandırmalar için `filters`, `filterGroups`, `groups` ve `fieldGroups` de tanımlayabilirsiniz. -* `position`, aynı nesne için birden fazla görünüm olduğunda sıralamayı kontrol eder. - - - - -Gezinme menüsü öğeleri, çalışma alanı kenar çubuğuna özel girişler ekler. Görünümlere, harici URL'lere veya nesnelere bağlanmak için `defineNavigationMenuItem()` kullanın: - -```ts src/navigation-menu-items/example-navigation-menu-item.ts -import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; -import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; - -export default defineNavigationMenuItem({ - universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', - name: 'example-navigation-menu-item', - icon: 'IconList', - color: 'blue', - position: 0, - type: NavigationMenuItemType.VIEW, - viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, -}); -``` - -Önemli noktalar: -* `type`, menü öğesinin neye bağlanacağını belirler: kaydedilmiş bir görünüm için `NavigationMenuItemType.VIEW` veya harici bir URL için `NavigationMenuItemType.LINK`. -* Görünüm bağlantıları için `viewUniversalIdentifier` ayarlayın. Harici bağlantılar için `link` ayarlayın. -* `position`, kenar çubuğundaki sıralamayı kontrol eder. -* `icon` ve `color` (isteğe bağlı) görünümü özelleştirir. - - - - -Sayfa düzenleri, bir kayıt ayrıntı sayfasının nasıl görüneceğini özelleştirmenizi sağlar — hangi sekmelerin görüneceği, her sekmenin içinde hangi widget'ların olacağı ve bunların nasıl düzenleneceği. Uygulamanızla özel düzenler sunmak için `definePageLayout()` kullanın: - -```ts src/page-layouts/example-record-page-layout.ts -import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; - -export default definePageLayout({ - universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', - name: 'Example Record Page', - type: 'RECORD_PAGE', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - tabs: [ - { - universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', - title: 'Hello World', - position: 50, - icon: 'IconWorld', - layoutMode: PageLayoutTabLayoutMode.CANVAS, - widgets: [ - { - universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', - title: 'Hello World', - type: 'FRONT_COMPONENT', - configuration: { - configurationType: 'FRONT_COMPONENT', - frontComponentUniversalIdentifier: - HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, - }, - }, - ], - }, - ], -}); -``` - -Önemli noktalar: -* `type` genellikle belirli bir nesnenin ayrıntı görünümünü özelleştirmek için `'RECORD_PAGE'` olur. -* `objectUniversalIdentifier`, bu düzenin hangi nesneye uygulanacağını belirtir. -* Her `tab`, bir `title`, `position` ve `layoutMode` ile sayfanın bir bölümünü tanımlar (serbest biçimli düzen için `CANVAS`). -* Bir sekmenin içindeki her `widget`, bir ön uç bileşeni, bir ilişki listesi veya diğer yerleşik widget türlerini oluşturabilir. -* Sekmelerdeki `position`, sıralarını kontrol eder. Özel sekmeleri yerleşik olanların sonrasına yerleştirmek için daha yüksek değerler kullanın (ör. 50). - - -
- -## Genel varlıklar (`public/` klasörü) - -Uygulamanızın kökündeki `public/` klasörü, statik dosyaları barındırır — görseller, simgeler, yazı tipleri veya uygulamanızın çalışma zamanında ihtiyaç duyduğu diğer varlıklar. Bu dosyalar derlemelere otomatik olarak dahil edilir, geliştirme modunda senkronize edilir ve sunucuya yüklenir. - -`public/` içine yerleştirilen dosyalar şunlardır: - -* **Herkese açık olarak erişilebilir** — sunucuya senkronize edildikten sonra varlıklar genel bir URL'den sunulur. Onlara erişmek için kimlik doğrulama gerekmez. -* **Ön uç bileşenlerinde kullanılabilir** — React bileşenlerinizin içinde görseller, simgeler veya herhangi bir medyayı göstermek için varlık URL'lerini kullanın. -* **Mantık işlevlerinde kullanılabilir** — e-postalarda, API yanıtlarında veya herhangi bir sunucu tarafı mantıkta varlık URL'lerine referans verin. -* **Pazar yeri üst verileri için kullanılır** — `defineApplication()` içindeki `logoUrl` ve `screenshots` alanları bu klasördeki dosyalara referans verir (örn. `public/logo.png`). Bunlar, uygulamanız yayımlandığında pazar yerinde görüntülenir. -* **Geliştirme modunda otomatik senkronize edilir** — `public/` içinde bir dosya eklediğinizde, güncellediğinizde veya sildiğinizde otomatik olarak sunucuya senkronize edilir. Yeniden başlatma gerekmez. -* **Derlemelere dahil edilir** — `yarn twenty build`, tüm genel varlıkları dağıtım çıktısına paketler. - -### `getPublicAssetUrl` ile genel varlıklara erişme - -`twenty-sdk` içindeki `getPublicAssetUrl` yardımcı işlevini kullanarak `public/` dizininizdeki bir dosyanın tam URL'sini alın. Hem **mantık işlevlerinde** hem de **ön uç bileşenlerinde** çalışır. - -**Bir mantık işlevinde:** - -```ts src/logic-functions/send-invoice.ts -import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; - -const handler = async (): Promise => { - const logoUrl = getPublicAssetUrl('logo.png'); - const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); - - // Fetch the file content (no auth required — public endpoint) - const response = await fetch(invoiceUrl); - const buffer = await response.arrayBuffer(); - - return { logoUrl, size: buffer.byteLength }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'a1b2c3d4-...', - name: 'send-invoice', - description: 'Sends an invoice with the app logo', - timeoutSeconds: 10, - handler, -}); -``` - -**Bir ön uç bileşeninde:** - -```tsx src/front-components/company-card.tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; - -export default defineFrontComponent(() => { - const logoUrl = getPublicAssetUrl('logo.png'); - - return App logo; -}); -``` - -`path` bağımsız değişkeni, uygulamanızın `public/` klasörüne göre görelidir. Hem `getPublicAssetUrl('logo.png')` hem de `getPublicAssetUrl('public/logo.png')` aynı URL'ye çözümlenir — `public/` öneki varsa otomatik olarak kaldırılır. - -## npm paketlerini kullanma - -Uygulamanızda herhangi bir npm paketini yükleyip kullanabilirsiniz. Hem mantık işlevleri hem de ön uç bileşenleri, tüm bağımlılıkları çıktıya satır içi olarak ekleyen [esbuild](https://esbuild.github.io/) ile paketlenir — çalışma zamanında `node_modules` gerekmez. - -### Bir paketi yükleme - -```bash filename="Terminal" -yarn add axios -``` - -Ardından kodunuza içe aktarın: - -```ts src/logic-functions/fetch-data.ts -import { defineLogicFunction } from 'twenty-sdk/define'; -import axios from 'axios'; - -const handler = async (): Promise => { - const { data } = await axios.get('https://api.example.com/data'); - - return { data }; -}; - -export default defineLogicFunction({ - universalIdentifier: '...', - name: 'fetch-data', - description: 'Fetches data from an external API', - timeoutSeconds: 10, - handler, -}); -``` - -Aynısı ön uç bileşenleri için de geçerlidir: - -```tsx src/front-components/chart.tsx -import { defineFrontComponent } from 'twenty-sdk/define'; -import { format } from 'date-fns'; - -const DateWidget = () => { - return

Today is {format(new Date(), 'MMMM do, yyyy')}

; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### Paketleme nasıl çalışır - -Derleme adımı, her mantık işlevi ve her ön uç bileşeni için tek bir bağımsız dosya üretmek üzere esbuild kullanır. Tüm içe aktarılan paketler pakete satır içi eklenir. - -**Mantık işlevleri**, Node.js ortamında çalışır. Node yerleşik modülleri (`fs`, `path`, `crypto`, `http` vb.) kullanılabilir ve kurulmaları gerekmez. - -**Ön uç bileşenleri**, bir Web Worker içinde çalışır. Node'un yerleşik modülleri **kullanılamaz** — yalnızca tarayıcı ortamında çalışan tarayıcı API'leri ve npm paketleri kullanılabilir. - -Her iki ortamda da `twenty-client-sdk/core` ve `twenty-client-sdk/metadata` önceden sağlanmış modüller olarak mevcuttur — bunlar paketlenmez, ancak çalışma zamanında sunucu tarafından çözülür. - -## `yarn twenty add` ile varlıklar için iskelet oluşturma - -Varlık dosyalarını elle oluşturmak yerine etkileşimli iskelet oluşturucuyu kullanabilirsiniz: - -```bash filename="Terminal" -yarn twenty add -``` - -Bu, bir varlık türü seçmenizi ister ve gerekli alanlar boyunca size yol gösterir. Kararlı bir `universalIdentifier` ve doğru `defineEntity()` çağrısıyla kullanıma hazır bir dosya üretir. - -İlk istemi atlamak için varlık türünü doğrudan da geçebilirsiniz: - -```bash filename="Terminal" -yarn twenty add object -yarn twenty add logicFunction -yarn twenty add frontComponent -``` - -### Kullanılabilir varlık türleri - -| Varlık türü | Komut | Oluşturulan dosya | -| -------------------- | ------------------------------------ | ------------------------------------------------------- | -| Nesne | `yarn twenty add object` | `src/objects/\.ts` | -| Alan | `yarn twenty add field` | `src/fields/\.ts` | -| Mantık işlevi | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | -| Ön uç bileşeni | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | -| Rol | `yarn twenty add role` | `src/roles/\.ts` | -| Beceri | `yarn twenty add skill` | `src/skills/\.ts` | -| Temsilci | `yarn twenty add agent` | `src/agents/\.ts` | -| Görünüm | `yarn twenty add view` | `src/views/\.ts` | -| Gezinme menüsü öğesi | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | -| Sayfa düzeni | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | - -### İskelet oluşturucunun ürettikleri - -Her varlık türünün kendi şablonu vardır. Örneğin, `yarn twenty add object` şunları sorar: - -1. **Ad (tekil)** — ör. `invoice` -2. **Ad (çoğul)** — ör. `invoices` -3. **Etiket (tekil)** — adından otomatik doldurulur (ör. `Invoice`) -4. **Etiket (çoğul)** — otomatik doldurulur (ör. `Invoices`) -5. **Bir görünüm ve gezinme öğesi oluşturulsun mu?** — evet derseniz, iskelet oluşturucu yeni nesne için eşleşen bir görünüm ve kenar çubuğu bağlantısı da üretir. - -Diğer varlık türlerinin istemleri daha basittir — çoğu yalnızca bir ad sorar. - -`field` varlık türü daha ayrıntılıdır: alan adını, etiketi, türü (`TEXT`, `NUMBER`, `SELECT`, `RELATION` vb. gibi mevcut tüm alan türlerinin listesinden) ve hedef nesnenin `universalIdentifier` değerini sorar. - -### Özel çıktı yolu - -`--path` bayrağını kullanarak oluşturulan dosyayı özel bir konuma yerleştirin: - -```bash filename="Terminal" -yarn twenty add logicFunction --path src/custom-folder -``` - -## Tipli API istemcileri (twenty-client-sdk) - -`twenty-client-sdk` paketi, mantık fonksiyonlarınızdan ve ön uç bileşenlerinizden Twenty API ile etkileşim kurmak için tip tanımlı iki GraphQL istemcisi sağlar. - -| İstemci | İçe Aktar | Uç nokta | Oluşturuldu mu? | -| ------------------- | ---------------------------- | ------------------------------------------------------------- | --------------------------------------- | -| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — çalışma alanı verileri (kayıtlar, nesneler) | Evet, geliştirme/derleme zamanında | -| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — çalışma alanı yapılandırması, dosya yüklemeleri | Hayır, önceden hazırlanmış olarak gelir | - - - - -`CoreApiClient`, çalışma alanı verilerini sorgulamak ve değiştirmek için ana istemcidir. `yarn twenty dev` veya `yarn twenty build` sırasında **çalışma alanı şemanızdan oluşturulur**, bu nedenle nesnelerinize ve alanlarınıza uyacak şekilde tamamen tiplenmiştir. - -```ts -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const client = new CoreApiClient(); - -// Query records -const { companies } = await client.query({ - companies: { - edges: { - node: { - id: true, - name: true, - domainName: { - primaryLinkLabel: true, - primaryLinkUrl: true, - }, - }, - }, - }, -}); - -// Create a record -const { createCompany } = await client.mutation({ - createCompany: { - __args: { - data: { - name: 'Acme Corp', - }, - }, - id: true, - name: true, - }, -}); -``` - -İstemci bir seçim kümesi sözdizimi kullanır: Bir alanı dahil etmek için `true` geçin, bağımsız değişkenler için `__args` kullanın ve ilişkiler için nesneleri iç içe yerleştirin. Çalışma alanı şemanıza göre tam otomatik tamamlama ve tip denetimi elde edersiniz. - - -**CoreApiClient geliştirme/derleme zamanında oluşturulur.** Bunu önce `yarn twenty dev` veya `yarn twenty build` çalıştırmadan kullanırsanız, bir hata verir. Oluşturma otomatik olarak gerçekleşir — CLI, çalışma alanınızın GraphQL şemasını inceler ve `@genql/cli` kullanarak tiplenmiş bir istemci üretir. - - -#### Tür açıklamaları için CoreSchema'yı kullanma - -`CoreSchema`, çalışma alanı nesnelerinize uyan TypeScript türleri sağlar — bileşen durumunu veya işlev parametrelerini tiplemek için kullanışlıdır: - -```ts -import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; -import { useState } from 'react'; - -const [company, setCompany] = useState< - Pick | undefined ->(undefined); - -const client = new CoreApiClient(); -const result = await client.query({ - company: { - __args: { filter: { position: { eq: 1 } } }, - id: true, - name: true, - }, -}); -setCompany(result.company); -``` - - - - -`MetadataApiClient`, SDK ile birlikte önceden hazırlanmış olarak gelir (oluşturma gerektirmez). Çalışma alanı yapılandırması, uygulamalar ve dosya yüklemeleri için `/metadata` uç noktasını sorgular. - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const metadataClient = new MetadataApiClient(); - -// List first 10 objects in the workspace -const { objects } = await metadataClient.query({ - objects: { - edges: { - node: { - id: true, - nameSingular: true, - namePlural: true, - labelSingular: true, - isCustom: true, - }, - }, - __args: { - filter: {}, - paging: { first: 10 }, - }, - }, -}); -``` - -#### Dosya yükleme - -`MetadataApiClient`, dosya türü alanlara dosya eklemek için bir `uploadFile` yöntemi içerir: - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -| Parametre | Tür | Açıklama | -| ---------------------------------- | -------- | --------------------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | Dosyanın ham içeriği | -| `filename` | `string` | Dosyanın adı (depolama ve görüntüleme için kullanılır) | -| `contentType` | `string` | MIME türü (belirtilmezse varsayılan olarak `application/octet-stream` kullanılır) | -| `fieldMetadataUniversalIdentifier` | `string` | Nesnenizdeki dosya türü alanının `universalIdentifier` değeri | - -Önemli noktalar: -* Alan için `universalIdentifier` kullanır (çalışma alanına özgü kimliği değil), böylece yükleme kodunuz uygulamanızın yüklü olduğu herhangi bir çalışma alanında çalışır. -* Döndürülen `url`, yüklenen dosyaya erişmek için kullanabileceğiniz imzalı bir URL'dir. - - - - - - Kodunuz Twenty üzerinde çalıştığında (mantık işlevleri veya ön uç bileşenleri), platform kimlik bilgilerini ortam değişkenleri olarak enjekte eder: - - * `TWENTY_API_URL` — Twenty API'nin temel URL'si - * `TWENTY_APP_ACCESS_TOKEN` — Uygulamanızın varsayılan fonksiyon rolü kapsamında kısa ömürlü anahtar - - Bunları istemcilere iletmeniz gerekmez — otomatik olarak `process.env`'den okurlar. API anahtarının izinleri, `application-config.ts` içinde `defaultRoleUniversalIdentifier` ile referans verilen role göre belirlenir. - - -## Uygulamanızı test etme - -SDK, test kodundan uygulamanızı derlemenize, dağıtmanıza, yüklemenize ve kaldırmanıza olanak tanıyan programatik API'ler sağlar. Tiplenmiş API istemcileriyle birlikte [Vitest](https://vitest.dev/) kullanarak, uygulamanızın gerçek bir Twenty sunucusunda uçtan uca çalıştığını doğrulayan entegrasyon testleri yazabilirsiniz. - -### Kurulum - -İskelet aracıyla oluşturulan uygulama zaten Vitest'i içerir. Manuel kurulum yaparsanız, bağımlılıkları yükleyin: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Uygulamanızın kök dizininde bir `vitest.config.ts` oluşturun: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Testler çalışmadan önce sunucuya erişilebildiğini doğrulayan bir kurulum dosyası oluşturun: - -```ts src/__tests__/setup-test.ts -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { beforeAll } from 'vitest'; - -const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; -const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### Programatik SDK API'leri - -`twenty-sdk/cli` alt yolu, test kodundan doğrudan çağırabileceğiniz fonksiyonları dışa aktarır: - -| Fonksiyon | Açıklama | -| -------------- | ----------------------------------------------------------------- | -| `appBuild` | Uygulamayı derleyin ve isteğe bağlı olarak bir tarball paketleyin | -| `appDeploy` | Bir tarball'ı sunucuya yükleyin | -| `appInstall` | Uygulamayı etkin çalışma alanına yükleyin | -| `appUninstall` | Uygulamayı etkin çalışma alanından kaldırın | - -Her fonksiyon, `success: boolean` ile birlikte `data` veya `error` içeren bir sonuç nesnesi döndürür. - -### Bir entegrasyon testi yazma - -İşte uygulamayı derleyen, dağıtan ve yükleyen; ardından çalışma alanında göründüğünü doğrulayan tam bir örnek: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Testleri çalıştırma - -Yerel Twenty sunucunuzun çalıştığından emin olun, ardından: - -```bash filename="Terminal" -yarn test -``` - -Veya geliştirme sırasında izleme modunda: - -```bash filename="Terminal" -yarn test:watch -``` - -### Tip denetimi - -Ayrıca testleri çalıştırmadan uygulamanızda tip denetimi çalıştırabilirsiniz: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -Bu, `tsc --noEmit` komutunu çalıştırır ve tüm tip hatalarını raporlar. - -## CLI başvurusu - -`dev`, `build`, `add` ve `typecheck` dışında CLI, fonksiyonları çalıştırma, günlükleri görüntüleme ve uygulama kurulumlarını yönetme komutları sağlar. - -### Fonksiyonları çalıştırma (`yarn twenty exec`) - -Bir mantık fonksiyonunu HTTP, cron veya veritabanı olayıyla tetiklemeden manuel olarak çalıştırın: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute the post-install function -yarn twenty exec --postInstall -``` - -### Fonksiyon günlüklerini görüntüleme (`yarn twenty logs`) - -Uygulamanızın mantık fonksiyonlarının yürütme günlüklerini akış olarak alın: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - - -Bu, Docker konteyner günlüklerini gösteren `yarn twenty server logs` komutundan farklıdır. `yarn twenty logs`, uygulamanızın fonksiyon yürütme günlüklerini Twenty sunucusundan gösterir. - - -### Bir uygulamayı kaldırma (`yarn twenty uninstall`) - -Uygulamanızı etkin çalışma alanından kaldırın: - -```bash filename="Terminal" -yarn twenty uninstall - -# Skip the confirmation prompt -yarn twenty uninstall --yes -``` - -## Uzakları yönetme - -Bir **uzak**, uygulamanızın bağlandığı Twenty sunucusudur. Kurulum sırasında iskelet oluşturucu sizin için otomatik olarak bir tane oluşturur. Dilediğiniz zaman daha fazla uzak ekleyebilir veya aralarında geçiş yapabilirsiniz. - -```bash filename="Terminal" -# Add a new remote (opens a browser for OAuth login) -yarn twenty remote add - -# Connect to a local Twenty server (auto-detects port 2020 or 3000) -yarn twenty remote add --local - -# Add a remote non-interactively (useful for CI) -yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote - -# List all configured remotes -yarn twenty remote list - -# Switch the active remote -yarn twenty remote switch -``` - -Kimlik bilgileriniz `~/.twenty/config.json` içinde saklanır. - -## GitHub Actions ile CI - -İskelet oluşturucu, `.github/workflows/ci.yml` konumunda kullanıma hazır bir GitHub Actions iş akışı üretir. Entegrasyon testlerinizi `main` dalına yapılan her itmede ve çekme isteklerinde otomatik olarak çalıştırır. - -İş akışı: - -1. Kodunuzu çalışma alanına alır -2. `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` eylemini kullanarak geçici bir Twenty sunucusu başlatır -3. `yarn install --immutable` ile bağımlılıkları kurar -4. Eylem çıktılarından enjekte edilen `TWENTY_API_URL` ve `TWENTY_API_KEY` ile `yarn test` çalıştırır - -```yaml .github/workflows/ci.yml -name: CI - -on: - push: - branches: - - main - pull_request: {} - -env: - TWENTY_VERSION: latest - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Spawn Twenty instance - id: twenty - uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main - with: - twenty-version: ${{ env.TWENTY_VERSION }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Enable Corepack - run: corepack enable - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - - name: Install dependencies - run: yarn install --immutable - - - name: Run integration tests - run: yarn test - env: - TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} - TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} -``` - -Herhangi bir gizli değişken yapılandırmanız gerekmez — `spawn-twenty-docker-image` eylemi, koşucu içinde doğrudan geçici bir Twenty sunucusu başlatır ve bağlantı ayrıntılarını çıktı olarak verir. `GITHUB_TOKEN` gizli değişkeni GitHub tarafından otomatik olarak sağlanır. - -`latest` yerine belirli bir Twenty sürümünü sabitlemek için iş akışının başındaki `TWENTY_VERSION` ortam değişkenini değiştirin. +* **`yarn twenty dev`** — watches your source files and live-syncs changes to a connected Twenty server. The typed API client is regenerated automatically when the schema changes. +* **`yarn twenty build`** — compiles TypeScript, bundles logic functions and front components with esbuild, and produces a manifest. +* **Pre/post-install hooks** — optional logic functions that run during installation. See [Logic Functions](/l/tr/developers/extend/apps/logic-functions) for details. + +## Sonraki adımlar + + + + Define objects, fields, roles, and relations. + + + Server-side functions with HTTP, cron, and event triggers. + + + Sandboxed React components inside Twenty's UI. + + + Views, navigation items, and record page layouts. + + + AI skills and agents with custom prompts. + + + CLI commands, testing, assets, remotes, and CI. + + + Deploy to a server or publish to the marketplace. + + diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/cli-and-testing.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/cli-and-testing.mdx new file mode 100644 index 00000000000..326305af3d2 --- /dev/null +++ b/packages/twenty-docs/l/tr/developers/extend/apps/cli-and-testing.mdx @@ -0,0 +1,434 @@ +--- +title: CLI & Testing +description: CLI commands, testing setup, public assets, npm packages, remotes, and CI configuration. +icon: terminal +--- + +## Genel varlıklar (`public/` klasörü) + +Uygulamanızın kökündeki `public/` klasörü, statik dosyaları barındırır — görseller, simgeler, yazı tipleri veya uygulamanızın çalışma zamanında ihtiyaç duyduğu diğer varlıklar. Bu dosyalar derlemelere otomatik olarak dahil edilir, geliştirme modunda senkronize edilir ve sunucuya yüklenir. + +`public/` içine yerleştirilen dosyalar şunlardır: + +* **Herkese açık olarak erişilebilir** — sunucuya senkronize edildikten sonra varlıklar genel bir URL'den sunulur. Onlara erişmek için kimlik doğrulama gerekmez. +* **Ön uç bileşenlerinde kullanılabilir** — React bileşenlerinizin içinde görseller, simgeler veya herhangi bir medyayı göstermek için varlık URL'lerini kullanın. +* **Mantık işlevlerinde kullanılabilir** — e-postalarda, API yanıtlarında veya herhangi bir sunucu tarafı mantıkta varlık URL'lerine referans verin. +* **Pazar yeri üst verileri için kullanılır** — `defineApplication()` içindeki `logoUrl` ve `screenshots` alanları bu klasördeki dosyalara referans verir (örn. `public/logo.png`). Bunlar, uygulamanız yayımlandığında pazar yerinde görüntülenir. +* **Geliştirme modunda otomatik senkronize edilir** — `public/` içinde bir dosya eklediğinizde, güncellediğinizde veya sildiğinizde otomatik olarak sunucuya senkronize edilir. Yeniden başlatma gerekmez. +* **Derlemelere dahil edilir** — `yarn twenty build`, tüm genel varlıkları dağıtım çıktısına paketler. + +### `getPublicAssetUrl` ile genel varlıklara erişme + +`twenty-sdk` içindeki `getPublicAssetUrl` yardımcı işlevini kullanarak `public/` dizininizdeki bir dosyanın tam URL'sini alın. Hem **mantık işlevlerinde** hem de **ön uç bileşenlerinde** çalışır. + +**Bir mantık işlevinde:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; + +const handler = async (): Promise => { + const logoUrl = getPublicAssetUrl('logo.png'); + const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); + + // Fetch the file content (no auth required — public endpoint) + const response = await fetch(invoiceUrl); + const buffer = await response.arrayBuffer(); + + return { logoUrl, size: buffer.byteLength }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'a1b2c3d4-...', + name: 'send-invoice', + description: 'Sends an invoice with the app logo', + timeoutSeconds: 10, + handler, +}); +``` + +**Bir ön uç bileşeninde:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +`path` bağımsız değişkeni, uygulamanızın `public/` klasörüne göre görelidir. Hem `getPublicAssetUrl('logo.png')` hem de `getPublicAssetUrl('public/logo.png')` aynı URL'ye çözümlenir — `public/` öneki varsa otomatik olarak kaldırılır. + +## npm paketlerini kullanma + +Uygulamanızda herhangi bir npm paketini yükleyip kullanabilirsiniz. Hem mantık işlevleri hem de ön uç bileşenleri, tüm bağımlılıkları çıktıya satır içi olarak ekleyen [esbuild](https://esbuild.github.io/) ile paketlenir — çalışma zamanında `node_modules` gerekmez. + +### Bir paketi yükleme + +```bash filename="Terminal" +yarn add axios +``` + +Ardından kodunuza içe aktarın: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +Aynısı ön uç bileşenleri için de geçerlidir: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### Paketleme nasıl çalışır + +Derleme adımı, her mantık işlevi ve her ön uç bileşeni için tek bir bağımsız dosya üretmek üzere esbuild kullanır. Tüm içe aktarılan paketler pakete satır içi eklenir. + +**Mantık işlevleri**, Node.js ortamında çalışır. Node yerleşik modülleri (`fs`, `path`, `crypto`, `http` vb.) kullanılabilir ve kurulmaları gerekmez. + +**Ön uç bileşenleri**, bir Web Worker içinde çalışır. Node'un yerleşik modülleri **kullanılamaz** — yalnızca tarayıcı ortamında çalışan tarayıcı API'leri ve npm paketleri kullanılabilir. + +Her iki ortamda da `twenty-client-sdk/core` ve `twenty-client-sdk/metadata` önceden sağlanmış modüller olarak mevcuttur — bunlar paketlenmez, ancak çalışma zamanında sunucu tarafından çözülür. + +## Uygulamanızı test etme + +SDK, test kodundan uygulamanızı derlemenize, dağıtmanıza, yüklemenize ve kaldırmanıza olanak tanıyan programatik API'ler sağlar. Tiplenmiş API istemcileriyle birlikte [Vitest](https://vitest.dev/) kullanarak, uygulamanızın gerçek bir Twenty sunucusunda uçtan uca çalıştığını doğrulayan entegrasyon testleri yazabilirsiniz. + +### Kurulum + +İskelet aracıyla oluşturulan uygulama zaten Vitest'i içerir. Manuel kurulum yaparsanız, bağımlılıkları yükleyin: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +Uygulamanızın kök dizininde bir `vitest.config.ts` oluşturun: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +Testler çalışmadan önce sunucuya erişilebildiğini doğrulayan bir kurulum dosyası oluşturun: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### Programatik SDK API'leri + +`twenty-sdk/cli` alt yolu, test kodundan doğrudan çağırabileceğiniz fonksiyonları dışa aktarır: + +| Fonksiyon | Açıklama | +| -------------- | ----------------------------------------------------------------- | +| `appBuild` | Uygulamayı derleyin ve isteğe bağlı olarak bir tarball paketleyin | +| `appDeploy` | Bir tarball'ı sunucuya yükleyin | +| `appInstall` | Uygulamayı etkin çalışma alanına yükleyin | +| `appUninstall` | Uygulamayı etkin çalışma alanından kaldırın | + +Her fonksiyon, `success: boolean` ile birlikte `data` veya `error` içeren bir sonuç nesnesi döndürür. + +### Bir entegrasyon testi yazma + +İşte uygulamayı derleyen, dağıtan ve yükleyen; ardından çalışma alanında göründüğünü doğrulayan tam bir örnek: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### Testleri çalıştırma + +Yerel Twenty sunucunuzun çalıştığından emin olun, ardından: + +```bash filename="Terminal" +yarn test +``` + +Veya geliştirme sırasında izleme modunda: + +```bash filename="Terminal" +yarn test:watch +``` + +### Tip denetimi + +Ayrıca testleri çalıştırmadan uygulamanızda tip denetimi çalıştırabilirsiniz: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +Bu, `tsc --noEmit` komutunu çalıştırır ve tüm tip hatalarını raporlar. + +## CLI başvurusu + +`dev`, `build`, `add` ve `typecheck` dışında CLI, fonksiyonları çalıştırma, günlükleri görüntüleme ve uygulama kurulumlarını yönetme komutları sağlar. + +### Fonksiyonları çalıştırma (`yarn twenty exec`) + +Bir mantık fonksiyonunu HTTP, cron veya veritabanı olayıyla tetiklemeden manuel olarak çalıştırın: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute the post-install function +yarn twenty exec --postInstall +``` + +### Fonksiyon günlüklerini görüntüleme (`yarn twenty logs`) + +Uygulamanızın mantık fonksiyonlarının yürütme günlüklerini akış olarak alın: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +Bu, Docker konteyner günlüklerini gösteren `yarn twenty server logs` komutundan farklıdır. `yarn twenty logs`, uygulamanızın fonksiyon yürütme günlüklerini Twenty sunucusundan gösterir. + + +### Bir uygulamayı kaldırma (`yarn twenty uninstall`) + +Uygulamanızı etkin çalışma alanından kaldırın: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` + +## Uzakları yönetme + +Bir **uzak**, uygulamanızın bağlandığı Twenty sunucusudur. Kurulum sırasında iskelet oluşturucu sizin için otomatik olarak bir tane oluşturur. Dilediğiniz zaman daha fazla uzak ekleyebilir veya aralarında geçiş yapabilirsiniz. + +```bash filename="Terminal" +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add + +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local + +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote + +# List all configured remotes +yarn twenty remote list + +# Switch the active remote +yarn twenty remote switch +``` + +Kimlik bilgileriniz `~/.twenty/config.json` içinde saklanır. + +## GitHub Actions ile CI + +İskelet oluşturucu, `.github/workflows/ci.yml` konumunda kullanıma hazır bir GitHub Actions iş akışı üretir. Entegrasyon testlerinizi `main` dalına yapılan her itmede ve çekme isteklerinde otomatik olarak çalıştırır. + +İş akışı: + +1. Kodunuzu çalışma alanına alır +2. `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` eylemini kullanarak geçici bir Twenty sunucusu başlatır +3. `yarn install --immutable` ile bağımlılıkları kurar +4. Eylem çıktılarından enjekte edilen `TWENTY_API_URL` ve `TWENTY_API_KEY` ile `yarn test` çalıştırır + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +Herhangi bir gizli değişken yapılandırmanız gerekmez — `spawn-twenty-docker-image` eylemi, koşucu içinde doğrudan geçici bir Twenty sunucusu başlatır ve bağlantı ayrıntılarını çıktı olarak verir. `GITHUB_TOKEN` gizli değişkeni GitHub tarafından otomatik olarak sağlanır. + +`latest` yerine belirli bir Twenty sürümünü sabitlemek için iş akışının başındaki `TWENTY_VERSION` ortam değişkenini değiştirin. diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/data-model.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/data-model.mdx new file mode 100644 index 00000000000..e62aa573890 --- /dev/null +++ b/packages/twenty-docs/l/tr/developers/extend/apps/data-model.mdx @@ -0,0 +1,494 @@ +--- +title: Veri modeli +description: Define objects, fields, roles, and application metadata with the Twenty SDK. +icon: database +--- + +The `twenty-sdk` package provides `defineEntity` functions to declare your app's data model. SDK'nin varlıklarınızı algılayabilmesi için `export default defineEntity({...})` kullanmanız gerekir. Bu fonksiyonlar, derleme zamanında yapılandırmanızı doğrular ve IDE otomatik tamamlama ile tür güvenliği sağlar. + + + **Dosya organizasyonu size kalmış.** + Varlık algılama AST tabanlıdır — dosyanın nerede bulunduğundan bağımsız olarak SDK `export default defineEntity(...)` çağrılarını bulur. Dosyaları türe göre gruplamak (örn. `logic-functions/`, `roles/`) bir gereklilik değil, yalnızca bir gelenektir. + + + + + +Roller, çalışma alanınızdaki nesneler ve eylemler üzerindeki izinleri kapsar. + +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export default defineRole({ + universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', + label: 'My new role', + description: 'A role that can be used in your workspace', + canReadAllObjectRecords: false, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + canReadObjectRecords: true, + canUpdateObjectRecords: true, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + }, + ], + fieldPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + fieldUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, + canReadFieldValue: false, + canUpdateFieldValue: false, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +Her uygulamanın, şunları tanımlayan tam olarak bir adet `defineApplication` çağrısı olmalıdır: + +* **Kimlik**: tanımlayıcılar, görünen ad ve açıklama. +* **İzinler**: işlevlerinin ve ön bileşenlerinin hangi rolü kullandığı. +* **(İsteğe bağlı) Değişkenler**: fonksiyonlarınıza ortam değişkenleri olarak sunulan anahtar–değer çiftleri. +* **(İsteğe bağlı) Kurulum öncesi / kurulum sonrası fonksiyonlar**: kurulumdan önce veya sonra çalışan mantık fonksiyonları. + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk/define'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +Notlar: +* `universalIdentifier` alanları, size ait deterministik kimliklerdir. Bunları bir kez oluşturun ve senkronizasyonlar boyunca kararlı tutun. +* `applicationVariables`, fonksiyonlarınız ve ön bileşenleriniz için ortam değişkenlerine dönüşür (örn. `DEFAULT_RECIPIENT_NAME`, `process.env.DEFAULT_RECIPIENT_NAME` olarak kullanılabilir). +* `defaultRoleUniversalIdentifier`, `defineRole()` ile tanımlanmış bir role referans vermelidir (yukarıya bakın). +* Kurulum öncesi ve kurulum sonrası fonksiyonlar manifest derlemesi sırasında otomatik olarak algılanır — bunlara `defineApplication()` içinde referans vermeniz gerekmez. + +#### Pazaryeri meta verileri + +Eğer [uygulamanızı yayımlamayı](/l/tr/developers/extend/apps/publishing) planlıyorsanız, bu isteğe bağlı alanlar uygulamanızın pazaryerinde nasıl görüneceğini kontrol eder: + +| Alan | Açıklama | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | +| `author` | Yazar veya şirket adı | +| `category` | Pazaryerinde filtreleme için uygulama kategorisi | +| `logoUrl` | Uygulamanızın logosuna giden yol (örn. `public/logo.png`) | +| `screenshots` | Ekran görüntüsü yollarının dizisi (örn. `public/screenshot-1.png`) | +| `aboutDescription` | "Hakkında" sekmesi için daha uzun bir markdown açıklaması. Belirtilmezse, pazaryeri npm'deki paketin `README.md` dosyasını kullanır | +| `websiteUrl` | Web sitenize bağlantı | +| `termsUrl` | Hizmet Koşulları'na bağlantı | +| `emailSupport` | Destek e-posta adresi | +| `issueReportUrl` | Sorun izleyicisine bağlantı | + +#### Roller ve izinler + +`application-config.ts` içindeki `defaultRoleUniversalIdentifier`, uygulamanızın mantık fonksiyonları ve ön bileşenleri tarafından kullanılan varsayılan rolü belirtir. Ayrıntılar için yukarıdaki `defineRole` bölümüne bakın. + +* `TWENTY_APP_ACCESS_TOKEN` olarak enjekte edilen çalışma zamanı belirteci bu rolden türetilir. +* Türlendirilmiş istemci, o role tanınan izinlerle sınırlandırılır. +* En az ayrıcalık ilkesini izleyin: Yalnızca fonksiyonlarınızın ihtiyaç duyduğu izinlere sahip özel bir rol oluşturun. + +##### Varsayılan fonksiyon rolü + +Yeni bir uygulama iskeleti oluşturduğunuzda, CLI varsayılan bir rol dosyası oluşturur: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk/define'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + label: 'Default function role', + description: 'Default role for function Twenty client', + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [], + fieldPermissions: [], + permissionFlags: [], +}); +``` + +Bu rolün `universalIdentifier` değeri, `application-config.ts` içinde `defaultRoleUniversalIdentifier` olarak referans verilir: + +* **\*.role.ts**, bir rolün neler yapabileceğini tanımlar. +* **application-config.ts**, fonksiyonlarınızın izinlerini devralması için bu role işaret eder. + +Notlar: +* Oluşturulan rolden başlayın ve en az ayrıcalık ilkesini izleyerek bunu aşamalı olarak kısıtlayın. +* `objectPermissions` ve `fieldPermissions` değerlerini, fonksiyonlarınızın ihtiyaç duyduğu nesneler ve alanlarla değiştirin. +* `permissionFlags`, platform düzeyindeki yeteneklere erişimi kontrol eder. Bunları asgari düzeyde tutun. +* Çalışan bir örnek için bkz.: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). + + + + +Özel nesneler, çalışma alanınızdaki kayıtlar için hem şemayı hem de davranışı tanımlar. Yerleşik doğrulamayla nesneler tanımlamak için `defineObject()` kullanın: + +```ts postCard.object.ts +import { defineObject, FieldType } from 'twenty-sdk/define'; + +enum PostCardStatus { + DRAFT = 'DRAFT', + SENT = 'SENT', + DELIVERED = 'DELIVERED', + RETURNED = 'RETURNED', +} + +export default defineObject({ + universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', + nameSingular: 'postCard', + namePlural: 'postCards', + labelSingular: 'Post Card', + labelPlural: 'Post Cards', + description: 'A post card object', + icon: 'IconMail', + fields: [ + { + universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', + name: 'content', + type: FieldType.TEXT, + label: 'Content', + description: "Postcard's content", + icon: 'IconAbc', + }, + { + universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', + name: 'recipientName', + type: FieldType.FULL_NAME, + label: 'Recipient name', + icon: 'IconUser', + }, + { + universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', + name: 'recipientAddress', + type: FieldType.ADDRESS, + label: 'Recipient address', + icon: 'IconHome', + }, + { + universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', + name: 'status', + type: FieldType.SELECT, + label: 'Status', + icon: 'IconSend', + defaultValue: `'${PostCardStatus.DRAFT}'`, + options: [ + { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, + { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, + { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, + { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, + ], + }, + { + universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', + name: 'deliveredAt', + type: FieldType.DATE_TIME, + label: 'Delivered at', + icon: 'IconCheck', + isNullable: true, + defaultValue: null, + }, + ], +}); +``` + +Önemli noktalar: + +* Yerleşik doğrulama ve daha iyi IDE desteği için `defineObject()` kullanın. +* `universalIdentifier` dağıtımlar arasında benzersiz ve kararlı olmalıdır. +* Her alan bir `name`, `type`, `label` ve kendi kararlı `universalIdentifier` değerini gerektirir. +* `fields` dizisi isteğe bağlıdır — özel alanlar olmadan da nesneler tanımlayabilirsiniz. +* `yarn twenty add` kullanarak, adlandırma, alanlar ve ilişkiler konusunda sizi yönlendirerek yeni nesneler oluşturabilirsiniz. + + +**Temel alanlar otomatik olarak oluşturulur.** Özel bir nesne tanımladığınızda Twenty, standart alanları otomatik olarak ekler +örneğin `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` ve `deletedAt`. +Bunları `fields` dizinizde tanımlamanız gerekmez — yalnızca özel alanlarınızı ekleyin. +`fields` dizinizde aynı ada sahip bir alan tanımlayarak varsayılan alanları geçersiz kılabilirsiniz, +ancak bu önerilmez. + + + + + +Sahibi olmadığınız nesnelere alan eklemek için `defineField()` kullanın — standart Twenty nesneleri (Person, Company, vb.) gibi. veya diğer uygulamalardaki nesneler. `defineObject()` içindeki satır içi alanların aksine, bağımsız alanlar hangi nesneyi genişlettiklerini belirtmek için bir `objectUniversalIdentifier` gerektirir: + +```ts src/fields/company-loyalty-tier.field.ts +import { defineField, FieldType } from 'twenty-sdk/define'; + +export default defineField({ + universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', + objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object + name: 'loyaltyTier', + type: FieldType.SELECT, + label: 'Loyalty Tier', + icon: 'IconStar', + options: [ + { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, + { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, + { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, + ], +}); +``` + +Önemli noktalar: +* `objectUniversalIdentifier` hedef nesneyi tanımlar. Standart nesneler için, `twenty-sdk`'den dışa aktarılan `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`'ı kullanın. +* Alanları `defineObject()` içinde satır içi tanımlarken, `objectUniversalIdentifier`'a ihtiyacınız yoktur — üst nesneden devralınır. +* `defineField()`, `defineObject()` ile oluşturmadığınız nesnelere alan eklemenin tek yoludur. + + + + +İlişkiler nesneleri birbirine bağlar. Twenty'de ilişkiler her zaman **çift yönlüdür** — her iki tarafı da tanımlarsınız ve her taraf diğerine başvurur. + +İki ilişki türü vardır: + +| İlişki türü | Açıklama | Yabancı anahtar var mı? | +| ------------- | --------------------------------------------------------- | ----------------------- | +| `MANY_TO_ONE` | Bu nesnenin birçok kaydı, hedefin bir kaydını işaret eder | Evet (`joinColumnName`) | +| `ONE_TO_MANY` | Bu nesnenin bir kaydı, hedefin birçok kaydına sahiptir | Hayır (ters taraf) | + +#### İlişkiler nasıl çalışır + +Her ilişki, birbirine referans veren iki alan gerektirir: + +1. **MANY_TO_ONE** tarafı — yabancı anahtarı tutan nesne üzerinde bulunur +2. **ONE_TO_MANY** tarafı — koleksiyona sahip olan nesne üzerinde bulunur + +Her iki alan da `FieldType.RELATION` kullanır ve `relationTargetFieldMetadataUniversalIdentifier` aracılığıyla birbirine karşılıklı referans verir. + +#### Örnek: Posta Kartı'nın birçok Alıcısı vardır + +Bir `PostCard`'ın birçok `PostCardRecipient` kaydına gönderilebildiğini varsayalım. Her alıcı tam olarak bir posta kartına aittir. + +**Adım 1: PostCard üzerinde ONE_TO_MANY tarafını tanımlayın** ("bir" taraf): + +```ts src/fields/post-card-recipients-on-post-card.field.ts +import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; +// Import from the other side +import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; + +export default defineField({ + universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCardRecipients', + label: 'Post Card Recipients', + icon: 'IconUsers', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); +``` + +**Adım 2: PostCardRecipient üzerinde MANY_TO_ONE tarafını tanımlayın** ("çok" taraf — yabancı anahtarı tutar): + +```ts src/fields/post-card-on-post-card-recipient.field.ts +import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; +// Import from the other side +import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; + +export default defineField({ + universalIdentifier: POST_CARD_FIELD_ID, + objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + icon: 'IconMail', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, +}); +``` + + +**Döngüsel içe aktarmalar:** Her iki ilişki alanı da birbirlerinin `universalIdentifier` değerine referans verir. Döngüsel içe aktarma sorunlarından kaçınmak için, alan kimliklerinizi her dosyadan adlandırılmış sabitler olarak dışa aktarın ve diğer dosyada içe aktarın. Derleme sistemi bunları derleme zamanında çözer. + + +#### Standart nesnelerle ilişkilendirme + +Yerleşik bir Twenty nesnesiyle (Person, Company, vb.) ilişki oluşturmak için `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` kullanın: + +```ts src/fields/person-on-self-hosting-user.field.ts +import { + defineField, + FieldType, + RelationType, + OnDeleteAction, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; +import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; + +export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; +export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; + +export default defineField({ + universalIdentifier: PERSON_FIELD_ID, + objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'person', + label: 'Person', + description: 'Person matching with the self hosting user', + isNullable: true, + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'personId', + }, +}); +``` + +#### İlişki alanı özellikleri + +| Özellik | Zorunlu | Açıklama | +| ------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- | +| `type` | Evet | `FieldType.RELATION` olmalıdır | +| `relationTargetObjectMetadataUniversalIdentifier` | Evet | Hedef nesnenin `universalIdentifier` değeri | +| `relationTargetFieldMetadataUniversalIdentifier` | Evet | Hedef nesnedeki eşleşen alanın `universalIdentifier` değeri | +| `universalSettings.relationType` | Evet | `RelationType.MANY_TO_ONE` veya `RelationType.ONE_TO_MANY` | +| `universalSettings.onDelete` | Yalnızca MANY_TO_ONE | Başvurulan kayıt silindiğinde ne olacağı: `CASCADE`, `SET_NULL`, `RESTRICT` veya `NO_ACTION` | +| `universalSettings.joinColumnName` | Yalnızca MANY_TO_ONE | Yabancı anahtar için veritabanı sütun adı (örn. `postCardId`) | + +#### defineObject içinde satır içi ilişki alanları + +İlişki alanlarını doğrudan `defineObject()` içinde de tanımlayabilirsiniz. Bu durumda, `objectUniversalIdentifier`'ı atlayın — üst nesneden devralınır: + +```ts +export default defineObject({ + universalIdentifier: '...', + nameSingular: 'postCardRecipient', + // ... + fields: [ + { + universalIdentifier: POST_CARD_FIELD_ID, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, + }, + // ... other fields + ], +}); +``` + + + +## `yarn twenty add` ile varlıklar için iskelet oluşturma + +Varlık dosyalarını elle oluşturmak yerine etkileşimli iskelet oluşturucuyu kullanabilirsiniz: + +```bash filename="Terminal" +yarn twenty add +``` + +Bu, bir varlık türü seçmenizi ister ve gerekli alanlar boyunca size yol gösterir. Kararlı bir `universalIdentifier` ve doğru `defineEntity()` çağrısıyla kullanıma hazır bir dosya üretir. + +İlk istemi atlamak için varlık türünü doğrudan da geçebilirsiniz: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### Kullanılabilir varlık türleri + +| Varlık türü | Komut | Oluşturulan dosya | +| -------------------- | ------------------------------------ | ------------------------------------------------------- | +| Nesne | `yarn twenty add object` | `src/objects/\.ts` | +| Alan | `yarn twenty add field` | `src/fields/\.ts` | +| Mantık işlevi | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | +| Ön uç bileşeni | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | +| Rol | `yarn twenty add role` | `src/roles/\.ts` | +| Beceri | `yarn twenty add skill` | `src/skills/\.ts` | +| Temsilci | `yarn twenty add agent` | `src/agents/\.ts` | +| Görünüm | `yarn twenty add view` | `src/views/\.ts` | +| Gezinme menüsü öğesi | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | +| Sayfa düzeni | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | + +### İskelet oluşturucunun ürettikleri + +Her varlık türünün kendi şablonu vardır. Örneğin, `yarn twenty add object` şunları sorar: + +1. **Ad (tekil)** — ör. `invoice` +2. **Ad (çoğul)** — ör. `invoices` +3. **Etiket (tekil)** — adından otomatik doldurulur (ör. `Invoice`) +4. **Etiket (çoğul)** — otomatik doldurulur (ör. `Invoices`) +5. **Bir görünüm ve gezinme öğesi oluşturulsun mu?** — evet derseniz, iskelet oluşturucu yeni nesne için eşleşen bir görünüm ve kenar çubuğu bağlantısı da üretir. + +Diğer varlık türlerinin istemleri daha basittir — çoğu yalnızca bir ad sorar. + +`field` varlık türü daha ayrıntılıdır: alan adını, etiketi, türü (`TEXT`, `NUMBER`, `SELECT`, `RELATION` vb. gibi mevcut tüm alan türlerinin listesinden) ve hedef nesnenin `universalIdentifier` değerini sorar. + +### Özel çıktı yolu + +`--path` bayrağını kullanarak oluşturulan dosyayı özel bir konuma yerleştirin: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/front-components.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/front-components.mdx new file mode 100644 index 00000000000..830deec5707 --- /dev/null +++ b/packages/twenty-docs/l/tr/developers/extend/apps/front-components.mdx @@ -0,0 +1,419 @@ +--- +title: Ön uç bileşenleri +description: Build React components that render inside Twenty's UI with sandboxed isolation. +icon: window-maximize +--- + +Ön uç bileşenler, Twenty'nin UI'si içinde doğrudan görüntülenen React bileşenleridir. Remote DOM kullanan izole bir Web Worker içinde çalışırlar — kodunuz izole bir ortamda (sandbox) çalışır ancak bir iframe içinde değil, sayfada yerel olarak işlenir. + +## Ön uç bileşenlerinin kullanılabileceği yerler + +Ön uç bileşenler, Twenty içinde iki konumda işlenebilir: + +* **Yan panel** — Headless olmayan ön uç bileşenler, sağ taraftaki yan panelde açılır. Bir ön uç bileşeni komut menüsünden tetiklendiğinde varsayılan davranış budur. +* **Widget'lar (panolar ve kayıt sayfaları)** — Ön uç bileşenler, sayfa düzenlerine widget olarak gömülebilir. Bir pano veya kayıt sayfası düzeni yapılandırılırken kullanıcılar bir ön uç bileşen widget'ı ekleyebilir. + +## Basit örnek + +Bir ön uç bileşenini çalışırken görmenin en hızlı yolu, onu bir komut olarak kaydetmektir. `isPinned: true` ile bir `command` alanı eklemek, sayfanın sağ üst köşesinde hızlı işlem düğmesi olarak görünmesini sağlar — herhangi bir sayfa düzenine gerek yoktur: + +```tsx src/front-components/hello-world.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; + +const HelloWorld = () => { + return ( +
+

Hello from my app!

+

This component renders inside Twenty.

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, +}); +``` + +`yarn twenty dev` ile senkronize ettikten sonra (veya tek seferlik bir `yarn twenty dev --once` çalıştırdıktan sonra), hızlı işlem sayfanın sağ üst köşesinde görünür: + +
+ Sağ üst köşedeki hızlı işlem düğmesi +
+ +Bileşeni satır içi işlemek için üzerine tıklayın. + +## Yapılandırma alanları + +| Alan | Zorunlu | Açıklama | +| --------------------- | ------- | ------------------------------------------------------------------------------------------ | +| `universalIdentifier` | Evet | Bu bileşen için kalıcı benzersiz kimlik | +| `component` | Evet | Bir React bileşen fonksiyonu | +| `name` | Hayır | Görünen Ad | +| `description` | Hayır | Bileşenin ne yaptığına dair açıklama | +| `isHeadless` | Hayır | Bileşenin görünür bir kullanıcı arayüzü yoksa `true` olarak ayarlayın (aşağıya bakın) | +| `command` | Hayır | Bileşeni bir komut olarak kaydedin (aşağıda [komut seçeneklerine](#command-options) bakın) | + +## Bir ön uç bileşenini bir sayfaya yerleştirme + +Komutların ötesinde, bir ön uç bileşenini bir **sayfa düzeninde** widget olarak ekleyerek doğrudan bir kayıt sayfasına gömebilirsiniz. Ayrıntılar için [definePageLayout](/l/tr/developers/extend/apps/skills-and-agents#definepagelayout) bölümüne bakın. + +## Headless ve headless olmayan + +Ön uç bileşenler, `isHeadless` seçeneğiyle kontrol edilen iki işleme kipiyle gelir: + +**Headless olmayan (varsayılan)** — Bileşen görünür bir kullanıcı arayüzü (UI) oluşturur. Komut menüsünden tetiklendiğinde yan panelde açılır. `isHeadless` `false` olduğunda veya belirtilmediğinde bu varsayılan davranıştır. + +**Headless (`isHeadless: true`)** — Bileşen arka planda görünmez şekilde bağlanır. Yan paneli açmaz. Headless bileşenler, mantığı çalıştırıp ardından kendilerini kaldıran eylemler için tasarlanmıştır — örneğin, bir async görevi çalıştırma, bir sayfaya gitme veya bir onay modalı gösterme. Aşağıda açıklanan SDK Command bileşenleriyle doğal olarak eşleşirler. + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Bileşen `null` döndürdüğü için, Twenty bunun için bir kapsayıcı oluşturmayı atlar — düzende boş alan görünmez. Bileşen yine de tüm hook'lara ve host iletişim API'sine erişime sahiptir. + +## SDK Command bileşenleri + +`twenty-sdk` paketi, headless ön bileşenler için tasarlanmış dört Command yardımcı bileşeni sağlar. Her bileşen bağlandığında bir eylem yürütür, hataları bir snackbar bildirimi göstererek ele alır ve tamamlandığında ön bileşeni otomatik olarak kaldırır. + +Bunları `twenty-sdk/command` içinden içe aktarın: + +* **`Command`** — `execute` prop'u aracılığıyla async bir geri çağrıyı çalıştırır. +* **`CommandLink`** — Bir uygulama yoluna gider. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Bir onay modalı açar. Kullanıcı onaylarsa `execute` geri çağrısını yürütür. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Belirli bir yan panel sayfasını açar. Props: `page`, `pageTitle`, `pageIcon`. + +`Command` kullanarak komut menüsünden bir eylem çalıştıran headless bir ön bileşenin tam örneği: + +```tsx src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +Ve yürütmeden önce onay istemek için `CommandModal` kullanan bir örnek: + +```tsx src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +## Çalışma zamanı bağlamına erişme + +Bileşeninizin içinde, geçerli kullanıcıya, kayda ve bileşen örneğine erişmek için SDK hook'larını kullanın: + +```tsx src/front-components/record-info.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk/front-component'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Kullanılabilir hook'lar: + +| Hook | Döndürür | Açıklama | +| --------------------------------------------- | -------------------- | ------------------------------------------------------------- | +| `useUserId()` | `string` veya `null` | Geçerli kullanıcının ID'si | +| `useRecordId()` | `string` veya `null` | Geçerli kaydın ID'si (bir kayıt sayfasına yerleştirildiğinde) | +| `useFrontComponentId()` | `string` | Bu bileşen örneğinin ID'si | +| `useFrontComponentExecutionContext(selector)` | değişir | Bir seçici işlevle tam yürütme bağlamına erişin | + +## Host iletişim API'si + +Ön uç bileşenleri, `twenty-sdk`'deki işlevleri kullanarak gezinmeyi, modalları ve bildirimleri tetikleyebilir: + +| Fonksiyon | Açıklama | +| ----------------------------------------------- | ---------------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Uygulamada bir sayfaya git | +| `openSidePanelPage(params)` | Bir yan panel aç | +| `closeSidePanel()` | Yan paneli kapat | +| `openCommandConfirmationModal(params)` | Bir onay iletişim kutusu göster | +| `enqueueSnackbar(params)` | Bir toast bildirimi göster | +| `unmountFrontComponent()` | Bileşeni kaldır (unmount) | +| `updateProgress(progress)` | Bir ilerleme göstergesini güncelle | + +Bir eylem tamamlandıktan sonra bir snackbar göstermek ve yan paneli kapatmak için host API'sini kullanan bir örnek: + +```tsx src/front-components/archive-record.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId } from 'twenty-sdk/front-component'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + +## Komut seçenekleri + +`defineFrontComponent` içine bir `command` alanı eklemek, bileşeni komut menüsüne (Cmd+K) kaydeder. `isPinned` `true` ise, sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak da görünür. + +| Alan | Zorunlu | Açıklama | +| --------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `universalIdentifier` | Evet | Komut için kalıcı benzersiz kimlik | +| `label` | Evet | Komut menüsünde (Cmd+K) gösterilen tam etiket | +| `shortLabel` | Hayır | Sabitlenmiş hızlı işlem düğmesinde görüntülenen daha kısa etiket | +| `icon` | Hayır | Etiketin yanında görüntülenen simge adı (örn. `'IconBolt'`, `'IconSend'`) | +| `isPinned` | Hayır | `true` olduğunda, komutu sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak gösterir | +| `availabilityType` | Hayır | Komutun nerede görüneceğini kontrol eder: `'GLOBAL'` (her zaman kullanılabilir), `'RECORD_SELECTION'` (yalnızca kayıtlar seçiliyken) veya `'FALLBACK'` (başka hiçbir komut eşleşmediğinde gösterilir) | +| `availabilityObjectUniversalIdentifier` | Hayır | Komutu belirli bir nesne türünün sayfalarıyla sınırlandırın (örn. yalnızca Company kayıtlarında) | +| `conditionalAvailabilityExpression` | Hayır | Komutun görünür olup olmadığını dinamik olarak kontrol eden bir boolean ifade (aşağıya bakın) | + +## Koşullu kullanılabilirlik ifadeleri + +`conditionalAvailabilityExpression` alanı, geçerli sayfa bağlamına göre bir komutun ne zaman görünür olacağını kontrol etmenizi sağlar. İfadeler oluşturmak için `twenty-sdk`'den türlendirilmiş değişkenleri ve operatörleri içe aktarın: + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk/front-component'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Bağlam değişkenleri** — bunlar sayfanın mevcut durumunu temsil eder: + +| Değişken | Tür | Açıklama | +| ------------------------------ | --------- | ----------------------------------------------------------------- | +| `pageType` | `string` | Geçerli sayfa türü (örn. `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Bileşenin bir yan panelde oluşturulup oluşturulmadığı | +| `numberOfSelectedRecords` | `number` | Şu anda seçili kayıt sayısı | +| `isSelectAll` | `boolean` | "tümünü seç" seçeneğinin etkin olup olmadığı | +| `selectedRecords` | `array` | Seçili kayıt nesneleri | +| `favoriteRecordIds` | `array` | Favorilere eklenen kayıtların ID'leri | +| `objectPermissions` | `object` | Geçerli nesne türü için izinler | +| `targetObjectReadPermissions` | `object` | Hedef nesne için okuma izinleri | +| `targetObjectWritePermissions` | `object` | Hedef nesne için yazma izinleri | +| `featureFlags` | `object` | Etkin özellik bayrakları | +| `objectMetadataItem` | `object` | Geçerli nesne türünün üst verileri | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Geçerli görünümde soft-delete filtresi olup olmadığı | + +**Operatörler** — değişkenleri boolean ifadelere dönüştürmek için birleştirin: + +| Operatör | Açıklama | +| ----------------------------------- | --------------------------------------------------------- | +| `isDefined(value)` | Değer null/undefined değilse `true` | +| `isNonEmptyString(value)` | Değer boş olmayan bir string ise `true` | +| `includes(array, value)` | Dizi değeri içeriyorsa `true` | +| `includesEvery(array, prop, value)` | Her bir öğenin özelliği değeri içeriyorsa `true` | +| `every(array, prop)` | Özellik her öğede truthy ise `true` | +| `everyDefined(array, prop)` | Özellik her öğede tanımlıysa `true` | +| `everyEquals(array, prop, value)` | Özellik her öğede değere eşitse `true` | +| `some(array, prop)` | Özellik en az bir öğede truthy ise `true` | +| `someDefined(array, prop)` | Özellik en az bir öğede tanımlıysa `true` | +| `someEquals(array, prop, value)` | Özellik en az bir öğede değere eşitse `true` | +| `someNonEmptyString(array, prop)` | Özellik en az bir öğede boş olmayan bir string ise `true` | +| `none(array, prop)` | Özellik her öğede falsy ise `true` | +| `noneDefined(array, prop)` | Özellik her öğede tanımsızsa `true` | +| `noneEquals(array, prop, value)` | Özellik hiçbir öğede değere eşit değilse `true` | + +## Genel varlıklar + +Ön uç bileşenleri, `getPublicAssetUrl` kullanarak uygulamanın `public/` dizinindeki dosyalara erişebilir: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +Ayrıntılar için [genel varlıklar bölümüne](/l/tr/developers/extend/apps/cli-and-testing#public-assets-public-folder) bakın. + +## Stil + +Ön uç bileşenleri birden fazla biçimlendirme yaklaşımını destekler. Şunları kullanabilirsiniz: + +* **Satır içi stiller** — `style={{ color: 'red' }}` +* **Twenty UI bileşenleri** — `twenty-sdk/ui` içinden içe aktarın (Button, Tag, Status, Chip, Avatar ve daha fazlası) +* **Emotion** — `@emotion/react` ile CSS-in-JS +* **Styled-components** — `styled.div` kalıpları +* **Tailwind CSS** — yardımcı sınıflar +* **React ile uyumlu herhangi bir CSS-in-JS kitaplığı** + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/getting-started.mdx index aba27ae9b05..043cd618515 100644 --- a/packages/twenty-docs/l/tr/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/tr/developers/extend/apps/getting-started.mdx @@ -1,12 +1,9 @@ --- title: Başlarken +icon: rocket description: İlk Twenty uygulamanızı dakikalar içinde oluşturun. --- - -Uygulamalar şu anda alfa aşamasında. Özellik işlevsel ancak hâlâ gelişmekte. - - ## Uygulamalar nedir? Uygulamalar, Twenty'yi özel nesneler, alanlar, mantıksal işlevler, ön uç bileşenleri, yapay zekâ yetenekleri ve daha fazlasıyla genişletmenizi sağlar — tümü kod olarak yönetilir. Her şeyi UI üzerinden yapılandırmak yerine, veri modelinizi ve mantığınızı TypeScript'te tanımlar ve bunu bir veya daha fazla çalışma alanına dağıtırsınız. diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/layout.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/layout.mdx new file mode 100644 index 00000000000..f69a939d9d8 --- /dev/null +++ b/packages/twenty-docs/l/tr/developers/extend/apps/layout.mdx @@ -0,0 +1,131 @@ +--- +title: Düzen +description: Define views, navigation menu items, and page layouts to shape how your app appears in Twenty. +icon: table-columns +--- + +Layout entities control how your app surfaces inside Twenty's UI — what lives in the sidebar, which saved views ship with the app, and how a record detail page is arranged. + +## Layout concepts + +| Concept | What it controls | Varlık | +| ------------------------ | --------------------------------------------------------------------------------- | -------------------------- | +| **View** | A saved list configuration for an object — visible fields, order, filters, groups | `defineView` | +| **Navigation Menu Item** | An entry in the left sidebar that links to a view or an external URL | `defineNavigationMenuItem` | +| **Page Layout** | The tabs and widgets that make up a record's detail page | `definePageLayout` | + +Views, navigation items, and page layouts reference each other by `universalIdentifier`: + +* A **navigation menu item** of type `VIEW` points at a `defineView` identifier, so the sidebar link opens that saved view. +* A **page layout** of type `RECORD_PAGE` targets an object and can embed [front components](/l/tr/developers/extend/apps/front-components) inside its tabs as widgets. + + + + +Görünümler, bir nesnenin kayıtlarının nasıl görüntüleneceğine ilişkin kaydedilmiş yapılandırmalardır — hangi alanların görünür olacağını, sıralarını ve uygulanan filtreleri veya grupları içerir. Uygulamanızla önceden yapılandırılmış görünümler sunmak için `defineView()` kullanın: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +Önemli noktalar: +* `objectUniversalIdentifier`, bu görünümün hangi nesneye uygulanacağını belirtir. +* `key`, görünüm türünü belirler (ör. ana liste görünümü için `ViewKey.INDEX`). +* `fields`, hangi sütunların görüneceğini ve sıralarını kontrol eder. Her alan bir `fieldMetadataUniversalIdentifier` öğesine referans verir. +* Daha gelişmiş yapılandırmalar için `filters`, `filterGroups`, `groups` ve `fieldGroups` de tanımlayabilirsiniz. +* `position`, aynı nesne için birden fazla görünüm olduğunda sıralamayı kontrol eder. + + + + +Gezinme menüsü öğeleri, çalışma alanı kenar çubuğuna özel girişler ekler. Görünümlere, harici URL'lere veya nesnelere bağlanmak için `defineNavigationMenuItem()` kullanın: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +Önemli noktalar: +* `type`, menü öğesinin neye bağlanacağını belirler: kaydedilmiş bir görünüm için `NavigationMenuItemType.VIEW` veya harici bir URL için `NavigationMenuItemType.LINK`. +* Görünüm bağlantıları için `viewUniversalIdentifier` ayarlayın. Harici bağlantılar için `link` ayarlayın. +* `position`, kenar çubuğundaki sıralamayı kontrol eder. +* `icon` ve `color` (isteğe bağlı) görünümü özelleştirir. + + + + +Sayfa düzenleri, bir kayıt ayrıntı sayfasının nasıl görüneceğini özelleştirmenizi sağlar — hangi sekmelerin görüneceği, her sekmenin içinde hangi widget'ların olacağı ve bunların nasıl düzenleneceği. Uygulamanızla özel düzenler sunmak için `definePageLayout()` kullanın: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +Önemli noktalar: +* `type` genellikle belirli bir nesnenin ayrıntı görünümünü özelleştirmek için `'RECORD_PAGE'` olur. +* `objectUniversalIdentifier`, bu düzenin hangi nesneye uygulanacağını belirtir. +* Her `tab`, bir `title`, `position` ve `layoutMode` ile sayfanın bir bölümünü tanımlar (serbest biçimli düzen için `CANVAS`). +* Bir sekmenin içindeki her `widget`, bir ön uç bileşeni, bir ilişki listesi veya diğer yerleşik widget türlerini oluşturabilir. +* Sekmelerdeki `position`, sıralarını kontrol eder. Özel sekmeleri yerleşik olanların sonrasına yerleştirmek için daha yüksek değerler kullanın (ör. 50). + + + diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/logic-functions.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/logic-functions.mdx new file mode 100644 index 00000000000..eaa088e0e78 --- /dev/null +++ b/packages/twenty-docs/l/tr/developers/extend/apps/logic-functions.mdx @@ -0,0 +1,560 @@ +--- +title: Mantıksal işlevler +description: Define server-side TypeScript functions with HTTP, cron, and database event triggers. +icon: bolt +--- + +Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents. + + + + +Her fonksiyon dosyası, bir işleyici ve isteğe bağlı tetikleyiciler içeren bir yapılandırmayı dışa aktarmak için `defineLogicFunction()` kullanır. + +```ts src/logic-functions/createPostCard.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; + +const handler = async (params: RoutePayload) => { + const client = new CoreApiClient(); + const name = 'name' in params.queryStringParameters + ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' + : 'Hello world'; + + const result = await client.mutation({ + createPostCard: { + __args: { data: { name } }, + id: true, + name: true, + }, + }); + return result; +}; + +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'create-new-post-card', + timeoutSeconds: 2, + handler, + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: true, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ +}); +``` + +Kullanılabilir tetikleyici türleri: +* **httpRoute**: Fonksiyonunuzu bir HTTP yolu ve yöntemiyle **`/s/` uç noktasının altında** kullanıma sunar: +> örn. `path: '/post-card/create'` `https://your-twenty-server.com/s/post-card/create` adresinden çağrılabilir +* **cron**: Bir CRON ifadesi kullanarak fonksiyonunuzu bir zamanlamayla çalıştırır. +* **databaseEvent**: Çalışma alanı nesnesi yaşam döngüsü olaylarında çalışır. Olay işlemi `updated` olduğunda, dinlenecek belirli alanlar `updatedFields` dizisinde belirtilebilir. Tanımsız veya boş bırakılırsa, herhangi bir güncelleme fonksiyonu tetikler. +> örn. `person.updated`, `*.created`, `company.*` + + +Bir fonksiyonu CLI kullanarak manuel olarak da çalıştırabilirsiniz: + +```bash filename="Terminal" +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' +``` + +```bash filename="Terminal" +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + +Günlükleri şu şekilde izleyebilirsiniz: + +```bash filename="Terminal" +yarn twenty logs +``` + + +#### Rota tetikleyicisi yükü + +Bir rota tetikleyicisi mantık fonksiyonunuzu çağırdığında, +[AWS HTTP API v2 formatını](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) izleyen bir `RoutePayload` nesnesi alır. +`RoutePayload` türünü `twenty-sdk` içinden içe aktarın: + +```ts +import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; + +const handler = async (event: RoutePayload) => { + const { headers, queryStringParameters, pathParameters, body } = event; + const { method, path } = event.requestContext.http; + + return { message: 'Success' }; +}; +``` + +`RoutePayload` türünün yapısı şu şekildedir: + + | Özellik | Tür | Açıklama | Örnek | + | ---------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record\` | HTTP başlıkları (`forwardedRequestHeaders` içinde listelenenlerle sınırlı) | aşağıdaki bölüme bakın | + | `queryStringParameters` | `Record\` | Sorgu dizesi parametreleri (birden çok değer virgülle birleştirilir) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record\` | Rota deseninden çıkarılan yol parametreleri | `/users/:id`, `/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | Ayrıştırılmış istek gövdesi (JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | Gövdenin base64 ile kodlanıp kodlanmadığı | | + | `requestContext.http.method` | `string` | HTTP yöntemi (GET, POST, PUT, PATCH, DELETE) | | + | `requestContext.http.path` | `string` | Ham istek yolu | | + + +#### forwardedRequestHeaders + +Varsayılan olarak, güvenlik nedenleriyle gelen isteklerden HTTP başlıkları mantık fonksiyonunuza **aktarılmaz**. +Belirli başlıklara erişmek için bunları `forwardedRequestHeaders` dizisinde listeleyin: + +```ts +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'webhook-handler', + handler, + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, +}); +``` + +İşleyicinizde, iletilen başlıklara şu şekilde erişin: + +```ts +const handler = async (event: RoutePayload) => { + const signature = event.headers['x-webhook-signature']; + const contentType = event.headers['content-type']; + + // Validate webhook signature... + return { received: true }; +}; +``` + + +Başlık adları küçük harfe normalize edilir. Onlara küçük harfli anahtarlarla erişin (örneğin, `event.headers['content-type']`). + + +#### Bir fonksiyonu araç olarak sunma + +Mantık işlevleri, yapay zeka ajanları ve iş akışları için **araçlar** olarak sunulabilir. Bir fonksiyon bir araç olarak işaretlendiğinde, Twenty'nin yapay zeka özellikleri tarafından keşfedilebilir hâle gelir ve iş akışı otomasyonlarında kullanılabilir. + +Bir mantık fonksiyonunu araç olarak işaretlemek için `isTool: true` olarak ayarlayın: + +```ts src/logic-functions/enrich-company.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const handler = async (params: { companyName: string; domain?: string }) => { + const client = new CoreApiClient(); + + const result = await client.mutation({ + createTask: { + __args: { + data: { + title: `Enrich data for ${params.companyName}`, + body: `Domain: ${params.domain ?? 'unknown'}`, + }, + }, + id: true, + }, + }); + + return { taskId: result.createTask.id }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + name: 'enrich-company', + description: 'Enrich a company record with external data', + timeoutSeconds: 10, + handler, + isTool: true, +}); +``` + +Önemli noktalar: + +* `isTool` özelliğini tetikleyicilerle birleştirebilirsiniz — bir fonksiyon aynı anda hem bir araç (yapay zeka ajanları tarafından çağrılabilir) olabilir hem de olaylar tarafından tetiklenebilir. +* **`toolInputSchema`** (isteğe bağlı): Fonksiyonunuzun kabul ettiği parametreleri tanımlayan bir JSON Schema nesnesi. Şema, kaynak kodun statik analizinden otomatik olarak oluşturulur, ancak bunu açıkça belirleyebilirsiniz: + +```ts +export default defineLogicFunction({ + ..., + toolInputSchema: { + type: 'object', + properties: { + companyName: { + type: 'string', + description: 'The name of the company to enrich', + }, + domain: { + type: 'string', + description: 'The company website domain (optional)', + }, + }, + required: ['companyName'], + }, +}); +``` + + +**İyi bir `description` yazın.** AI ajanları, aracı ne zaman kullanacaklarına karar vermek için işlevin `description` alanına güvenir. Aracın ne yaptığını ve ne zaman çağrılması gerektiğini açıkça belirtin. + + + + + +Kurulum sonrası işlev, uygulamanız bir çalışma alanına yüklendikten sonra otomatik olarak çalışan bir mantık işlevidir. Sunucu, uygulamanın meta verileri senkronize edildikten ve SDK istemcisi oluşturulduktan **sonra** bunu yürütür; böylece çalışma alanı tamamen kullanıma hazırdır ve yeni şema kullanıma alınmıştır. Tipik kullanım örnekleri arasında varsayılan verilerin tohumlanması, başlangıç kayıtlarının oluşturulması, çalışma alanı ayarlarının yapılandırılması veya üçüncü taraf hizmetlerde kaynak sağlanması yer alır. + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + shouldRunSynchronously: false, + handler, +}); +``` + +Ayrıca kurulum sonrası işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +Önemli noktalar: +* Kurulum sonrası işlevler `definePostInstallLogicFunction()` kullanır — tetikleyici ayarlarını atlayan (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) özel bir varyanttır. +* İşleyici, `{ previousVersion?: string; newVersion: string }` içeren bir `InstallPayload` alır — `newVersion`, yüklenen sürümdür; `previousVersion` ise daha önce yüklü olan sürümdür (veya ilk kurulumda `undefined`). Bu değerleri ilk kurulumları yükseltmelerden ayırt etmek ve sürüme özgü geçiş (migration) mantığını çalıştırmak için kullanın. +* **Kanca ne zaman çalışır**: varsayılan olarak yalnızca ilk kurulumlarda. Uygulama önceki bir sürümden yükseltildiğinde de çalışmasını istiyorsanız `shouldRunOnVersionUpgrade: true` geçin. Belirtilmediğinde, bayrak varsayılan olarak `false` olur ve yükseltmeler kancayı atlar. +* **Yürütme modeli — varsayılan olarak eşzamansız, isteğe bağlı senkron**: `shouldRunSynchronously` bayrağı kurulum sonrası işlemin *nasıl* yürütüldüğünü kontrol eder. + * `shouldRunSynchronously: false` *(varsayılan)* — kanca, `retryLimit: 3` ile **mesaj kuyruğuna alınır** ve bir worker içinde eşzamansız çalışır. İş kuyruğa alınır alınmaz kurulum yanıtı döner; dolayısıyla yavaşlayan veya hata veren bir işleyici çağıranı engellemez. Worker en fazla üç kez yeniden deneyecektir. **Bunu uzun süre çalışan işler için kullanın** — büyük veri kümelerini tohumlama, yavaş üçüncü taraf API'lerini çağırma, harici kaynakları sağlama; makul bir HTTP yanıt süresini aşabilecek her şey. + * `shouldRunSynchronously: true` — kanca **kurulum akışı sırasında satır içi** olarak yürütülür (kurulum öncesi ile aynı yürütücü). İşleyici bitene kadar kurulum isteği engellenir; hata fırlatırsa, kurulum çağıranı bir `POST_INSTALL_ERROR` alır. Otomatik yeniden deneme yok. **Bunu, yanıt dönmeden mutlaka tamamlanması gereken hızlı işler için kullanın** — örneğin, kullanıcıya bir doğrulama hatası iletmek veya kurulum çağrısı döner dönmez istemcinin ihtiyaç duyacağı hızlı bir kurulum yapmak. Kurulum sonrası çalıştığında, üstveri (metadata) geçişinin zaten uygulanmış olduğunu unutmayın; bu nedenle, senkron moddaki bir hata şema değişikliklerini **geri almaz** — yalnızca hatayı görünür kılar. +* İşleyicinizin idempotent olduğundan emin olun. Eşzamansız modda kuyruk en fazla üç kez yeniden deneyebilir; her iki modda da `shouldRunOnVersionUpgrade: true` iken yükseltmelerde kanca tekrar çalışabilir. +* Ortam değişkenleri `APPLICATION_ID`, `APP_ACCESS_TOKEN` ve `API_URL` işleyici içinde kullanılabilir (diğer mantık işlevlerinde olduğu gibi), böylece uygulamanıza özel kapsamda bir uygulama erişim belirteciyle Twenty API'sini çağırabilirsiniz. +* Uygulama başına yalnızca bir kurulum sonrası işlevine izin verilir. Birden fazla tespit edilirse manifest oluşturma hataya düşer. +* İşlevin `universalIdentifier`, `shouldRunOnVersionUpgrade` ve `shouldRunSynchronously` değerleri, derleme sırasında uygulama manifestine `postInstallLogicFunction` alanı altında otomatik olarak eklenir — bunlara `defineApplication()` içinde atıfta bulunmanıza gerek yoktur. +* Varsayılan zaman aşımı, veri tohumlama gibi daha uzun kurulum görevlerine izin vermek için 300 saniye (5 dakika) olarak ayarlanmıştır. +* **Geliştirme modunda çalıştırılmaz**: bir uygulama yerel olarak kaydedildiğinde (`yarn twenty dev` aracılığıyla), sunucu kurulum akışını tamamen atlar ve dosyaları doğrudan CLI watcher üzerinden eşitler — bu nedenle, `shouldRunSynchronously` ne olursa olsun, kurulum sonrası geliştirme modunda hiç çalışmaz. Çalışan bir çalışma alanında bunu elle tetiklemek için `yarn twenty exec --postInstall` kullanın. + + + + +Kurulum öncesi işlev, kurulum sırasında otomatik olarak çalışan ve **çalışma alanı üstveri (metadata) geçişi uygulanmadan önce** yürütülen bir mantık işlevidir. Kurulum sonrası ile (`InstallPayload`) aynı yük (payload) biçimini paylaşır, ancak kurulum akışında daha erken konumlandığından yaklaşan geçişin bağlı olduğu durumu hazırlayabilir — tipik kullanımlar arasında verileri yedeklemek, yeni şemayla uyumluluğu doğrulamak veya yeniden yapılandırılacak ya da kaldırılacak kayıtları arşivlemek yer alır. + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +Ayrıca kurulum öncesi işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +Önemli noktalar: +* Kurulum öncesi işlevler `definePreInstallLogicFunction()` kullanır — kurulum sonrasıyla aynı özel yapılandırma, sadece yaşam döngüsünde farklı bir yuvaya eklenir. +* Hem kurulum öncesi hem de kurulum sonrası işleyiciler aynı `InstallPayload` türünü alır: `{ previousVersion?: string; newVersion: string }`. Bunu bir kez içe aktarın ve her iki kanca için yeniden kullanın. +* **Kanca ne zaman çalışır**: çalışma alanı üstveri (metadata) geçişinden hemen önce konumlandırılır (`synchronizeFromManifest`). Çalıştırmadan önce, sunucu yalnızca ekleyici bir "indirgenmiş eşitleme" yürütür; bu, çalışma alanı üstverisinde **yeni** sürümün kurulum öncesi işlevini kaydeder — başka hiçbir şeye dokunulmaz — ve ardından bunu yürütür. Bu eşitleme yalnızca ekleyici olduğundan, işleyiciniz çalıştığında önceki sürümün nesneleri, alanları ve verileri hâlâ sağlamdır: geçiş öncesi durumu güvenle okuyabilir ve yedekleyebilirsiniz. +* **Yürütme modeli**: kurulum öncesi **senkron** olarak yürütülür ve **kurulumu bloklar**. İşleyici bir hata fırlatırsa, herhangi bir şema değişikliği uygulanmadan önce kurulum iptal edilir — çalışma alanı, tutarlı bir durumda önceki sürümde kalır. Bu kasıtlıdır: kurulum öncesi, riskli bir yükseltmeyi reddetmek için son şansınızdır. +* Kurulum sonrası ile aynı şekilde, uygulama başına yalnızca bir kurulum öncesi işlevine izin verilir. Derleme sırasında uygulama manifestine `preInstallLogicFunction` altında otomatik olarak eklenir. +* **Geliştirme modunda çalıştırılmaz**: kurulum sonrasında olduğu gibi — yerel olarak kaydedilen uygulamalarda kurulum akışı tamamen atlanır, bu nedenle `yarn twenty dev` altında kurulum öncesi hiç çalışmaz. Bunu elle tetiklemek için `yarn twenty exec --preInstall` kullanın. + + + + +Her iki kanca da aynı kurulum akışının parçasıdır ve aynı `InstallPayload`'ı alır. Fark, çalışma alanı üstveri (metadata) geçişine göre **ne zaman** çalıştıklarıdır ve bu, güvenle erişebilecekleri verileri değiştirir. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ install flow │ +│ │ +│ upload package → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +│ │ +│ old schema visible new schema visible │ +└─────────────────────────────────────────────────────────────┘ +``` + +Kurulum öncesi her zaman **senkron**dur (kurulumu bloke eder ve iptal edebilir). Kurulum sonrası **varsayılan olarak asenkron**dur — otomatik yeniden denemelerle bir worker üzerinde kuyruğa alınır — ancak `shouldRunSynchronously: true` ile senkron yürütmeye geçebilir. Her modun ne zaman kullanılacağı için yukarıdaki `definePostInstallLogicFunction` akordeonuna bakın. + +**Yeni şemanın mevcut olmasını gerektiren her şey için `post-install` kullanın.** Bu yaygın durumdur: + +* Yeni eklenen nesne ve alanlara karşı varsayılan verileri tohumlama (ilk kayıtları, varsayılan görünümleri, demo içeriği oluşturma). +* Uygulamanın kimlik bilgileri artık mevcut olduğuna göre, üçüncü taraf hizmetlerle webhook'ları kaydetmek. +* Eşitlenmiş üstveriye (metadata) bağlı kurulumu tamamlamak için kendi API'nizi çağırmak. +* Her yükseltmede durumu uzlaştırması gereken idempotent "bu mevcut olsun" mantığı — `shouldRunOnVersionUpgrade: true` ile birleştirin. + +Örnek — kurulumdan sonra varsayılan bir `PostCard` kaydı tohumlama: + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion }: InstallPayload): Promise => { + if (previousVersion) return; // fresh installs only + + const client = createClient(); + await client.postCard.create({ + data: { title: 'Welcome to Postcard', content: 'Your first card!' }, + }); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Seeds a welcome post card after install.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + handler, +}); +``` + +**Bir geçiş mevcut verileri aksi takdirde silecek veya bozacaksa `pre-install` kullanın.** Kurulum öncesi *önceki* şemaya karşı çalıştığı ve hatalandığında yükseltmeyi geri aldığı için, riskli olan her şey için doğru yerdir: + +* **Kaldırılmak veya yeniden yapılandırılmak üzere olan verileri yedekleme** — örn. v2'de bir alanı kaldırıyorsunuz ve geçiş çalışmadan önce değerlerini başka bir alana kopyalamanız veya depolamaya aktarmanız gerekiyor. +* **Yeni bir kısıtın geçersiz kılacağı kayıtları arşivleme** — örn. bir alan `NOT NULL` oluyor ve önce null değerli satırları silmeniz veya düzeltmeniz gerekiyor. +* **Uyumluluğu doğrulama ve mevcut veriler temiz bir şekilde geçirilemiyorsa yükseltmeyi reddetme** — işleyiciden hata fırlatın ve kurulum, herhangi bir değişiklik uygulanmadan iptal edilir. Bu, uyumsuzluğu geçişin ortasında keşfetmekten daha güvenlidir. +* İlişkilendirmeyi kaybettirecek bir şema değişikliğinden önce **verileri yeniden adlandırma veya yeniden anahtarlama**. + +Örnek — yıkıcı bir geçişten önce kayıtları arşivleme: + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { + // Only the 1.x → 2.x upgrade drops the legacy `notes` field. + if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { + return; + } + + const client = createClient(); + const legacyRecords = await client.postCard.findMany({ + where: { notes: { isNotNull: true } }, + }); + + if (legacyRecords.length === 0) return; + + // Copy legacy `notes` into the new `description` field before the migration + // drops the `notes` column. If this fails, the upgrade is aborted and the + // workspace stays on v1 with all data intact. + await Promise.all( + legacyRecords.map((record) => + client.postCard.update({ + where: { id: record.id }, + data: { description: record.notes }, + }), + ), + ); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Backs up legacy notes into description before the v2 migration.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +**Kural olarak:** + +| You want to... | Kullan | +| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | +| Varsayılan verileri tohumlamak, çalışma alanını yapılandırmak, harici kaynakları kaydetmek | `post-install` | +| Kurulum yanıtını engellememesi gereken uzun süreli tohumlama veya üçüncü taraf çağrılarını çalıştırmak | `post-install` (varsayılan — `shouldRunSynchronously: false`, worker yeniden denemeleriyle) | +| Kurulum çağrısı döner dönmez çağıranın güveneceği hızlı kurulumu çalıştırmak | `post-install` ile `shouldRunSynchronously: true` | +| Yaklaşan geçişin kaybedeceği verileri okumak veya yedeklemek | `pre-install` | +| Mevcut verileri bozacak bir yükseltmeyi reddetmek | `pre-install` (işleyiciden hata fırlatmak) | +| Her yükseltmede uzlaştırma çalıştırmak | `post-install` ile `shouldRunOnVersionUpgrade: true` | +| Yalnızca ilk kurulumda tek seferlik kurulum yapmak | `post-install` ile `shouldRunOnVersionUpgrade: false` (varsayılan) | + + +Emin değilseniz, varsayılan olarak **kurulum sonrası**nı tercih edin. Yalnızca geçişin kendisi yıkıcıysa ve önceki durum yok olmadan önce onu yakalamanız gerekiyorsa kurulum öncesine başvurun. + + + + + +## Tipli API istemcileri (twenty-client-sdk) + +`twenty-client-sdk` paketi, mantık fonksiyonlarınızdan ve ön uç bileşenlerinizden Twenty API ile etkileşim kurmak için tip tanımlı iki GraphQL istemcisi sağlar. + +| İstemci | İçe Aktar | Uç nokta | Oluşturuldu mu? | +| ------------------- | ---------------------------- | ------------------------------------------------------------- | --------------------------------------- | +| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — çalışma alanı verileri (kayıtlar, nesneler) | Evet, geliştirme/derleme zamanında | +| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — çalışma alanı yapılandırması, dosya yüklemeleri | Hayır, önceden hazırlanmış olarak gelir | + + + + +`CoreApiClient`, çalışma alanı verilerini sorgulamak ve değiştirmek için ana istemcidir. `yarn twenty dev` veya `yarn twenty build` sırasında **çalışma alanı şemanızdan oluşturulur**, bu nedenle nesnelerinize ve alanlarınıza uyacak şekilde tamamen tiplenmiştir. + +```ts +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const client = new CoreApiClient(); + +// Query records +const { companies } = await client.query({ + companies: { + edges: { + node: { + id: true, + name: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, + }, + }, + }, +}); + +// Create a record +const { createCompany } = await client.mutation({ + createCompany: { + __args: { + data: { + name: 'Acme Corp', + }, + }, + id: true, + name: true, + }, +}); +``` + +İstemci bir seçim kümesi sözdizimi kullanır: Bir alanı dahil etmek için `true` geçin, bağımsız değişkenler için `__args` kullanın ve ilişkiler için nesneleri iç içe yerleştirin. Çalışma alanı şemanıza göre tam otomatik tamamlama ve tip denetimi elde edersiniz. + + +**CoreApiClient geliştirme/derleme zamanında oluşturulur.** Bunu önce `yarn twenty dev` veya `yarn twenty build` çalıştırmadan kullanırsanız, bir hata verir. Oluşturma otomatik olarak gerçekleşir — CLI, çalışma alanınızın GraphQL şemasını inceler ve `@genql/cli` kullanarak tiplenmiş bir istemci üretir. + + +#### Tür açıklamaları için CoreSchema'yı kullanma + +`CoreSchema`, çalışma alanı nesnelerinize uyan TypeScript türleri sağlar — bileşen durumunu veya işlev parametrelerini tiplemek için kullanışlıdır: + +```ts +import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; +import { useState } from 'react'; + +const [company, setCompany] = useState< + Pick | undefined +>(undefined); + +const client = new CoreApiClient(); +const result = await client.query({ + company: { + __args: { filter: { position: { eq: 1 } } }, + id: true, + name: true, + }, +}); +setCompany(result.company); +``` + + + + +`MetadataApiClient`, SDK ile birlikte önceden hazırlanmış olarak gelir (oluşturma gerektirmez). Çalışma alanı yapılandırması, uygulamalar ve dosya yüklemeleri için `/metadata` uç noktasını sorgular. + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; + +const metadataClient = new MetadataApiClient(); + +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, + }, +}); +``` + +#### Dosya yükleme + +`MetadataApiClient`, dosya türündeki alanlara dosya eklemek için bir `uploadFile` yöntemi içerir: + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import * as fs from 'fs'; + +const metadataClient = new MetadataApiClient(); + +const fileBuffer = fs.readFileSync('./invoice.pdf'); + +const uploadedFile = await metadataClient.uploadFile( + fileBuffer, // file contents as a Buffer + 'invoice.pdf', // filename + 'application/pdf', // MIME type + '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier +); + +console.log(uploadedFile); +// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } +``` + +| Parametre | Tür | Açıklama | +| ---------------------------------- | -------- | --------------------------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | Dosyanın ham içeriği | +| `filename` | `string` | Dosyanın adı (depolama ve görüntüleme için kullanılır) | +| `contentType` | `string` | MIME türü (belirtilmezse varsayılan olarak `application/octet-stream` kullanılır) | +| `fieldMetadataUniversalIdentifier` | `string` | Nesnenizdeki dosya türü alanının `universalIdentifier` değeri | + +Önemli noktalar: +* Alan için `universalIdentifier` kullanır (çalışma alanına özgü kimliği değil), böylece yükleme kodunuz uygulamanızın yüklü olduğu herhangi bir çalışma alanında çalışır. +* Döndürülen `url`, yüklenen dosyaya erişmek için kullanabileceğiniz imzalı bir URL'dir. + + + + + + Kodunuz Twenty üzerinde çalıştığında (mantık işlevleri veya ön uç bileşenleri), platform kimlik bilgilerini ortam değişkenleri olarak enjekte eder: + + * `TWENTY_API_URL` — Twenty API'nin temel URL'si + * `TWENTY_APP_ACCESS_TOKEN` — Uygulamanızın varsayılan işlev rolü kapsamında kısa ömürlü bir anahtar + + Bunları istemcilere iletmeniz gerekmez — otomatik olarak `process.env`'den okurlar. API anahtarının izinleri, `application-config.ts` içinde `defaultRoleUniversalIdentifier` ile referans verilen role göre belirlenir. + diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/publishing.mdx index 8b2ee50636f..0f82662a236 100644 --- a/packages/twenty-docs/l/tr/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/tr/developers/extend/apps/publishing.mdx @@ -1,12 +1,9 @@ --- title: Yayımlama +icon: yükle description: Twenty uygulamanızı pazaryerine sunun ya da dahili olarak dağıtın. --- - - Uygulamalar şu anda alfa aşamasında. Özellik işlevsel ancak hâlâ gelişmekte. - - ## Genel Bakış Uygulamanız [yerelde derlenip test edildikten sonra](/l/tr/developers/extend/apps/building), dağıtım için iki yolunuz vardır: diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/skills-and-agents.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/skills-and-agents.mdx new file mode 100644 index 00000000000..c887fe6898b --- /dev/null +++ b/packages/twenty-docs/l/tr/developers/extend/apps/skills-and-agents.mdx @@ -0,0 +1,69 @@ +--- +title: Beceriler ve Aracılar +description: Define AI skills and agents for your app. +icon: robot +--- + + + Skills and agents are currently in alpha. Özellik işlevsel ancak hâlâ gelişmekte. + + +Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts. + + + + +Yetenekler, yapay zekâ ajanlarının çalışma alanınızda kullanabileceği yeniden kullanılabilir yönergeleri ve kabiliyetleri tanımlar. Yerleşik doğrulamayla yetenekleri tanımlamak için `defineSkill()` kullanın: + +```ts src/skills/example-skill.ts +import { defineSkill } from 'twenty-sdk/define'; + +export default defineSkill({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'sales-outreach', + label: 'Sales Outreach', + description: 'Guides the AI agent through a structured sales outreach process', + icon: 'IconBrain', + content: `You are a sales outreach assistant. When reaching out to a prospect: +1. Research the company and recent news +2. Identify the prospect's role and likely pain points +3. Draft a personalized message referencing specific details +4. Keep the tone professional but conversational`, +}); +``` + +Önemli noktalar: +* `name`, yetenek için benzersiz bir tanımlayıcı dizedir (kebab-case önerilir). +* `label`, UI'de gösterilen, insan tarafından okunabilir addır. +* `content`, yetenek yönergelerini içerir — bu, yapay zekâ ajanının kullandığı metindir. +* `icon` (isteğe bağlı), UI'de gösterilen simgeyi ayarlar. +* `description` (isteğe bağlı), yeteneğin amacı hakkında ek bağlam sağlar. + + + + +Ajanlar, çalışma alanınız içinde bulunan yapay zekâ asistanlarıdır. Özel bir sistem istemiyle ajanlar oluşturmak için `defineAgent()` kullanın: + +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk/define'; + +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +Önemli noktalar: +* `name`, ajan için benzersiz bir tanımlayıcı dizedir (kebab-case önerilir). +* `label`, UI'de gösterilen görünen addır. +* `prompt`, ajanın davranışını tanımlayan sistem istemidir. +* `description` (isteğe bağlı), ajanın ne yaptığı hakkında bağlam sağlar. +* `icon` (isteğe bağlı), UI'de gösterilen simgeyi ayarlar. +* `modelId` (isteğe bağlı), ajanın kullandığı varsayılan yapay zekâ modelini geçersiz kılar. + + + diff --git a/packages/twenty-docs/l/tr/developers/extend/oauth.mdx b/packages/twenty-docs/l/tr/developers/extend/oauth.mdx new file mode 100644 index 00000000000..b998d0c74b2 --- /dev/null +++ b/packages/twenty-docs/l/tr/developers/extend/oauth.mdx @@ -0,0 +1,189 @@ +--- +title: OAuth +icon: anahtar +description: Authorization code flow with PKCE and client credentials for server-to-server access. +--- + +Twenty implements OAuth 2.0 with authorization code + PKCE for user-facing apps and client credentials for server-to-server access. Clients are registered dynamically via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) — no manual setup in a dashboard. + +## When to Use OAuth + +| Senaryo | Auth Method | +| --------------------------------------- | -------------------------------------------------------------------------------- | +| Internal scripts, automation | [API Key](/l/tr/developers/extend/api#authentication) | +| External app acting on behalf of a user | **OAuth — Authorization Code** | +| Server-to-server, no user context | **OAuth — Client Credentials** | +| Twenty App with UI extensions | [Apps](/l/tr/developers/extend/apps/getting-started) (OAuth is handled automatically) | + +## Register a Client + +Twenty supports **dynamic client registration** per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). No manual setup needed — register programmatically: + +```bash +POST /oauth/register +Content-Type: application/json + +{ + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"], + "grant_types": ["authorization_code"], + "token_endpoint_auth_method": "client_secret_post" +} +``` + +**Response:** + +```json +{ + "client_id": "abc123", + "client_secret": "secret456", + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"] +} +``` + + +Store the `client_secret` securely — it cannot be retrieved later. + + +## Kapsamlar + +| Scope | Erişim | +| -------- | ---------------------------------------------------- | +| `api` | Full read/write access to the Core and Metadata APIs | +| `profil` | Read the authenticated user's profile information | + +Request scopes as a space-separated string: `scope=api profile` + +## Authorization Code Flow + +Use this flow when your app acts on behalf of a Twenty user. + +### 1. Redirect the user to authorize + +``` +GET /oauth/authorize? + client_id=YOUR_CLIENT_ID& + response_type=code& + redirect_uri=https://myapp.com/callback& + scope=api& + state=random_state_value& + code_challenge=CHALLENGE& + code_challenge_method=S256 +``` + +| Parametre | Zorunlu | Açıklama | +| ----------------------- | -------- | ------------------------------------------------------------ | +| `client_id` | Evet | Your registered client ID | +| `response_type` | Evet | Must be `code` | +| `redirect_uri` | Evet | Must match a registered redirect URI | +| `scope` | Hayır | Space-separated scopes (defaults to `api`) | +| `durum` | Önerilen | Random string to prevent CSRF attacks | +| `code_challenge` | Önerilen | PKCE challenge (SHA-256 hash of verifier, base64url-encoded) | +| `code_challenge_method` | Önerilen | Must be `S256` when using PKCE | + +The user sees a consent screen and approves or denies access. + +### 2. Handle the callback + +After authorization, Twenty redirects back to your `redirect_uri`: + +``` +https://myapp.com/callback?code=AUTH_CODE&state=random_state_value +``` + +Verify that `state` matches what you sent. + +### 3. Exchange the code for tokens + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=authorization_code& +code=AUTH_CODE& +redirect_uri=https://myapp.com/callback& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +code_verifier=YOUR_PKCE_VERIFIER +``` + +**Response:** + +```json +{ + "access_token": "eyJhbG...", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "dGhpcyBpcyBh..." +} +``` + +### 4. Use the access token + +```bash +GET /rest/companies +Authorization: Bearer ACCESS_TOKEN +``` + +### 5. Refresh when expired + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=refresh_token& +refresh_token=YOUR_REFRESH_TOKEN& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET +``` + +## Client Credentials Flow + +For server-to-server integrations with no user interaction: + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=client_credentials& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +scope=api +``` + +The returned token has workspace-level access, not tied to any specific user. + +## Server Discovery + +Twenty publishes its OAuth configuration at a standard discovery endpoint: + +``` +GET /.well-known/oauth-authorization-server +``` + +This returns all endpoints, supported grant types, scopes, and capabilities — useful for building generic OAuth clients. + +## API Endpoints Summary + +| Uç nokta | Amaç | +| ----------------------------------------- | --------------------------- | +| `/.well-known/oauth-authorization-server` | Server metadata discovery | +| `/oauth/register` | Dynamic client registration | +| `/oauth/authorize` | User authorization | +| `/oauth/token` | Token exchange and refresh | + +| Ortam | Temel URL | +| ---------------------------- | ------------------------ | +| **Bulut** | `https://api.twenty.com` | +| **Kendi Kendine Barındırma** | `https://{your-domain}` | + +## OAuth vs API Keys + +| | API Anahtarları | OAuth | +| ------------------ | ----------------------- | -------------------------------------- | +| **Kurulum** | Generate in Settings | Register a client, implement flow | +| **User context** | None (workspace-level) | Specific user's permissions | +| **En uygun** | Scripts, internal tools | External apps, multi-user integrations | +| **Token rotation** | Manuel | Automatic via refresh tokens | +| **Scoped access** | Full API access | Granular via scopes | diff --git a/packages/twenty-docs/l/tr/developers/extend/webhooks.mdx b/packages/twenty-docs/l/tr/developers/extend/webhooks.mdx index d108c7f0ae5..13a9185f480 100644 --- a/packages/twenty-docs/l/tr/developers/extend/webhooks.mdx +++ b/packages/twenty-docs/l/tr/developers/extend/webhooks.mdx @@ -1,11 +1,12 @@ --- title: Webhook'lar -description: CRM'inizde olaylar gerçekleştiğinde gerçek zamanlı bildirimler alın. +icon: satellite-dish +description: Get notified when records change — HTTP POST to your endpoint on every create, update, or delete. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -Webhook'lar, Twenty'de olaylar gerçekleştiğinde verileri sistemlerinize gerçek zamanlı olarak iletir — sürekli sorgulamaya gerek yok. Harici sistemleri senkron tutmak, otomasyonları tetiklemek veya uyarılar göndermek için bunları kullanın. +Twenty sends an HTTP POST to your URL whenever a record is created, updated, or deleted. All object types are covered, including custom objects. ## Webhook oluştur diff --git a/packages/twenty-docs/l/tr/developers/introduction.mdx b/packages/twenty-docs/l/tr/developers/introduction.mdx index 943afd7952b..7b03cca4dfb 100644 --- a/packages/twenty-docs/l/tr/developers/introduction.mdx +++ b/packages/twenty-docs/l/tr/developers/introduction.mdx @@ -1,23 +1,28 @@ --- -title: Başlarken -description: Twenty Geliştirici Belgeleri'ne hoş geldiniz; Twenty'yi genişletme, kendi altyapınızda barındırma ve Twenty'ye katkıda bulunma için kaynaklarınız. +title: Geliştiriciler +description: Build apps, use the API, self-host, or contribute to the codebase. --- import { CardTitle } from "/snippets/card-title.mdx" - - Extend - API'ler, webhook'lar ve özel uygulamalarla entegrasyonlar oluşturun. + + Apps + Extend Twenty with custom objects, server-side logic, UI components, and AI agents — all as TypeScript packages. - + + API + REST and GraphQL APIs, webhooks, and OAuth. + + + Self-Host - Twenty'yi kendi altyapınızda dağıtın ve yönetin. + Run Twenty on your own infrastructure. - + Contribute - Açık kaynak topluluğumuza katılın ve Twenty'ye katkıda bulunun. + Set up the monorepo locally and submit PRs. diff --git a/packages/twenty-docs/l/tr/developers/self-host/capabilities/cloud-providers.mdx b/packages/twenty-docs/l/tr/developers/self-host/capabilities/cloud-providers.mdx index a85000fcad4..c340354e6f0 100644 --- a/packages/twenty-docs/l/tr/developers/self-host/capabilities/cloud-providers.mdx +++ b/packages/twenty-docs/l/tr/developers/self-host/capabilities/cloud-providers.mdx @@ -1,5 +1,6 @@ --- title: Diğer yöntemler +icon: cloud --- diff --git a/packages/twenty-docs/l/tr/developers/self-host/capabilities/docker-compose.mdx b/packages/twenty-docs/l/tr/developers/self-host/capabilities/docker-compose.mdx index 777054be58e..531e18e62a1 100644 --- a/packages/twenty-docs/l/tr/developers/self-host/capabilities/docker-compose.mdx +++ b/packages/twenty-docs/l/tr/developers/self-host/capabilities/docker-compose.mdx @@ -1,5 +1,6 @@ --- -title: 1-Tıklama ile Docker Compose +title: Docker Compose +icon: docker --- diff --git a/packages/twenty-docs/l/tr/developers/self-host/capabilities/setup.mdx b/packages/twenty-docs/l/tr/developers/self-host/capabilities/setup.mdx index 28d1dd32f17..5ef7fd0e8ca 100644 --- a/packages/twenty-docs/l/tr/developers/self-host/capabilities/setup.mdx +++ b/packages/twenty-docs/l/tr/developers/self-host/capabilities/setup.mdx @@ -1,5 +1,6 @@ --- title: Kurulum +icon: gear --- # Konfigürasyon Yönetimi diff --git a/packages/twenty-docs/l/tr/developers/self-host/capabilities/troubleshooting.mdx b/packages/twenty-docs/l/tr/developers/self-host/capabilities/troubleshooting.mdx index d38259d23bc..d5d499ef670 100644 --- a/packages/twenty-docs/l/tr/developers/self-host/capabilities/troubleshooting.mdx +++ b/packages/twenty-docs/l/tr/developers/self-host/capabilities/troubleshooting.mdx @@ -1,5 +1,6 @@ --- title: Sorun Giderme +icon: wrench --- ## Sorun Giderme diff --git a/packages/twenty-docs/l/tr/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/tr/developers/self-host/capabilities/upgrade-guide.mdx index 047b927d8ac..fb2c38194c6 100644 --- a/packages/twenty-docs/l/tr/developers/self-host/capabilities/upgrade-guide.mdx +++ b/packages/twenty-docs/l/tr/developers/self-host/capabilities/upgrade-guide.mdx @@ -1,5 +1,6 @@ --- title: Yükseltme rehberi +icon: arrow-up-right-dots --- ## Genel kılavuzlar @@ -16,359 +17,14 @@ Docker Compose kullandıysanız, aşağıdaki adımları izleyin: 3. Twenty'yi `docker compose up -d` ile tekrar çevrimiçi hale getirin. -Örneğin v0.33.0'dan v0.35.0'a birkaç sürüm birden yükseltmek istiyorsanız, önce v0.33.0'dan v0.34.0'a, ardından v0.34.0'dan v0.35.0'a sıralı şekilde yükseltmelisiniz. - **Her bir yükseltme sonrasında bozulmamış bir yedeğiniz olduğundan emin olun.** ## Sürüme özel yükseltme adımları -## v1.0 +## After v1.21 -Merhaba Twenty v1.0! 🎉 +We know support sequential upgrades. You don't need to go through each version one by one. -## v0.60 +## Before v1.21 -### Performans İyileştirmeleri - -Tüm meta veri API etkileşimleri, özellikle nesne meta verisi manipülasyonu ve çalışma alanı oluşturma işlemleri için daha iyi performans sağlanacak şekilde optimize edilmiştir. - -Mümkün olduğunda veritabanı sorgularına kıyasla önbellek isabetlerini önceliklendirecek şekilde önbellekleme stratejimizi yeniden düzenledik; bu, meta veri API işlemlerinin performansını önemli ölçüde iyileştirdi. - -Yükseltme sonrasında herhangi bir çalıştırma sorunu yaşarsanız, önbelleğinizi temizlemeniz gerekebilir, böylece en son değişikliklerle senkronize olmuş olur. twenty-server konteynerınızda bu komutu çalıştırın: - -```bash -yarn command:prod cache:flush -``` - -### v0.55 - -Twenty örneğinizi v0.55 görüntüsünü kullanacak şekilde yükseltin - -Artık hiçbir komut çalıştırmanız gerekmeyecek, yeni görüntü gerekli tüm migrasyonları otomatik olarak yapacaktır. - -### `User does not have permission` hatası - -Yükseltme sonrası çoğu istekte yetkilendirme hatalarıyla karşılaşırsanız, en son izinleri yeniden hesaplamak için önbelleğinizi temizlemeniz gerekebilir. - -`twenty-server` konteynerinizde şu komutu çalıştırın: - -```bash -yarn command:prod cache:flush -``` - -Bu sorun, bu Twenty sürümüne özgüdür ve gelecekteki yükseltmeler için gerekli olmamalıdır. - -### v0.54 - -`0.53` sürümünden itibaren, manuel işlem gerekmiyor. - -#### Meta veri şeması kullanımının kaldırılması - -Veri alımını basitleştirmek için `metadata` şemasını `core` şemasına birleştirdik. -`yükseltme` komutu içindeki `migrate` komut adımını birleştirdik. Sunucu/işçi konteynerlarınızın herhangi birinde `migrate` komutunu elle çalıştırmanızı önermiyoruz. - -### v0.53'ten itibaren - -`0.53` sürümünden itibaren, yükseltme `DockerFile` içinde programatik olarak yapılmaktadır, bu nedenle artık hiçbir komutu manuel olarak çalıştırmanız gerekmemektedir. - -Örneğinizi, hiçbir ana sürümü atlamadan sıralı şekilde yükselttiğinizden emin olun (ör. `0.43.3`'ten `0.44.0`'a izin verilir, ancak `0.43.1`'den `0.45.0`'a izin verilmez); aksi halde bu durum, çalışma alanı sürümünde senkronizasyon kaybına yol açarak çalışma zamanı hatalarına ve işlev eksikliğine neden olabilir. - -Bir çalışma alanının doğru şekilde taşınıp taşımadığını kontrol etmek için veritabanındaki `core.workspace` tablosundaki sürümünü inceleyebilirsiniz. - -Her zaman, mevcut Twenty örneğinizin `major.minor` sürüm aralığında olması gerekir, örnek sürümünüzü yönetici panelinde (veritabanında `canAccessFullAdminPanel` özelliği true olarak ayarlandığında `settings/admin-panel` altında bulunabilir) veya `twenty-server` konteynerinizde `echo $APP_VERSION` çalıştırarak görüntüleyebilirsiniz. - -Senkronsuz bir çalışma alanı sürümünü düzeltmek için, ilgili yükseltme kılavuzunu izleyerek, istenen sürüme ulaşana kadar sıralı şekilde yükseltmeniz gerekecektir. - -#### `auditLog` kaldırılması - -AuditLog standart nesnesini kaldırdık, bu da bu geçişten sonra yedekleme boyutunuzun önemli ölçüde azalabileceği anlamına gelir. - -### v0.51'den v0.52'ye - -Twenty sürümünüzü v0.52 görüntüsünü kullanacak şekilde yükseltin - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### Sürümü `0.52.0` ile `0.52.6` arasında takılı kalan bir çalışma alanım var - -Maalesef, `0.52.0` ve `0.52.6` tamamen dockerHub'dan kaldırıldı. -Veritabanında çalışma alanı sürümünüzü manuel olarak `0.51.0` olarak güncelleyip, yukarıdaki yükseltme kılavuzunu takip ederek Twenty sürümü `0.52.11` ile yükseltmeniz gerekecek. - -### v0.50'den v0.51'e - -Twenty sürümünüzü v0.51 görüntüsünü kullanacak şekilde yükseltin - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.44.0'dan v0.50.0'a - -Twenty sürümünüzü v0.50.0 görüntüsünü kullanacak şekilde yükseltin - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### Docker-compose.yml mutasyonu - -Bu sürüm, `worker` hizmetine `server-local-data` hacmine erişim vermek için bir `docker-compose.yml` mutasyonu içerir. -Yerel `docker-compose.yml` dosyanızı [v0.50.0 docker-compose.yml](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml) ile güncelleyin - -### v0.43.0'dan v0.44.0'a - -Twenty sürümünüzü v0.44.0 görüntüsünü kullanacak şekilde yükseltin - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.42.0'dan v0.43.0'a - -Twenty sürümünüzü v0.43.0 görüntüsünü kullanacak şekilde yükseltin - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -Bu sürümde ayrıca docker-compose.yml içinde postgres:16 görüntüsüne geçiş yaptık. - -#### (Seçenek 1) Veri tabanı geçişi - -Mevcut postgres-spilo görüntüsünü korumak uygundur, ancak sürümü docker-compose.yml dosyanızda 0.43.0 olarak dondurmanız gerekecektir. - -#### (Seçenek 2) Veri tabanı geçişi - -Veri tabanınızı yeni postgres:16 görüntüsüne geçirmek istiyorsanız, lütfen bu adımları izleyin: - -1. Eski postgres-spilo konteynerinden veritabanınızı dökün - -``` -docker exec -it twenty-db-1 sh -pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql -exit -docker cp twenty-db-1:/home/postgres/databases_backup.sql . -``` - -Yedekleme dosyanızın boş olmadığından emin olun. - -2. docker-compose.yml dosyanızı postgres:16 görüntüsü ile kullanacak şekilde yükseltin [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) dosyasında belirtildiği gibi güncelleyin. - -3. Veri tabanını yeni postgres:16 konteynerine geri yükleyin - -``` -docker cp databases_backup.sql twenty-db-1:/databases_backup.sql -docker exec -it twenty-db-1 sh -psql -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} -f databases_backup.sql -exit -``` - -### v0.41.0'dan v0.42.0'ye - -Twenty sürümünüzü v0.42.0 görüntüsünü kullanacak şekilde yükseltin - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.42 -``` - -**Çevre Değişkenleri** - -* Kaldırıldı: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT` -* Eklendi: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED` - -### v0.40.0'dan v0.41.0'e - -Twenty sürümünüzü v0.41.0 görüntüsünü kullanacak şekilde yükseltin - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.41 -``` - -**Çevre Değişkenleri** - -* Kaldırıldı: `AUTH_MICROSOFT_TENANT_ID` - -### v0.35.0'dan v0.40.0'a - -Twenty sürümünüzü v0.40.0 görüntüsünü kullanacak şekilde yükseltin - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.40 -``` - -**Çevre Değişkenleri** - -* Eklendi: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL` - -### v0.34.0'dan v0.35.0'a - -Twenty sürümünüzü v0.35.0 görüntüsünü kullanacak şekilde yükseltin - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.35 -``` - -`yarn database:migrate:prod` komutu, veritabanı yapısına (çıkış ve meta veri şemaları) migrasyonları uygulayacaktır. `yarn command:prod upgrade-0.35` tüm çalışma alanlarının veri migrasyonunu sağlar. - -**Çevre Değişkenleri** - -* `ENABLE_DB_MIGRATIONS` ile `DISABLE_DB_MIGRATIONS`'ı değiştirdik (varsayılan değer artık `false`, muhtemelen bir şey ayarlamanız gerekmez) - -### v0.33.0'dan v0.34.0'a - -Twenty örneğinizi v0.34.0 görüntüsünü kullanacak şekilde yükseltin - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.34 -``` - -`yarn database:migrate:prod` komutu veritabanı yapısına (çıkış ve meta veri şemaları) migrasyonları uygulayacaktır. `yarn command:prod upgrade-0.34`, tüm çalışma alanlarının veri migrasyonunu sağlar. - -**Çevre Değişkenleri** - -* Kaldırıldı: `FRONT_BASE_URL` -* Eklendi: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT` - -Ön yüz URL'sini ele alma şeklimizi güncelledik. -Artık `FRONT_DOMAIN`, `FRONT_PROTOCOL` ve `FRONT_PORT` değişkenlerini kullanarak ön yüz URL'sini ayarlayabilirsiniz. -FRONT_DOMAIN ayarlanmazsa, ön yüz URL'si `SERVER_URL`'ye geri dönecektir. - -### v0.32.0'dan v0.33.0'a - -Twenty örneğinizi v0.33.0 görüntüsünü kullanacak şekilde yükseltin - -``` -yarn command:prod cache:flush -yarn database:migrate:prod -yarn command:prod upgrade-0.33 -``` - -`yarn command:prod cache:flush` komutu Redis önbelleğini temizler. -`yarn database:migrate:prod` komutu veritabanı yapısına (çıkış ve meta veri şemaları) migrasyonları uygulayacaktır. `yarn command:prod upgrade-0.33` tüm çalışma alanlarının veri migrasyonunu sağlar. - -Bu sürümden itibaren, DB için twenty-postgres görüntüsü kullanımdan kalktı ve yerine twenty-postgres-spilo kullanılıyor. -Twenty-postgres görüntüsünü kullanmaya devam etmek istiyorsanız, docker-compose.yml dosyasında `twentycrm/twenty-postgres:${TAG}`i `twentycrm/twenty-postgres` ile değiştirin. - -### v0.31.0'dan v0.32.0'ya - -Twenty örneğinizi v0.32.0 görüntüsünü kullanacak şekilde yükseltin - -**Şema ve veri migrasyonu** - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.32 -``` - -`yarn database:migrate:prod` komutu veritabanı yapısına (çıkış ve meta veri şemaları) migrasyonları uygulayacaktır. `yarn command:prod upgrade-0.32` tüm çalışma alanlarının veri migrasyonunu sağlar. - -**Çevre Değişkenleri** - -Redis bağlantısını ele alma şeklimizi güncelledik. - -* Kaldırıldı: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD` -* Eklendi: `REDIS_URL` - -Çevre dosyanızı ayrı ayrı Redis bağlantı parametreleri yerine yeni `REDIS_URL` değişkenini kullanacak şekilde güncelleyin. - -JWT tokenlarını ele alma şeklimizi de basitleştirdik. - -* Kaldırıldı: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET` -* Eklendi: `APP_SECRET` - -`.env` dosyanızı, ayrı ayrı güvenlik bilgileri yerine yeni `APP_SECRET` değişkenini kullanacak şekilde güncelleyin (önceden kullandığınız aynı güvenlik bilgilerini kullanabilir veya yeni bir rastgele dizgi üretebilirsiniz) - -**Bağlı Hesap** - -Google hesaplarınızı senkronize etmek için bir bağlı hesap kullanıyorsanız, Google Yönetici konsolunuzda [People API](https://developers.google.com/people) etkinleştirilmelidir. - -### v0.30.0'dan v0.31.0'e - -Twenty örneğinizi v0.31.0 görüntüsünü kullanacak şekilde yükseltin - -**Şema ve veri migrasyonu**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.31 -``` - -`yarn database:migrate:prod` komutu veritabanı yapısına (çıkış ve meta veri şemaları) migrasyonları uygulayacaktır. `yarn command:prod upgrade-0.31` tüm çalışma alanlarının veri migrasyonunu sağlar. - -### v0.24.0'dan v0.30.0'a - -Twenty sürümünüzü v0.30.0 görüntüsünü kullanacak şekilde yükseltin - -**Yıkıcı değişiklik**: -Performansı artırmak için Twenty artık redis önbelleğinin yapılandırılmasını gerektirir. [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) dosyamızı bu durumu yansıtacak şekilde güncelledik. -Yapılandırmanızı güncellediğinizden ve ortam değişkenlerinizi uygun şekilde güncellediğinizden emin olun: - -``` -REDIS_HOST={your-redis-host} -REDIS_PORT={your-redis-port} -CACHE_STORAGE_TYPE=redis -``` - -**Şema ve veri migrasyonu**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.30 -``` - -`yarn database:migrate:prod` komutu veritabanı yapısına (çıkış ve meta veri şemaları) migrasyonları uygulayacaktır. `yarn command:prod upgrade-0.30` tüm çalışma alanlarının veri migrasyonunu sağlar. - -### v0.23.0'dan v0.24.0'a - -Twenty sürümünüzü v0.24.0 görüntüsünü kullanacak şekilde yükseltin - -Aşağıdaki komutları çalıştırın: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.24 -``` - -`yarn database:migrate:prod` komutu veritabanı yapısına (çıkış ve metadata şemaları) migrasyonları uygular. `yarn command:prod upgrade-0.24`, tüm çalışma alanlarının veri göçünü ele alır. - -### v0.22.0'dan v0.23.0'a - -Twenty sürümünüzü v0.23.0 görüntüsünü kullanacak şekilde yükseltin - -Aşağıdaki komutları çalıştırın: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.23 -``` - -`yarn database:migrate:prod` komutu veritabanı yapısına göçleri uygular. -`yarn command:prod upgrade-0.23` veri göçünü yönetir, etkinlikleri görev/nota aktarımı da dahil. - -### v0.21.0'dan v0.22.0'a - -Twenty sürümünüzü v0.22.0 görüntüsünü kullanacak şekilde yükseltin - -Aşağıdaki komutları çalıştırın: - -``` -yarn database:migrate:prod -yarn command:prod workspace:sync-metadata -f -yarn command:prod upgrade-0.22 -``` - -`yarn database:migrate:prod` komutu veritabanı yapısına göçleri uygular. -`yarn command:prod workspace:sync-metadata -f` komutu, standart nesnelerin tanımını meta veri tablolarıyla eşitleyecek ve mevcut çalışma alanlarına gerekli geçişleri uygulayacaktır. -`yarn command:prod upgrade-0.22` komutu, yeni nesnenin defaultRequestInstrumentationOptions değerine uyum sağlamak için belirli veri dönüşümlerini uygulayacaktır. +Make sure to go through every major tagged version when upgrading (upgrade v1.6.x to v.7.y, then v.7.y to v.8.z, etc.). diff --git a/packages/twenty-docs/l/tr/navigation.json b/packages/twenty-docs/l/tr/navigation.json index 2604ccf308f..a2a796b3d52 100644 --- a/packages/twenty-docs/l/tr/navigation.json +++ b/packages/twenty-docs/l/tr/navigation.json @@ -1,24 +1,27 @@ { "tabs": { + "gettingStarted": { + "label": "Başlarken", + "groups": { + "welcome": { + "label": "Welcome" + }, + "coreConcepts": { + "label": "Core Concepts" + } + } + }, "userGuide": { "label": "Kullanıcı Rehberi", "groups": { - "discoverTwenty": { - "label": "Twenty'yi Keşfedin", - "groups": { - "gettingStartedCapabilities": { - "label": "Yetkinlikler" - }, - "gettingStartedHowTos": { - "label": "Nasıl Yapılırlar" - } - } + "userGuideOverview": { + "label": "Genel Bakış" }, "dataModel": { "label": "Veri modeli", "groups": { - "dataModelCapabilities": { - "label": "Yetkinlikler" + "dataModelReference": { + "label": "Reference" }, "dataModelHowTos": { "label": "Nasıl Yapılırlar" @@ -28,8 +31,8 @@ "dataMigration": { "label": "Veri Geçişi", "groups": { - "dataMigrationCapabilities": { - "label": "Yetkinlikler" + "dataMigrationReference": { + "label": "Reference" }, "dataMigrationHowTos": { "label": "Nasıl Yapılırlar" @@ -39,8 +42,8 @@ "calendarEmails": { "label": "Takvim ve E-postalar", "groups": { - "calendarEmailsCapabilities": { - "label": "Yetkinlikler" + "calendarEmailsReference": { + "label": "Reference" }, "calendarEmailsHowTos": { "label": "Nasıl Yapılırlar" @@ -50,8 +53,8 @@ "workflows": { "label": "İş Akışları", "groups": { - "workflowsCapabilities": { - "label": "Yetkinlikler" + "workflowsReference": { + "label": "Reference" }, "workflowsHowTos": { "label": "Nasıl Yapılırlar", @@ -75,21 +78,26 @@ "ai": { "label": "AI", "groups": { - "aiCapabilities": { - "label": "Yetkinlikler" + "aiReference": { + "label": "Reference" }, "aiHowTos": { "label": "Nasıl Yapılırlar" } } }, - "viewsPipelines": { - "label": "Görünümler ve Boru Hatları", + "layout": { + "label": "Düzen", "groups": { - "viewsPipelinesCapabilities": { - "label": "Yetkinlikler" + "layoutReference": { + "label": "Reference", + "groups": { + "layoutViews": { + "label": "Görünümler" + } + } }, - "viewsPipelinesHowTos": { + "layoutHowTos": { "label": "Nasıl Yapılırlar" } } @@ -97,8 +105,8 @@ "dashboards": { "label": "Gösterge Panelleri", "groups": { - "dashboardsCapabilities": { - "label": "Yetkinlikler" + "dashboardsReference": { + "label": "Reference" }, "dashboardsHowTos": { "label": "Nasıl Yapılırlar" @@ -108,8 +116,8 @@ "permissionsAccess": { "label": "İzinler ve Erişim", "groups": { - "permissionsAccessCapabilities": { - "label": "Yetkinlikler" + "permissionsAccessReference": { + "label": "Reference" }, "permissionsAccessHowTos": { "label": "Nasıl Yapılırlar" @@ -119,8 +127,8 @@ "billing": { "label": "Faturalandırma", "groups": { - "billingCapabilities": { - "label": "Yetkinlikler" + "billingReference": { + "label": "Reference" }, "billingHowTos": { "label": "Nasıl Yapılırlar" @@ -130,8 +138,8 @@ "settings": { "label": "Ayarlar", "groups": { - "settingsCapabilities": { - "label": "Yetkinlikler" + "settingsReference": { + "label": "Reference" }, "settingsHowTos": { "label": "Nasıl Yapılırlar" @@ -143,59 +151,20 @@ "developers": { "label": "Geliştiriciler", "groups": { - "developersGroup": { - "label": "Geliştiriciler" + "developersOverview": { + "label": "Genel Bakış" }, - "extend": { - "label": "Genişlet", - "groups": { - "apps": { - "label": "Uygulamalar" - } - } + "apps": { + "label": "Uygulamalar" + }, + "api": { + "label": "API" }, "selfHost": { - "label": "Kendi Kendine Barındırma", - "groups": { - "selfHostCapabilities": { - "label": "Yetkinlikler" - } - } + "label": "Kendi Kendine Barındırma" }, "contribute": { - "label": "Katkıda Bulunun", - "groups": { - "contributeCapabilities": { - "label": "Yetkinlikler", - "groups": { - "frontendDevelopment": { - "label": "Frontend Geliştirme", - "groups": { - "twentyUi": { - "label": "Twenty UI", - "groups": { - "display": { - "label": "Görüntüle" - }, - "feedback": { - "label": "Geri Bildirim" - }, - "input": { - "label": "Girdi" - }, - "navigation": { - "label": "Gezinme" - } - } - } - } - }, - "backendDevelopment": { - "label": "Backend Geliştirme" - } - } - } - } + "label": "Katkıda Bulunun" } } } diff --git a/packages/twenty-docs/l/tr/twenty-ui/display/app-tooltip.mdx b/packages/twenty-docs/l/tr/twenty-ui/display/app-tooltip.mdx index ce84e25fc8b..9ae46b6787e 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/display/app-tooltip.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/display/app-tooltip.mdx @@ -1,5 +1,6 @@ --- title: Uygulama Araç İpucu +icon: mesaj --- diff --git a/packages/twenty-docs/l/tr/twenty-ui/display/checkmark.mdx b/packages/twenty-docs/l/tr/twenty-ui/display/checkmark.mdx index cef74c5e559..345d6515ddf 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/display/checkmark.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/display/checkmark.mdx @@ -1,5 +1,6 @@ --- title: Onay işareti +icon: circle-check --- diff --git a/packages/twenty-docs/l/tr/twenty-ui/display/icons.mdx b/packages/twenty-docs/l/tr/twenty-ui/display/icons.mdx index de6a06b83c6..b74d434b1ad 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/display/icons.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/display/icons.mdx @@ -1,5 +1,6 @@ --- title: İkonlar +icon: i̇konlar --- diff --git a/packages/twenty-docs/l/tr/twenty-ui/display/soon-pill.mdx b/packages/twenty-docs/l/tr/twenty-ui/display/soon-pill.mdx index 5dc31835890..91b1ff4d8af 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/display/soon-pill.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/display/soon-pill.mdx @@ -2,7 +2,6 @@ title: Yakında Rozeti --- - Bir şeyin yakında geleceğini belirtmek için küçük bir rozet veya "hap" biçimli etiket. ```jsx diff --git a/packages/twenty-docs/l/tr/twenty-ui/display/tag.mdx b/packages/twenty-docs/l/tr/twenty-ui/display/tag.mdx index b2834430e3f..05b24d4a106 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/display/tag.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/display/tag.mdx @@ -1,8 +1,8 @@ --- title: Etiket +icon: etiket --- - İçeriği görsel olarak kategorize etmek veya etiketlemek için bileşen. diff --git a/packages/twenty-docs/l/tr/twenty-ui/input/buttons.mdx b/packages/twenty-docs/l/tr/twenty-ui/input/buttons.mdx index 5abb215cefe..a9347f53b58 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/input/buttons.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/input/buttons.mdx @@ -1,5 +1,6 @@ --- title: Düğmeler +icon: hand-pointer --- diff --git a/packages/twenty-docs/l/tr/twenty-ui/input/checkbox.mdx b/packages/twenty-docs/l/tr/twenty-ui/input/checkbox.mdx index 56e7d010dbc..0f085a0b296 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/input/checkbox.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/input/checkbox.mdx @@ -1,5 +1,6 @@ --- title: Kontrol Kutusu +icon: square-check --- diff --git a/packages/twenty-docs/l/tr/twenty-ui/input/color-scheme.mdx b/packages/twenty-docs/l/tr/twenty-ui/input/color-scheme.mdx index 8d22b7c0d0f..cad06a2ea58 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/input/color-scheme.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/input/color-scheme.mdx @@ -1,5 +1,6 @@ --- title: Renk Şeması +icon: palet --- diff --git a/packages/twenty-docs/l/tr/twenty-ui/input/radio.mdx b/packages/twenty-docs/l/tr/twenty-ui/input/radio.mdx index 18e7ce440f7..7af7abd0a4c 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/input/radio.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/input/radio.mdx @@ -1,5 +1,6 @@ --- title: Radyo +icon: circle-dot --- diff --git a/packages/twenty-docs/l/tr/twenty-ui/input/toggle.mdx b/packages/twenty-docs/l/tr/twenty-ui/input/toggle.mdx index 9832d4ae2db..90fce272a87 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/input/toggle.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/input/toggle.mdx @@ -1,8 +1,8 @@ --- title: Toggle +icon: toggle-on --- - diff --git a/packages/twenty-docs/l/tr/twenty-ui/introduction.mdx b/packages/twenty-docs/l/tr/twenty-ui/introduction.mdx index 5f1098adf7b..ede2e5302fc 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/introduction.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/introduction.mdx @@ -1,5 +1,6 @@ --- title: Genel Bakış +icon: palet description: Twenty CRM için bileşen kütüphanesi --- diff --git a/packages/twenty-docs/l/tr/twenty-ui/navigation.mdx b/packages/twenty-docs/l/tr/twenty-ui/navigation.mdx index 74155bfb025..c5eb05da747 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/navigation.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/navigation.mdx @@ -1,5 +1,6 @@ --- title: Gezinme +icon: compass --- diff --git a/packages/twenty-docs/l/tr/twenty-ui/navigation/links.mdx b/packages/twenty-docs/l/tr/twenty-ui/navigation/links.mdx index 9f6f67db739..61f9cd7d3e3 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/navigation/links.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/navigation/links.mdx @@ -1,5 +1,6 @@ --- title: Bağlantılar +icon: bağlantı --- diff --git a/packages/twenty-docs/l/tr/twenty-ui/navigation/menu-item.mdx b/packages/twenty-docs/l/tr/twenty-ui/navigation/menu-item.mdx index 6be242e1721..5f85162a05b 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/navigation/menu-item.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/navigation/menu-item.mdx @@ -1,8 +1,8 @@ --- title: Menü Öğesi +icon: bars --- - Bir menü veya navigasyon listesinde kullanılmak üzere tasarlanmış çok yönlü bir menü öğesi. diff --git a/packages/twenty-docs/l/tr/twenty-ui/navigation/navigation-bar.mdx b/packages/twenty-docs/l/tr/twenty-ui/navigation/navigation-bar.mdx index d4198c14e89..779456d4bfc 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/navigation/navigation-bar.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/navigation/navigation-bar.mdx @@ -1,8 +1,8 @@ --- title: Gezinti Çubuğu +icon: bars --- - Birden fazla `NavigationBarItem` bileşeni içeren bir gezinti çubuğu oluşturur. diff --git a/packages/twenty-docs/l/tr/twenty-ui/progress-bar.mdx b/packages/twenty-docs/l/tr/twenty-ui/progress-bar.mdx index b925579032c..cdc51ba1231 100644 --- a/packages/twenty-docs/l/tr/twenty-ui/progress-bar.mdx +++ b/packages/twenty-docs/l/tr/twenty-ui/progress-bar.mdx @@ -2,7 +2,6 @@ title: Geri Bildirim --- - İlerlemeyi veya geri sayımı belirtir ve sağdan sola doğru hareket eder. diff --git a/packages/twenty-docs/l/tr/user-guide/billing/overview.mdx b/packages/twenty-docs/l/tr/user-guide/billing/overview.mdx index 1fff7accb51..7a597ac2958 100644 --- a/packages/twenty-docs/l/tr/user-guide/billing/overview.mdx +++ b/packages/twenty-docs/l/tr/user-guide/billing/overview.mdx @@ -3,7 +3,6 @@ title: Faturalandırma description: Twenty'nin fiyatlandırmasını anlayın ve aboneliğinizi yönetin. --- - Twenty, ekibinizin ihtiyaçlarına uygun esnek fiyatlandırma planları sunar. Aboneliğinizi yönetin, iş akışı kredilerinizi takip edin ve tüm faturalarınıza **Ayarlar → Faturalandırma** bölümünden erişin. ## Bu bölümde neler var diff --git a/packages/twenty-docs/l/tr/user-guide/calendar-emails/overview.mdx b/packages/twenty-docs/l/tr/user-guide/calendar-emails/overview.mdx index c6ad39e98d7..536aedc025e 100644 --- a/packages/twenty-docs/l/tr/user-guide/calendar-emails/overview.mdx +++ b/packages/twenty-docs/l/tr/user-guide/calendar-emails/overview.mdx @@ -3,7 +3,6 @@ title: Takvim ve E-postalar description: E-posta ve takvim hesaplarınızı Twenty'ye bağlayın. --- - ## Bağlantı Seçenekleri ### Google Hesabı (Gmail & Google Takvim) diff --git a/packages/twenty-docs/l/tr/user-guide/dashboards/overview.mdx b/packages/twenty-docs/l/tr/user-guide/dashboards/overview.mdx index baba7e7211e..39df52882a6 100644 --- a/packages/twenty-docs/l/tr/user-guide/dashboards/overview.mdx +++ b/packages/twenty-docs/l/tr/user-guide/dashboards/overview.mdx @@ -3,7 +3,6 @@ title: Gösterge Panelleri description: Twenty'de raporlama ve gösterge panellerinin temellerini öğrenin. --- - Panolar şu anda beta sürümünde. Bunları **Ayarlar → Güncellemeler → Erken Erişim** altında etkinleştirin. diff --git a/packages/twenty-docs/l/tr/user-guide/data-migration/overview.mdx b/packages/twenty-docs/l/tr/user-guide/data-migration/overview.mdx index a43c4b63ba8..cb7038d4a00 100644 --- a/packages/twenty-docs/l/tr/user-guide/data-migration/overview.mdx +++ b/packages/twenty-docs/l/tr/user-guide/data-migration/overview.mdx @@ -5,7 +5,6 @@ description: CSV dosyaları veya API aracılığıyla CRM verilerinizi içe ve d import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## İçe Aktarma Yöntemleri Twenty, veri içe aktarmak için iki ana yöntemi destekler: diff --git a/packages/twenty-docs/l/tr/user-guide/data-model/overview.mdx b/packages/twenty-docs/l/tr/user-guide/data-model/overview.mdx index 5d6e6c342f6..829f530c7e7 100644 --- a/packages/twenty-docs/l/tr/user-guide/data-model/overview.mdx +++ b/packages/twenty-docs/l/tr/user-guide/data-model/overview.mdx @@ -3,7 +3,6 @@ title: Veri modeli description: Veri modelinin ne olduğunu ve işinize uygun bir modeli nasıl tasarlayacağınızı öğrenin. --- - ## Veri modeli nedir? Veri modeli, CRM'nizde bilgilerin nasıl organize edildiğini tanımlayan yapıdır. Bunu müşteri verilerinizin **mimari planı** olarak düşünün — bir kez tasarlarsınız, sonra gerçek verilerinizle doldurursunuz. diff --git a/packages/twenty-docs/l/tr/user-guide/introduction.mdx b/packages/twenty-docs/l/tr/user-guide/introduction.mdx index cb0fcd3d6c3..e35e7370eb3 100644 --- a/packages/twenty-docs/l/tr/user-guide/introduction.mdx +++ b/packages/twenty-docs/l/tr/user-guide/introduction.mdx @@ -1,16 +1,11 @@ --- -title: Twenty'yi Keşfedin +title: Kullanıcı Rehberi description: Twenty Kullanıcı Kılavuzu'na hoş geldiniz; gelişmiş yapılandırmalar ve en iyi uygulamalar için başvuru kaynağınız. --- import { CardTitle } from "/snippets/card-title.mdx" - - Twenty'yi Keşfedin - Twenty'nin ne olduğunu ve işletmenize nasıl yardımcı olabileceğini öğrenin. - - Veri Modeli Veri modelinizi iş süreçlerinize uyacak şekilde özelleştirin. @@ -36,9 +31,9 @@ import { CardTitle } from "/snippets/card-title.mdx" Yapay zekâ ajanlarıyla ekibinizi güçlendirin. - - Görünümler ve Boru Hatları - Eyleme dönüştürülebilir görünümler ve boru hatlarıyla verilerinizi düzenleyin. + + Layout + Navigation, views, and record page customization. diff --git a/packages/twenty-docs/l/tr/user-guide/layout/capabilities/navigation.mdx b/packages/twenty-docs/l/tr/user-guide/layout/capabilities/navigation.mdx new file mode 100644 index 00000000000..ece17658349 --- /dev/null +++ b/packages/twenty-docs/l/tr/user-guide/layout/capabilities/navigation.mdx @@ -0,0 +1,32 @@ +--- +title: Gezinme +description: Customize the left sidebar to match how your team works. +--- + +The left sidebar is your primary way to move around Twenty. It's fully customizable — you can reorganize it to match your workflow without touching any settings page. + +## Reordering items + +Drag and drop any item in the sidebar to change its position. The order is saved per user, so each team member can arrange their own sidebar. + +## Klasörler + +Group related items into folders. For example, you might create a "Sales" folder containing your pipeline views, a "Support" folder for tickets, or an "Operations" folder for internal objects. + +To create a folder, right-click in the sidebar or use the `+` button. + +## Hiding objects + +Objects you don't use can be hidden from the sidebar. They're not deleted — they're just out of the way. You can show them again anytime from Settings > Data Model. + +## Favoriler + +Pin views, records, or searches to the Favorites section at the top of the sidebar for one-click access. Favorites are personal — each user manages their own. + +## Custom links + +Add links to external tools directly in the sidebar. Useful for linking to your wiki, dashboards in other tools, or any URL your team uses regularly. + +## Command menu + +Press `Cmd+K` (or `Ctrl+K`) to open the command menu — a quick-access search bar for jumping to any record, view, or action without navigating the sidebar. diff --git a/packages/twenty-docs/l/tr/user-guide/layout/capabilities/record-pages.mdx b/packages/twenty-docs/l/tr/user-guide/layout/capabilities/record-pages.mdx new file mode 100644 index 00000000000..3b0d8a66612 --- /dev/null +++ b/packages/twenty-docs/l/tr/user-guide/layout/capabilities/record-pages.mdx @@ -0,0 +1,51 @@ +--- +title: Kayıt Sayfaları +description: Her bir kayıt ayrıntı sayfasının düzenini sekmeler ve widget'larla özelleştirin. +--- + +Twenty'de bir kaydı açtığınızda, ayrıntı sayfası **sekmeler** ve **widget'lar** içerir. Her ikisi de her nesne türü bazında tamamen özelleştirilebilir. + +## Sekmeler + +Each record page can have multiple tabs — similar to tabs in a browser. Use them to organize different aspects of a record. For example, a Company record might have tabs for Overview, Communication, Tasks, and Files. + +Şunları yapabilirsiniz: + +* Add and remove tabs +* Rename tabs +* Reorder tabs by dragging +* Set which tab shows by default + +## Widget'lar + +Widgets are the building blocks inside each tab. Available widget types include: + +| Widget | What it shows | +| ------------------- | ------------------------------------------ | +| **Alan** | Record fields, grouped or individually | +| **Related records** | Table of records linked via a relation | +| **Emails** | Email history from connected accounts | +| **Calendar** | Calendar events associated with the record | +| **Timeline** | Activity and event history | +| **Tasks** | Associated tasks | +| **Notes** | Rich text notes | +| **Files** | Dosya ekleri | +| **Charts** | Visual data from related records | +| **iFrame** | Embedded external content | +| **Rich text** | Static content or descriptions | + +## Customizing a record page + +1. Open any record +2. Press `Cmd+K` and search for "Edit record page layout" +3. You're now in customization mode: + * **Add widgets** from the widget picker + * **Drag widgets** to reposition them on the grid + * **Resize widgets** by dragging their edges + * **Configure fields** shown within each widget + * **Manage tabs** — add, remove, rename, reorder +4. Save your changes — they apply to all records of that object type + +## Field visibility + +Within a Fields widget, you can control which fields are visible and in what order. This lets you create focused layouts — for example, showing only the most important fields on the Overview tab and putting detailed fields in a separate tab. diff --git a/packages/twenty-docs/l/tr/user-guide/layout/overview.mdx b/packages/twenty-docs/l/tr/user-guide/layout/overview.mdx new file mode 100644 index 00000000000..d3355ca6e0f --- /dev/null +++ b/packages/twenty-docs/l/tr/user-guide/layout/overview.mdx @@ -0,0 +1,45 @@ +--- +title: Düzen +description: Customize how you navigate, browse, and view records in Twenty. +--- + +Twenty's layout is customizable at three levels: how you navigate the app, how you browse lists of records, and what you see when you open an individual record. + +## Gezinme + +The left sidebar is fully customizable. Şunları yapabilirsiniz: + +* **Reorder items** by dragging and dropping +* **Create folders** to group related objects and views +* **Hide objects** you don't use +* **Add custom links** to external tools +* **Pin favorites** for quick access to views, records, or searches + +[Navigation reference →](/l/tr/user-guide/layout/capabilities/navigation) + +## Görünümler + +Views control how lists of records are displayed. Twenty supports three view types: + +| Görünüm | Best for | +| ------------ | ---------------------------------------------------------------------- | +| **Table** | Working with many records at once — spreadsheet-style rows and columns | +| **Kanban** | Pipeline tracking — drag-and-drop cards organized by stage | +| **Calendar** | Time-based planning — records plotted by a date field | + +Each view saves its own filters, sorting, field visibility, and grouping configuration. Views can be shared with the workspace or kept private. + +[Table views →](/l/tr/user-guide/views-pipelines/capabilities/table-views) · [Kanban views →](/l/tr/user-guide/views-pipelines/capabilities/kanban-views) · [Calendar view →](/l/tr/user-guide/views-pipelines/capabilities/calendar-view) + +## Record pages + +When you open a record, the detail page is built from configurable tabs and widgets. Şunları yapabilirsiniz: + +* **Add, remove, and reorder tabs** on any record type +* **Configure widgets** — fields, related records, emails, timeline, calendar, tasks, notes, files, charts, iframes, and more +* **Drag and resize widgets** on a grid layout +* **Control field visibility** per widget + +Enter layout customization mode from the command menu (`Cmd+K` → "Edit record page layout"). + +[Record pages reference →](/l/tr/user-guide/layout/capabilities/record-pages) diff --git a/packages/twenty-docs/l/tr/user-guide/permissions-access/overview.mdx b/packages/twenty-docs/l/tr/user-guide/permissions-access/overview.mdx index 793e0b5d143..d86c0769135 100644 --- a/packages/twenty-docs/l/tr/user-guide/permissions-access/overview.mdx +++ b/packages/twenty-docs/l/tr/user-guide/permissions-access/overview.mdx @@ -3,7 +3,6 @@ title: İzinler ve Erişim description: Çalışma alanınızda roller, izinler ve erişim denetimini yönetin. --- - Twenty'nin izin sistemi, çalışma alanınızdaki verilere kimin erişebileceğini ve bunları kimin değiştirebileceğini kontrol etmenizi sağlar. Roller oluşturun, izinler atayın ve güvenli erişim için SSO'yu yapılandırın. ## Bu bölümde neler var diff --git a/packages/twenty-docs/l/tr/user-guide/settings/overview.mdx b/packages/twenty-docs/l/tr/user-guide/settings/overview.mdx index f86ba06ca03..5908a5c2c2b 100644 --- a/packages/twenty-docs/l/tr/user-guide/settings/overview.mdx +++ b/packages/twenty-docs/l/tr/user-guide/settings/overview.mdx @@ -3,7 +3,6 @@ title: Ayarlar description: Twenty çalışma alanınızı temel yapılandırmalarla kurun. --- - ## İlk Kurulum Çalışma alanınızı ilk oluşturduğunuzda yapılandırmanız gereken birkaç temel ayar vardır. diff --git a/packages/twenty-docs/l/tr/user-guide/views-pipelines/overview.mdx b/packages/twenty-docs/l/tr/user-guide/views-pipelines/overview.mdx index 84a343b7af9..fbd3b5104a0 100644 --- a/packages/twenty-docs/l/tr/user-guide/views-pipelines/overview.mdx +++ b/packages/twenty-docs/l/tr/user-guide/views-pipelines/overview.mdx @@ -5,7 +5,6 @@ description: Twenty'de görünümler oluşturmayı ve yönetmeyi öğrenin. import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## Görünümleri Anlama Görünümler, verilerinizin nasıl görüntüleneceğini belirleyen kaydedilmiş yapılandırmalardır. Her görünümün kendine ait şunları olabilir: diff --git a/packages/twenty-docs/l/tr/user-guide/workflows/overview.mdx b/packages/twenty-docs/l/tr/user-guide/workflows/overview.mdx index 3c56f6de9bc..290e7296829 100644 --- a/packages/twenty-docs/l/tr/user-guide/workflows/overview.mdx +++ b/packages/twenty-docs/l/tr/user-guide/workflows/overview.mdx @@ -3,7 +3,6 @@ title: İş Akışları description: Twenty'de otomasyonlar oluşturmayı öğrenin. --- - ## İş Akışları Neden Önemlidir? Twenty, kullanıcılarına maksimum esneklik sağlamak için tasarlandı. İş süreçlerinizi katı, önceden oluşturulmuş özelliklere uyarlamak yerine, iş akışları, benzersiz iş kullanım durumlarınızı en iyi destekleyen CRM'yi oluşturmanıza olanak tanır. diff --git a/packages/twenty-docs/l/zh/developers/contribute/capabilities/backend-development/server-commands.mdx b/packages/twenty-docs/l/zh/developers/contribute/capabilities/backend-development/server-commands.mdx index 7d22b1a3721..7d9202cc1e3 100644 --- a/packages/twenty-docs/l/zh/developers/contribute/capabilities/backend-development/server-commands.mdx +++ b/packages/twenty-docs/l/zh/developers/contribute/capabilities/backend-development/server-commands.mdx @@ -1,5 +1,6 @@ --- title: 后端命令 +icon: terminal --- ## 实用命令 @@ -43,6 +44,13 @@ npx nx run twenty-server:database:reset ``` ### 迁移 + +#### 适用于 Core/Metadata 模式中的对象 (TypeORM) + +```bash +npx nx run twenty-server:database:migrate:generate +``` + ## 技术栈 Twenty 主要使用 NestJS 作为后端。 diff --git a/packages/twenty-docs/l/zh/developers/contribute/capabilities/bug-and-requests.mdx b/packages/twenty-docs/l/zh/developers/contribute/capabilities/bug-and-requests.mdx index a602dc3bd15..eb1b88c1f70 100644 --- a/packages/twenty-docs/l/zh/developers/contribute/capabilities/bug-and-requests.mdx +++ b/packages/twenty-docs/l/zh/developers/contribute/capabilities/bug-and-requests.mdx @@ -1,5 +1,6 @@ --- title: 错误、功能请求与 Pull Request +icon: bug info: 报告问题、提出功能请求并贡献代码 --- diff --git a/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/best-practices-front.mdx b/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/best-practices-front.mdx index 447e2d2b38a..f9118f92b37 100644 --- a/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/best-practices-front.mdx +++ b/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/best-practices-front.mdx @@ -1,5 +1,6 @@ --- title: 最佳实践 +icon: star --- 本文档概述了在前端工作时应遵循的最佳实践。 diff --git a/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx b/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx index 047e8235eb9..c4e9c9ca51d 100644 --- a/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx +++ b/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/folder-architecture-front.mdx @@ -1,5 +1,6 @@ --- title: 文件夹架构 +icon: folder-tree info: 深入了解我们的文件夹架构 --- diff --git a/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/frontend-commands.mdx b/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/frontend-commands.mdx index d642d5d880e..06f32f5743d 100644 --- a/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/frontend-commands.mdx +++ b/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/frontend-commands.mdx @@ -1,5 +1,6 @@ --- title: 前端命令 +icon: terminal --- ## 实用命令 diff --git a/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/style-guide.mdx b/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/style-guide.mdx index c5d8406aa0f..d551d52d781 100644 --- a/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/style-guide.mdx +++ b/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/style-guide.mdx @@ -1,5 +1,6 @@ --- title: 样式指南 +icon: paintbrush --- 本文档包含编写代码时需要遵循的规则。 diff --git a/packages/twenty-docs/l/zh/developers/contribute/capabilities/local-setup.mdx b/packages/twenty-docs/l/zh/developers/contribute/capabilities/local-setup.mdx index 86b58b6afcc..353b94a7bce 100644 --- a/packages/twenty-docs/l/zh/developers/contribute/capabilities/local-setup.mdx +++ b/packages/twenty-docs/l/zh/developers/contribute/capabilities/local-setup.mdx @@ -1,5 +1,6 @@ --- title: 本地设置 +icon: laptop-code description: 本指南适用于希望在本地运行 Twenty 的贡献者或好奇的开发人员。 --- diff --git a/packages/twenty-docs/l/zh/developers/contribute/commands.mdx b/packages/twenty-docs/l/zh/developers/contribute/commands.mdx new file mode 100644 index 00000000000..266e2716a8b --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/contribute/commands.mdx @@ -0,0 +1,77 @@ +--- +title: Commands +icon: terminal +description: Useful commands for developing Twenty. +--- + +Commands can be run from the repository root using `npx nx`. Use `npx nx run {project}:{command}` for explicit targeting. + +## Starting the App + +```bash +npx nx start twenty-front # Frontend dev server (http://localhost:3001) +npx nx start twenty-server # Backend server (http://localhost:3000) +npx nx run twenty-server:worker # Background worker +``` + +## Database + +```bash +npx nx database:reset twenty-server # Reset and seed database +npx nx run twenty-server:database:migrate:prod # Run migrations +npx nx run twenty-server:database:migrate:generate --name --type # Generate a migration +``` + +## Linting + +```bash +npx nx lint:diff-with-main twenty-front # Lint changed files (fastest) +npx nx lint:diff-with-main twenty-server +npx nx lint twenty-front --configuration=fix # Auto-fix +``` + +## Type Checking + +```bash +npx nx typecheck twenty-front +npx nx typecheck twenty-server +``` + +## 测试 + +```bash +# Frontend +npx nx test twenty-front # Jest unit tests +npx nx storybook:build twenty-front # Build Storybook +npx nx storybook:test twenty-front # Storybook tests + +# Backend +npx nx run twenty-server:test:unit # Unit tests +npx nx run twenty-server:test:integration # Integration tests +npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset + +# Single file (fastest) +npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs +``` + +## GraphQL + +```bash +npx nx run twenty-front:graphql:generate # Regenerate types +npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema +``` + +## 翻译 + +```bash +npx nx run twenty-front:lingui:extract # Extract strings +npx nx run twenty-front:lingui:compile # Compile translations +``` + +## Build + +```bash +npx nx build twenty-shared # Must be built first +npx nx build twenty-front +npx nx build twenty-server +``` diff --git a/packages/twenty-docs/l/zh/developers/contribute/style-guide.mdx b/packages/twenty-docs/l/zh/developers/contribute/style-guide.mdx new file mode 100644 index 00000000000..880f839cb28 --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/contribute/style-guide.mdx @@ -0,0 +1,176 @@ +--- +title: 样式指南 +icon: paintbrush +description: Code conventions and best practices for contributing to Twenty. +--- + +## React + +### Functional components only + +Always use TSX functional components with named exports. + +```tsx +// ❌ Bad +const MyComponent = () => { + return
Hello World
; +}; +export default MyComponent; + +// ✅ Good +export function MyComponent() { + return
Hello World
; +}; +``` + +### 属性 + +Create a type named `{ComponentName}Props`. Use destructuring. Don't use `React.FC`. + +```tsx +type MyComponentProps = { + name: string; +}; + +export const MyComponent = ({ name }: MyComponentProps) =>
Hello {name}
; +``` + +### No single-variable prop spreading + +```tsx +// ❌ Bad +const MyComponent = (props: MyComponentProps) => ; + +// ✅ Good +const MyComponent = ({ prop1, prop2 }: MyComponentProps) => ; +``` + +## 状态管理 + +### Jotai atoms for global state + +```tsx +import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; +import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; + +export const myAtomState = createAtomState({ + key: 'myAtomState', + defaultValue: 'default value', +}); +``` + +* Prefer atoms over prop drilling +* Don't use `useRef` for state — use `useState` or atoms +* Use atom families and selectors for lists + +### Avoid unnecessary re-renders + +* Extract `useEffect` and data fetching into sibling sidecar components +* Prefer event handlers (`handleClick`, `handleChange`) over `useEffect` +* Don't use `React.memo()` — fix the root cause instead +* Limit `useCallback` / `useMemo` usage + +```tsx +// ❌ Bad — useEffect in the same component causes re-renders +export const Page = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return
{data}
; +}; + +// ✅ Good — extract into sibling +export const PageData = () => { + const [data, setData] = useAtomState(dataState); + const [dep] = useAtomState(depState); + useEffect(() => { setData(dep); }, [dep]); + return <>; +}; +export const Page = () => { + const [data] = useAtomState(dataState); + return
{data}
; +}; +``` + +## TypeScript + +* **`type` over `interface`** — more flexible, easier to compose +* **String literals over enums** — except for GraphQL codegen enums and internal library APIs +* **No `any`** — strict TypeScript enforced +* **No type imports** — use regular imports (enforced by Oxlint `typescript/consistent-type-imports`) +* **Use [Zod](https://github.com/colinhacks/zod)** for runtime validation of untyped objects + +## JavaScript + +```tsx +// Use nullish-coalescing (??) instead of || +const value = process.env.MY_VALUE ?? 'default'; + +// Use optional chaining +onClick?.(); +``` + +## 命名 + +* **Variables**: camelCase, descriptive (`email` not `value`, `fieldMetadata` not `fm`) +* **Constants**: SCREAMING_SNAKE_CASE +* **Types/Classes**: PascalCase +* **Files/directories**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`) +* **Event handlers**: `handleClick` (not `onClick` for the handler function) +* **Component props**: prefix with component name (`ButtonProps`) +* **Styled components**: prefix with `Styled` (`StyledTitle`) + +## 样式 + +Use [Linaria](https://github.com/callstack/linaria) styled components. Use theme values — avoid hardcoded `px`, `rem`, or colors. + +```tsx +// ❌ Bad +const StyledButton = styled.button` + color: #333333; + font-size: 1rem; + margin-left: 4px; +`; + +// ✅ Good +const StyledButton = styled.button` + color: ${({ theme }) => theme.font.color.primary}; + font-size: ${({ theme }) => theme.font.size.md}; + margin-left: ${({ theme }) => theme.spacing(1)}; +`; +``` + +## 导入 + +Use aliases instead of relative paths: + +```tsx +// ❌ Bad +import { Foo } from '../../../../../testing/decorators/Foo'; + +// ✅ Good +import { Foo } from '~/testing/decorators/Foo'; +import { Bar } from '@/modules/bar/components/Bar'; +``` + +## Folder Structure + +``` +front +└── modules/ # Feature modules +│ └── module1/ +│ ├── components/ +│ ├── constants/ +│ ├── contexts/ +│ ├── graphql/ (fragments, queries, mutations) +│ ├── hooks/ +│ ├── states/ (atoms, selectors) +│ ├── types/ +│ └── utils/ +└── pages/ # Route-level components +└── ui/ # Reusable UI components (display, input, feedback, ...) +``` + +* Modules can import from other modules, but `ui/` should stay dependency-free +* Use `internal/` subfolders for module-private code +* Components under 300 lines, services under 500 lines diff --git a/packages/twenty-docs/l/zh/developers/extend/api.mdx b/packages/twenty-docs/l/zh/developers/extend/api.mdx index 3ab68043924..167dcb48cfe 100644 --- a/packages/twenty-docs/l/zh/developers/extend/api.mdx +++ b/packages/twenty-docs/l/zh/developers/extend/api.mdx @@ -1,147 +1,55 @@ --- title: 接口 -description: 使用 REST 或 GraphQL 以编程方式查询和修改您的客户关系管理数据。 +icon: plug +description: REST and GraphQL APIs generated from your workspace schema. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -Twenty 的设计对开发者友好,提供适配您自定义数据模型的强大 API。 我们提供四种不同的 API 类型来满足不同的集成需求。 +## Schema-per-tenant APIs -## 开发者优先的方法 +There is no static API reference for Twenty. Each workspace has its own schema — when you add a custom object (say `Invoice`), it immediately gets REST and GraphQL endpoints identical to built-in objects like `Company` or `Person`. The API is generated from the schema, so endpoints use your object and field names directly — no opaque IDs. -Twenty 会针对您的数据模型生成专用 API: +Your workspace-specific API documentation is available under **Settings → API & Webhooks** after creating an API key. It includes an interactive playground where you can execute real calls against your data. -* **无需长 ID**:直接在端点中使用对象和字段名称 -* **标准与自定义对象平等对待**:您的自定义对象将享有与内置对象相同的 API 支持 -* **专用端点**:每个对象和字段都有自己的 API 端点 -* **自定义文档**:专门为您的工作区的数据模型生成 +## Two APIs - -创建 API 密钥后,可在 **设置 → API & Webhooks** 中查看您的个性化 API 文档。 由于 Twenty 会生成与您的自定义数据模型相匹配的 API,因此文档对您的工作区是唯一的。 - +**Core API** — `/rest/` and `/graphql/` -## 两种 API 类型 +CRUD on records: People, Companies, Opportunities, your custom objects. Query, filter, traverse relations. -### 核心 API +**Metadata API** — `/rest/metadata/` and `/metadata/` -访问路径:`/rest/`或`/graphql/`。 +Schema management: create/modify/delete objects, fields, and relations. This is how you programmatically change your data model. -处理您实际的**记录**(数据): +Both are available as REST and GraphQL. GraphQL adds batch upserts and the ability to traverse relations in a single query. Same underlying data either way. -* 创建、读取、更新、删除 People、Companies、Opportunities 等。 -* 查询并筛选数据 -* 管理记录关系 +## Base URLs -### 元数据 API - -访问路径:`/rest/metadata/`或`/metadata/`。 - -管理您的**工作区和数据模型**: - -* 创建、修改或删除对象和字段 -* 配置工作区设置 -* 定义对象之间的关系 - -## REST 与 GraphQL - -核心 API 和元数据 API 均提供 REST 和 GraphQL 格式: - -| 格式 | 可用操作 | -| ----------- | ------------------------------- | -| **REST** | CRUD、批量操作、Upsert | -| **GraphQL** | 同上 + **批量 Upsert**,在一次调用中进行关系查询 | - -可根据需要选择 — 两种格式访问的是同一份数据。 - -## API 端点 - -| 环境 | 基础 URL | -| ------- | ------------------------- | -| **云端** | `https://api.twenty.com/` | -| **自托管** | `https://{your-domain}/` | +| 环境 | 基础 URL | +| ----------- | ------------------------- | +| Cloud | `https://api.twenty.com/` | +| Self-Hosted | `https://{your-domain}/` | ## 身份验证 -每个 API 请求都需要在请求头中包含 API 密钥: - ``` Authorization: Bearer YOUR_API_KEY ``` -### 创建 API 密钥 - -1. 前往 **设置 → APIs & Webhooks** -2. 点击 **+ 创建密钥** -3. 配置: - * **名称**:密钥的描述性名称 - * **到期日期**:密钥的到期时间 -4. 单击 **保存** -5. **立即复制** — 密钥仅显示一次 +Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access. - -您的 API 密钥可访问敏感数据。 不要与不受信任的服务共享它。 如果遭到泄露,请立即将其禁用并生成一个新的。 - +For OAuth-based access (external apps acting on behalf of users), see [OAuth](/l/zh/developers/extend/oauth). -### 为 API 密钥分配角色 +## Batch operations -为提高安全性,请分配特定角色以限制访问: +Both REST and GraphQL support batching up to 60 records per request — create, update, or delete. GraphQL also supports batch upsert (create-or-update in one call) using plural names like `CreateCompanies`. -1. 进入 **设置 → 角色** -2. 点击要分配的角色 -3. 打开 **分配** 选项卡 -4. 在 **API Keys** 下,点击 **+ Assign to API key** -5. 选择该 API 密钥 +## Rate limits -该密钥将继承该角色的权限。 详见 [权限](/l/zh/user-guide/permissions-access/capabilities/permissions)。 - -### 管理 API 密钥 - -**Regenerate**: 设置 → APIs & Webhooks → 点击密钥 → **Regenerate** - -**Delete**: 设置 → APIs & Webhooks → 点击密钥 → **Delete** - -## API 操作台 - -使用我们内置的操作台,可直接在浏览器中测试您的 API — 同时支持 **REST** 和 **GraphQL**。 - -### 访问操作台 - -1. 前往 **设置 → APIs & Webhooks** -2. 创建 API 密钥(必需) -3. 点击 **REST API** 或 **GraphQL API** 打开操作台 - -### 您将获得 - -* **交互式文档**:针对您的特定数据模型生成 -* **实时测试**:对您的工作区执行真实的 API 调用 -* **架构浏览器**:浏览可用的对象、字段和关系 -* **请求构建器**:使用自动补全构建查询 - -操作台会反映您的自定义对象和字段,因此文档始终与您的工作区保持一致且准确。 - -## 批量操作 - -REST 和 GraphQL 均支持批量操作: - -* **批量大小**:每个请求最多 60 条记录 -* **操作**:创建、更新、删除多条记录 - -**仅 GraphQL 功能:** - -* **批量 Upsert**:在一次调用中创建或更新 -* 使用复数对象名称(例如,用 `CreateCompanies` 而不是 `CreateCompany`) - -## 速率限制 - -为确保平台稳定性,API 请求将受到限流: - -| 限制 | 值 | -| -------- | ----------- | -| **请求** | 每分钟 100 次调用 | -| **批量大小** | 每次调用 60 条记录 | - - -使用批量操作以最大化吞吐量 — 在一次 API 调用中处理最多 60 条记录,而不是发起单独的请求。 - +| 限制 | 值 | +| ---------- | -------------- | +| Requests | 100 per minute | +| Batch size | 每次调用 60 条记录 | diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/building.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/building.mdx index 67a4957c674..5eb9140646f 100644 --- a/packages/twenty-docs/l/zh/developers/extend/apps/building.mdx +++ b/packages/twenty-docs/l/zh/developers/extend/apps/building.mdx @@ -1,1715 +1,104 @@ --- -title: 构建应用 -description: 使用 Twenty SDK 定义对象、逻辑函数、前端组件等。 +title: 架构 +description: How Twenty apps work — sandboxing, lifecycle, and the building blocks. +icon: sitemap --- - - 应用目前处于 Alpha 阶段。 该功能可用,但仍在演进中。 - +Twenty apps are TypeScript packages that extend your workspace with custom objects, logic, UI components, and AI capabilities. They run on the Twenty platform with full sandboxing and permission controls. -`twenty-sdk` 包提供类型化的构建块,用于创建你的应用。 本页涵盖 SDK 中可用的所有实体类型和 API 客户端。 +## How apps work -## DefineEntity 函数 +An app is a collection of **entities** declared using `defineEntity()` functions from the `twenty-sdk` package. The SDK detects these declarations via AST analysis at build time and produces a **manifest** — a complete description of what your app adds to a workspace. -SDK 提供用于定义你的应用实体的函数。 你必须使用 `export default defineEntity({...})`,这样 SDK 才能检测到你的实体。 这些函数会在构建时校验你的配置,并提供 IDE 自动补全和类型安全。 - - - **文件组织由你决定。** - 实体检测基于 AST——无论文件位于何处,SDK 都能找到 `export default defineEntity(...)` 的调用。 按类型对文件分组(例如 `logic-functions/`、`roles/`)只是代码组织的一种约定,并非必需。 - - - - - -角色封装了对你的工作空间对象与操作的权限。 - -```ts restricted-company-role.ts -import { - defineRole, - PermissionFlag, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk'; - -export default defineRole({ - universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', - label: 'My new role', - description: 'A role that can be used in your workspace', - canReadAllObjectRecords: false, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - canReadObjectRecords: true, - canUpdateObjectRecords: true, - canSoftDeleteObjectRecords: false, - canDestroyObjectRecords: false, - }, - ], - fieldPermissions: [ - { - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, - fieldUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, - canReadFieldValue: false, - canUpdateFieldValue: false, - }, - ], - permissionFlags: [PermissionFlag.APPLICATIONS], -}); ``` - - - - -每个应用必须且只能有一个 `defineApplication` 调用,用于描述: - -* **应用的身份**:标识符、显示名称和描述。 -* **权限**:其函数和前端组件所使用的角色。 -* **(可选)变量**:以环境变量形式提供给函数的键值对。 -* **(可选)安装前/安装后函数**:在安装之前或之后运行的逻辑函数。 - -```ts src/application-config.ts -import { defineApplication } from 'twenty-sdk'; -import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; - -export default defineApplication({ - universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', - displayName: 'My Twenty App', - description: 'My first Twenty app', - icon: 'IconWorld', - applicationVariables: { - DEFAULT_RECIPIENT_NAME: { - universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', - description: 'Default recipient name for postcards', - value: 'Jane Doe', - isSecret: false, - }, - }, - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -}); -``` - -备注: -* `universalIdentifier` 字段是你拥有的确定性 ID。 只需生成一次,并在多次同步过程中保持稳定不变。 -* `applicationVariables` 会变成你的函数和前端组件可用的环境变量(例如,`DEFAULT_RECIPIENT_NAME` 可作为 `process.env.DEFAULT_RECIPIENT_NAME` 使用)。 -* `defaultRoleUniversalIdentifier` 必须引用使用 `defineRole()` 定义的角色(见上文)。 -* 在构建清单时会自动检测安装前/安装后函数——无需在 `defineApplication()` 中引用它们。 - -#### 应用市场元数据 - -如果你计划[发布你的应用](/l/zh/developers/extend/apps/publishing),这些可选字段将控制你的应用在应用市场中的展示: - -| 字段 | 描述 | -| ------------------ | -------------------------------------------------------------- | -| `作者` | 作者或公司名称 | -| `类别` | 用于应用市场筛选的应用类别 | -| `logoUrl` | 应用徽标的路径(例如 `public/logo.png`) | -| `screenshots` | 截图路径数组(例如 `public/screenshot-1.png`) | -| `aboutDescription` | 用于“关于”选项卡的更长的 Markdown 描述。 如果省略,市场将使用该软件包在 npm 上的 `README.md`。 | -| `websiteUrl` | 你的网站链接 | -| `termsUrl` | 服务条款链接 | -| `emailSupport` | 支持电子邮件地址 | -| `issueReportUrl` | 问题跟踪器链接 | - -#### 角色和权限 - -`application-config.ts` 中的 `defaultRoleUniversalIdentifier` 字段指定你的应用的逻辑函数和前端组件所使用的默认角色。 详见上文的 `defineRole`。 - -* 作为 `TWENTY_APP_ACCESS_TOKEN` 注入的运行时令牌来源于该角色。 -* 类型化客户端将受限于该角色授予的权限。 -* 遵循最小权限原则:创建一个仅包含你的函数所需权限的专用角色。 - -##### 默认函数角色 - -当你使用脚手架创建新应用时,CLI 会创建一个默认角色文件: - -```ts src/roles/default-role.ts -import { defineRole, PermissionFlag } from 'twenty-sdk'; - -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - 'b648f87b-1d26-4961-b974-0908fd991061'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: 'Default function role', - description: 'Default role for function Twenty client', - canReadAllObjectRecords: true, - canUpdateAllObjectRecords: false, - canSoftDeleteAllObjectRecords: false, - canDestroyAllObjectRecords: false, - canUpdateAllSettings: false, - canBeAssignedToAgents: false, - canBeAssignedToUsers: false, - canBeAssignedToApiKeys: false, - objectPermissions: [], - fieldPermissions: [], - permissionFlags: [], -}); -``` - -该角色的 `universalIdentifier` 会在 `application-config.ts` 中被引用为 `defaultRoleUniversalIdentifier`: - -* **\*.role.ts** 定义该角色可以执行的操作。 -* **application-config.ts** 指向该角色,使你的函数继承其权限。 - -备注: -* 从脚手架生成的角色开始,然后按照最小权限原则逐步收紧权限。 -* 将 `objectPermissions` 和 `fieldPermissions` 替换为你的函数所需的对象/字段。 -* `permissionFlags` 控制对平台级能力的访问。 尽量保持最小化。 -* 查看一个可运行示例:[`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts)。 - - - - -自定义对象同时描述工作空间中记录的架构与行为。 使用 `defineObject()` 以内置校验定义对象: - -```ts postCard.object.ts -import { defineObject, FieldType } from 'twenty-sdk'; - -enum PostCardStatus { - DRAFT = 'DRAFT', - SENT = 'SENT', - DELIVERED = 'DELIVERED', - RETURNED = 'RETURNED', -} - -export default defineObject({ - universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', - nameSingular: 'postCard', - namePlural: 'postCards', - labelSingular: 'Post Card', - labelPlural: 'Post Cards', - description: 'A post card object', - icon: 'IconMail', - fields: [ - { - universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', - name: 'content', - type: FieldType.TEXT, - label: 'Content', - description: "Postcard's content", - icon: 'IconAbc', - }, - { - universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', - name: 'recipientName', - type: FieldType.FULL_NAME, - label: 'Recipient name', - icon: 'IconUser', - }, - { - universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', - name: 'recipientAddress', - type: FieldType.ADDRESS, - label: 'Recipient address', - icon: 'IconHome', - }, - { - universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconSend', - defaultValue: `'${PostCardStatus.DRAFT}'`, - options: [ - { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, - { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, - { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, - { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, - ], - }, - { - universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', - name: 'deliveredAt', - type: FieldType.DATE_TIME, - label: 'Delivered at', - icon: 'IconCheck', - isNullable: true, - defaultValue: null, - }, - ], -}); -``` - -关键点: - -* 使用 `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` 来指定它们要扩展的对象: - -```ts src/fields/company-loyalty-tier.field.ts -import { defineField, FieldType } from 'twenty-sdk'; - -export default defineField({ - universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', - objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object - name: 'loyaltyTier', - type: FieldType.SELECT, - label: 'Loyalty Tier', - icon: 'IconStar', - options: [ - { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, - { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, - { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, - ], -}); -``` - -关键点: -* `objectUniversalIdentifier` 用于标识目标对象。 对于标准对象,请使用从 `twenty-sdk` 导出的 `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`。 -* 在 `defineObject()` 中以内联方式定义字段时,你不需要 `objectUniversalIdentifier`——它会从父对象继承。 -* `defineField()` 是为非通过 `defineObject()` 创建的对象添加字段的唯一方式。 - - - - -关系用于将对象彼此连接。 在 Twenty 中,关系始终是双向的——你需要定义两侧,每一侧都引用另一侧。 - -关系有两种类型: - -| 关系类型 | 描述 | 是否有外键? | -| ------------- | ------------------- | ------------------- | -| `MANY_TO_ONE` | 该对象的多条记录指向目标对象的一条记录 | 是(`joinColumnName`) | -| `ONE_TO_MANY` | 该对象的一条记录拥有目标对象的多条记录 | 否(反向侧) | - -#### 关系如何工作 - -每个关系都需要两个相互引用的字段: - -1. **MANY_TO_ONE** 侧——位于持有外键的对象上 -2. **ONE_TO_MANY** 侧——位于拥有集合的对象上 - -两个字段都使用 `FieldType.RELATION`,并通过 `relationTargetFieldMetadataUniversalIdentifier` 相互交叉引用。 - -#### 示例:Post Card 拥有多个收件人 - -假设一个 `PostCard` 可以发送到多个 `PostCardRecipient` 记录。 每个收件人只隶属于一张 Post Card。 - -**步骤 1:在 PostCard 上定义 ONE_TO_MANY 侧**(“一”侧): - -```ts src/fields/post-card-recipients-on-post-card.field.ts -import { defineField, FieldType, RelationType } from 'twenty-sdk'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; -// Import from the other side -import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; - -export default defineField({ - universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCardRecipients', - label: 'Post Card Recipients', - icon: 'IconUsers', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); -``` - -**步骤 2:在 PostCardRecipient 上定义 MANY_TO_ONE 侧**(“多”侧——持有外键): - -```ts src/fields/post-card-on-post-card-recipient.field.ts -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk'; -import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; -import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; - -// Export so the other side can reference it -export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; -// Import from the other side -import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; - -export default defineField({ - universalIdentifier: POST_CARD_FIELD_ID, - objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - icon: 'IconMail', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, -}); +your-app/ +├── src/ +│ ├── application-config.ts ← defineApplication (required, one per app) +│ ├── roles/ ← defineRole +│ ├── objects/ ← defineObject +│ ├── fields/ ← defineField +│ ├── logic-functions/ ← defineLogicFunction +│ ├── front-components/ ← defineFrontComponent +│ ├── skills/ ← defineSkill +│ ├── agents/ ← defineAgent +│ ├── views/ ← defineView +│ ├── navigation-menu-items/ ← defineNavigationMenuItem +│ └── page-layouts/ ← definePageLayout +├── public/ ← Static assets (images, icons) +└── package.json ``` -\*\*循环导入:\*\*两个关系字段相互引用彼此的 `universalIdentifier`。 为避免循环导入问题,请在各自文件中将字段 ID 作为具名常量导出,并在另一个文件中导入它们。 构建系统会在编译时解析这些引用。 + **File organization is up to you.** Entity detection is AST-based — the SDK finds `export default defineEntity(...)` calls regardless of where the file lives. The folder structure above is a convention, not a requirement. -#### 与标准对象建立关系 +## Entity types -要与内置的 Twenty 对象(Person、Company 等)建立关系,请使用 `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: +| 实体 | 目的 | 文档 | +| ------------------------ | ----------------------------------------- | ------------------------------------------------------------ | +| **Application** | App identity, permissions, variables | [Data Model](/l/zh/developers/extend/apps/data-model) | +| **Role** | Permission sets for objects and fields | [Data Model](/l/zh/developers/extend/apps/data-model) | +| **对象** | Custom data tables with fields | [Data Model](/l/zh/developers/extend/apps/data-model) | +| **字段** | Extend existing objects, define relations | [Data Model](/l/zh/developers/extend/apps/data-model) | +| **Logic Function** | Server-side TypeScript with triggers | [Logic Functions](/l/zh/developers/extend/apps/logic-functions) | +| **Front Component** | Sandboxed React UI in Twenty's page | [Front Components](/l/zh/developers/extend/apps/front-components) | +| **Skill** | Reusable AI agent instructions | [Skills & Agents](/l/zh/developers/extend/apps/skills-and-agents) | +| **Agent** | AI assistants with custom prompts | [Skills & Agents](/l/zh/developers/extend/apps/skills-and-agents) | +| **View** | Pre-configured record list views | [Layout](/l/zh/developers/extend/apps/layout) | +| **Navigation Menu Item** | Custom sidebar entries | [Layout](/l/zh/developers/extend/apps/layout) | +| **Page Layout** | Custom record page tabs and widgets | [Layout](/l/zh/developers/extend/apps/layout) | -```ts src/fields/person-on-self-hosting-user.field.ts -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk'; -import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; +## Sandboxing -export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; -export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; +* **Logic functions** run in isolated Node.js processes on the server. They only access data through the typed API client, scoped to the app's role permissions. +* **Front components** run in Web Workers using Remote DOM — sandboxed from the main page but rendering native DOM elements (not iframes). They communicate with Twenty via a message-passing host API. +* **Permissions** are enforced at the API level. The runtime token (`TWENTY_APP_ACCESS_TOKEN`) is derived from the role defined in `defineApplication()`. -export default defineField({ - universalIdentifier: PERSON_FIELD_ID, - objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'person', - label: 'Person', - description: 'Person matching with the self hosting user', - isNullable: true, - relationTargetObjectMetadataUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, - relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'personId', - }, -}); +## App lifecycle + +``` +┌─────────────────────────────────────────────────────────┐ +│ Development │ +│ npx create-twenty-app → yarn twenty dev (live sync) │ +├─────────────────────────────────────────────────────────┤ +│ Build & Deploy │ +│ yarn twenty build → yarn twenty deploy │ +├─────────────────────────────────────────────────────────┤ +│ Install flow │ +│ upload → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +├─────────────────────────────────────────────────────────┤ +│ Publish │ +│ npm publish → appears in Twenty marketplace │ +└─────────────────────────────────────────────────────────┘ ``` -#### 关系字段属性 - -| 属性 | 必填 | 描述 | -| ------------------------------------------------- | ---------------- | -------------------------------------------------------------- | -| `类型` | 是 | 必须为 `FieldType.RELATION` | -| `relationTargetObjectMetadataUniversalIdentifier` | 是 | 目标对象的 `universalIdentifier` | -| `relationTargetFieldMetadataUniversalIdentifier` | 是 | 目标对象上匹配字段的 `universalIdentifier` | -| `universalSettings.relationType` | 是 | `RelationType.MANY_TO_ONE` 或 `RelationType.ONE_TO_MANY` | -| `universalSettings.onDelete` | 仅适用于 MANY_TO_ONE | 当被引用的记录被删除时的处理方式:`CASCADE`、`SET_NULL`、`RESTRICT` 或 `NO_ACTION` | -| `universalSettings.joinColumnName` | 仅适用于 MANY_TO_ONE | 外键的数据库列名(例如,`postCardId`) | - -#### 在 defineObject 中内联关系字段 - -你也可以直接在 `defineObject()` 内定义关系字段。 在这种情况下,省略 `objectUniversalIdentifier`——它会从父对象继承: - -```ts -export default defineObject({ - universalIdentifier: '...', - nameSingular: 'postCardRecipient', - // ... - fields: [ - { - universalIdentifier: POST_CARD_FIELD_ID, - type: FieldType.RELATION, - name: 'postCard', - label: 'Post Card', - relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'postCardId', - }, - }, - // ... other fields - ], -}); -``` - - - -每个函数文件都使用 `defineLogicFunction()` 导出包含处理程序和可选触发器的配置。 - -```ts src/logic-functions/createPostCard.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; -import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; - -const handler = async (params: RoutePayload) => { - const client = new CoreApiClient(); - const name = 'name' in params.queryStringParameters - ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' - : 'Hello world'; - - const result = await client.mutation({ - createPostCard: { - __args: { data: { name } }, - id: true, - name: true, - }, - }); - return result; -}; - -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'create-new-post-card', - timeoutSeconds: 2, - handler, - httpRouteTriggerSettings: { - path: '/post-card/create', - httpMethod: 'GET', - isAuthRequired: false, - }, - /*databaseEventTriggerSettings: { - eventName: 'people.created', - },*/ - /*cronTriggerSettings: { - pattern: '0 0 1 1 *', - },*/ -}); -``` - -可用的触发器类型: -* **httpRoute**:在 **`/s/` 端点**下通过 HTTP 路径和方法公开你的函数: -> 例如 `path: '/post-card/create'` 可在 `https://your-twenty-server.com/s/post-card/create` 调用 -* **cron**:使用 CRON 表达式按计划运行你的函数。 -* **databaseEvent**:在工作空间对象生命周期事件上运行。 当事件操作为 `updated` 时,可以在 `updatedFields` 数组中指定要监听的特定字段。 如果未定义或为空,任何更新都会触发该函数。 -> 例如 `person.updated`、`*.created`、`company.*` - - -你也可以使用 CLI 手动执行函数: - -```bash filename="Terminal" -yarn twenty exec -n create-new-post-card -p '{"key": "value"}' -``` - -```bash filename="Terminal" -yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - -你可以通过以下方式查看日志: - -```bash filename="Terminal" -yarn twenty logs -``` - - -#### 路由触发器负载 - -当路由触发器调用你的逻辑函数时,它会接收一个遵循 -[AWS HTTP API v2 格式](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html)的 `RoutePayload` 对象。 -从 `twenty-sdk` 导入 `RoutePayload` 类型: - -```ts -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; - -const handler = async (event: RoutePayload) => { - const { headers, queryStringParameters, pathParameters, body } = event; - const { method, path } = event.requestContext.http; - - return { message: 'Success' }; -}; -``` - -`RoutePayload` 类型具有以下结构: - - | 属性 | 类型 | 描述 | 示例 | - | ---------------------------- | ------------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------- | - | `headers` | `Record` | HTTP 请求头(仅限 `forwardedRequestHeaders` 中列出的那些) | 见下文 | - | `queryStringParameters` | `Record` | 查询字符串参数(多个值以逗号连接) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | - | `pathParameters` | `Record` | 从路由模式中提取的路径参数 | `/users/:id`,`/users/123` -> `{ id: '123' }` | - | `body` | `object \| null` | 已解析的请求体(JSON) | `{ id: 1 }` -> `{ id: 1 }` | - | `isBase64Encoded` | `boolean` | 请求体是否为 base64 编码 | | - | `requestContext.http.method` | `string` | HTTP 方法(GET、POST、PUT、PATCH、DELETE) | | - | `requestContext.http.path` | `string` | 原始请求路径 | | - - -#### forwardedRequestHeaders - -出于安全原因,默认**不会**将传入请求的 HTTP 请求头传递给你的逻辑函数。 -如需访问特定请求头,请在 `forwardedRequestHeaders` 数组中显式列出: - -```ts -export default defineLogicFunction({ - universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', - name: 'webhook-handler', - handler, - httpRouteTriggerSettings: { - path: '/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], - }, -}); -``` - -在你的处理程序中,可以这样访问被转发的请求头: - -```ts -const handler = async (event: RoutePayload) => { - const signature = event.headers['x-webhook-signature']; - const contentType = event.headers['content-type']; - - // Validate webhook signature... - return { received: true }; -}; -``` - - -请求头名称会被规范化为小写。 请使用小写键访问它们(例如,`event.headers['content-type']`)。 - - -#### 将函数作为工具公开 - -逻辑函数可以作为供 AI 智能体和工作流使用的**工具**对外提供。 当函数被标记为工具时,Twenty 的 AI 功能即可发现它,并可在工作流自动化中使用。 - -要将逻辑函数标记为工具,请设置 `isTool: true`: - -```ts src/logic-functions/enrich-company.logic-function.ts -import { defineLogicFunction } from 'twenty-sdk'; -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const handler = async (params: { companyName: string; domain?: string }) => { - const client = new CoreApiClient(); - - const result = await client.mutation({ - createTask: { - __args: { - data: { - title: `Enrich data for ${params.companyName}`, - body: `Domain: ${params.domain ?? 'unknown'}`, - }, - }, - id: true, - }, - }); - - return { taskId: result.createTask.id }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', - name: 'enrich-company', - description: 'Enrich a company record with external data', - timeoutSeconds: 10, - handler, - isTool: true, -}); -``` - -关键点: - -* 你可以将 `isTool` 与触发器结合使用——一个函数既可以作为工具(由 AI 代理调用),也可以同时由事件触发。 -* **`toolInputSchema`**(可选):描述函数可接受参数的 JSON Schema 对象。 该模式会通过对源代码的静态分析自动推导,但你也可以显式设置: - -```ts -export default defineLogicFunction({ - ..., - toolInputSchema: { - type: 'object', - properties: { - companyName: { - type: 'string', - description: 'The name of the company to enrich', - }, - domain: { - type: 'string', - description: 'The company website domain (optional)', - }, - }, - required: ['companyName'], - }, -}); -``` - - -**写一个好的 `description`。** AI 代理会依赖该函数的 `description` 字段来决定何时使用该工具。 明确说明该工具的作用以及应在何时调用。 - - - - - -安装前函数是在你的应用安装到工作区之前自动运行的逻辑函数。 这对于执行验证任务、先决条件检查,或在主安装开始前准备工作区状态很有用。 - -```ts src/logic-functions/pre-install.ts -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); -}; - -export default definePreInstallLogicFunction({ - universalIdentifier: 'e0604b9e-e946-456b-886d-3f27d9a6b324', - name: 'pre-install', - description: 'Runs before installation to prepare the application.', - timeoutSeconds: 300, - handler, -}); -``` - -你也可以随时使用 CLI 手动执行安装前函数: - -```bash filename="Terminal" -yarn twenty exec --preInstall -``` - -关键点: -* 安装前函数使用 `definePreInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。 -* 处理器会接收一个 `InstallLogicFunctionPayload`,其包含 `{ previousVersion: string }` —— 即之前安装的应用版本(全新安装则为空字符串)。 -* 每个应用仅允许一个安装前函数。 如果检测到多个,清单构建将报错。 -* 在构建期间,函数的 `universalIdentifier` 会自动设置为应用清单上的 `preInstallLogicFunctionUniversalIdentifier` —— 你无需在 `defineApplication()` 中引用它。 -* 默认超时时间设置为 300 秒(5 分钟),以便支持更长的准备任务。 - - - - -安装后函数是在你的应用安装到工作区后自动运行的逻辑函数。 这对于一次性设置任务很有用,例如填充默认数据、创建初始记录或配置工作区设置。 - -```ts src/logic-functions/post-install.ts -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; - -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Post install logic function executed successfully!', payload.previousVersion); -}; - -export default definePostInstallLogicFunction({ - universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', - name: 'post-install', - description: 'Runs after installation to set up the application.', - timeoutSeconds: 300, - handler, -}); -``` - -你也可以随时使用 CLI 手动执行安装后函数: - -```bash filename="Terminal" -yarn twenty exec --postInstall -``` - -关键点: -* 安装后函数使用 `definePostInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。 -* 处理器会接收一个 `InstallLogicFunctionPayload`,其包含 `{ previousVersion: string }` —— 即之前安装的应用版本(全新安装则为空字符串)。 -* 每个应用仅允许一个安装后函数。 如果检测到多个,清单构建将报错。 -* 在构建期间,函数的 `universalIdentifier` 会自动设置为应用清单上的 `postInstallLogicFunctionUniversalIdentifier` —— 你无需在 `defineApplication()` 中引用它。 -* 默认超时时间设置为 300 秒(5 分钟),以便支持更长的设置任务,如数据填充。 - - - - -前端组件是直接在 Twenty 的 UI 内渲染的 React 组件。 它们在使用 Remote DOM 的**隔离 Web Worker**中运行——你的代码在沙盒中执行,但会原生渲染到页面中,而非在 iframe 里。 - -#### 基础示例 - -最快体验前端组件运行方式的方法是将其注册为一个**命令**。 添加一个 `command` 字段并设置 `isPinned: true`,即可让它以快速操作按钮的形式出现在页面右上角——无需页面布局: - -```tsx src/front-components/hello-world.tsx -import { defineFrontComponent } from 'twenty-sdk'; - -const HelloWorld = () => { - return ( -
-

Hello from my app!

-

This component renders inside Twenty.

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', - name: 'hello-world', - description: 'A simple front component', - component: HelloWorld, - command: { - universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', - shortLabel: 'Hello', - label: 'Hello World', - icon: 'IconBolt', - isPinned: true, - availabilityType: 'GLOBAL', - }, -}); -``` - -使用 `yarn twenty dev` 同步后,快速操作会出现在页面右上角: - -
- 右上角的快速操作按钮 -
- -点击它以内联方式渲染该组件。 - -{/* TODO: add screenshot of the rendered front component */} - -#### 配置字段 - -| 字段 | 必填 | 描述 | -| --------------------- | -- | ----------------------------------------------------------------------------------- | -| `universalIdentifier` | 是 | 该组件的稳定唯一 ID | -| `component` | 是 | 一个 React 组件函数 | -| `name` | 否 | 显示名称 | -| `描述` | 否 | 组件的功能描述 | -| `isHeadless` | 否 | Set to `true` if the component has no visible UI (see below) | -| `命令` | 否 | Register the component as a command (see [command options](#command-options) below) | - -#### Placing a front component on a page - -Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a **page layout**. See the [definePageLayout](#definepagelayout) section for details. - -#### Headless components (`isHeadless: true`) - -Headless components render no visible UI but still run React logic. This is useful for **effect components** — components that perform side effects when mounted, such as syncing data, starting a timer, listening to events, or triggering a notification. - -```tsx src/front-components/sync-tracker.tsx -import { defineFrontComponent, useRecordId, enqueueSnackbar } from 'twenty-sdk'; -import { useEffect } from 'react'; - -const SyncTracker = () => { - const recordId = useRecordId(); - - useEffect(() => { - enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); - }, [recordId]); - - return null; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'sync-tracker', - description: 'Tracks record views silently', - isHeadless: true, - component: SyncTracker, -}); -``` - -Because the component returns `null`, Twenty skips rendering a container for it — no empty space appears in the layout. The component still has access to all hooks and the host communication API. - -#### Accessing runtime context - -Inside your component, use SDK hooks to access the current user, record, and component instance: - -```tsx src/front-components/record-info.tsx -import { - defineFrontComponent, - useUserId, - useRecordId, - useFrontComponentId, -} from 'twenty-sdk'; - -const RecordInfo = () => { - const userId = useUserId(); - const recordId = useRecordId(); - const componentId = useFrontComponentId(); - - return ( -
-

User: {userId}

-

Record: {recordId ?? 'No record context'}

-

Component: {componentId}

-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', - name: 'record-info', - component: RecordInfo, -}); -``` - -Available hooks: - -| 钩子 | Returns | 描述 | -| --------------------------------------------- | ------------------ | ---------------------------------------------------------- | -| `useUserId()` | `string` or `null` | The current user's ID | -| `useRecordId()` | `string` or `null` | The current record's ID (when placed on a record page) | -| `useFrontComponentId()` | `string` | This component instance's ID | -| `useFrontComponentExecutionContext(selector)` | 因情况而异 | Access the full execution context with a selector function | - -#### Host communication API - -Front components can trigger navigation, modals, and notifications using functions from `twenty-sdk`: - -| 函数 | 描述 | -| ----------------------------------------------- | ----------------------------- | -| `navigate(to, params?, queryParams?, options?)` | Navigate to a page in the app | -| `openSidePanelPage(params)` | Open a side panel | -| `closeSidePanel()` | 关闭侧边栏 | -| `openCommandConfirmationModal(params)` | Show a confirmation dialog | -| `enqueueSnackbar(params)` | Show a toast notification | -| `unmountFrontComponent()` | Unmount the component | -| `updateProgress(progress)` | Update a progress indicator | - -#### Command options - -Adding a `command` field to `defineFrontComponent` registers the component in the command menu (Cmd+K). If `isPinned` is `true`, it also appears as a quick-action button in the top-right corner of the page. - -| 字段 | 必填 | 描述 | -| --------------------------------------- | -- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `universalIdentifier` | 是 | Stable unique ID for the command | -| `标签` | 是 | Full label shown in the command menu (Cmd+K) | -| `shortLabel` | 否 | Shorter label displayed on the pinned quick-action button | -| `图标` | 否 | Icon name displayed next to the label (e.g. `'IconBolt'`, `'IconSend'`) | -| `isPinned` | 否 | When `true`, shows the command as a quick-action button in the top-right corner of the page | -| `availabilityType` | 否 | Controls where the command appears: `'GLOBAL'` (always available), `'RECORD_SELECTION'` (only when records are selected), or `'FALLBACK'` (shown when no other commands match) | -| `availabilityObjectUniversalIdentifier` | 否 | Restrict the command to pages of a specific object type (e.g. only on Company records) | -| `conditionalAvailabilityExpression` | 否 | A boolean expression to dynamically control whether the command is visible (see below) | - -#### Conditional availability expressions - -The `conditionalAvailabilityExpression` field lets you control when a command is visible based on the current page context. Import typed variables and operators from `twenty-sdk` to build expressions: - -```tsx -import { - defineFrontComponent, - pageType, - numberOfSelectedRecords, - objectPermissions, - everyEquals, - isDefined, -} from 'twenty-sdk'; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'bulk-action', - component: BulkAction, - command: { - universalIdentifier: '...', - label: 'Bulk Update', - availabilityType: 'RECORD_SELECTION', - conditionalAvailabilityExpression: everyEquals( - objectPermissions, - 'canUpdateObjectRecords', - true, - ), - }, -}); -``` - -**Context variables** — these represent the current state of the page: - -| 变量 | 类型 | 描述 | -| ------------------------------ | --------- | ---------------------------------------------------------------- | -| `pageType` | `string` | Current page type (e.g. `'RecordIndexPage'`, `'RecordShowPage'`) | -| `isInSidePanel` | `boolean` | Whether the component is rendered in a side panel | -| `numberOfSelectedRecords` | `数字` | Number of currently selected records | -| `isSelectAll` | `boolean` | Whether "select all" is active | -| `selectedRecords` | `array` | The selected record objects | -| `favoriteRecordIds` | `array` | IDs of favorited records | -| `objectPermissions` | `对象` | Permissions for the current object type | -| `targetObjectReadPermissions` | `对象` | Read permissions for the target object | -| `targetObjectWritePermissions` | `对象` | Write permissions for the target object | -| `featureFlags` | `对象` | Active feature flags | -| `objectMetadataItem` | `对象` | Metadata of the current object type | -| `hasAnySoftDeleteFilterOnView` | `boolean` | Whether the current view has a soft-delete filter | - -**Operators** — combine variables into boolean expressions: - -| Operator | 描述 | -| ----------------------------------- | ----------------------------------------------------------------- | -| `isDefined(value)` | `true` if the value is not null/undefined | -| `isNonEmptyString(value)` | `true` if the value is a non-empty string | -| `includes(array, value)` | `true` if the array contains the value | -| `includesEvery(array, prop, value)` | `true` if every item's property includes the value | -| `every(array, prop)` | `true` if the property is truthy on every item | -| `everyDefined(array, prop)` | `true` if the property is defined on every item | -| `everyEquals(array, prop, value)` | `true` if the property equals the value on every item | -| `some(array, prop)` | `true` if the property is truthy on at least one item | -| `someDefined(array, prop)` | `true` if the property is defined on at least one item | -| `someEquals(array, prop, value)` | `true` if the property equals the value on at least one item | -| `someNonEmptyString(array, prop)` | `true` if the property is a non-empty string on at least one item | -| `none(array, prop)` | `true` if the property is falsy on every item | -| `noneDefined(array, prop)` | `true` if the property is undefined on every item | -| `noneEquals(array, prop, value)` | `true` if the property does not equal the value on any item | - -#### Public assets - -Front components can access files from the app's `public/` directory using `getPublicAssetUrl`: - -```tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; - -const Logo = () => Logo; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'logo', - component: Logo, -}); -``` - -See the [public assets section](#accessing-public-assets-with-getpublicasseturl) for details. - -#### 样式 - -Front components support multiple styling approaches. You can use: - -* **Inline styles** — `style={{ color: 'red' }}` -* **Twenty UI components** — import from `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar, and more) -* **Emotion** — CSS-in-JS with `@emotion/react` -* **Styled-components** — `styled.div` patterns -* **Tailwind CSS** — utility classes -* **Any CSS-in-JS library** compatible with React - -```tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { Button, Tag, Status } from 'twenty-sdk/ui'; - -const StyledWidget = () => { - return ( -
-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', - name: 'styled-widget', - component: StyledWidget, -}); -``` - -
- - - -技能定义了可复用的指令和能力,AI 智能体可在你的工作区中使用。 使用 `defineSkill()` 定义带内置校验的技能: - -```ts src/skills/example-skill.ts -import { defineSkill } from 'twenty-sdk'; - -export default defineSkill({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'sales-outreach', - label: 'Sales Outreach', - description: 'Guides the AI agent through a structured sales outreach process', - icon: 'IconBrain', - content: `You are a sales outreach assistant. When reaching out to a prospect: -1. Research the company and recent news -2. Identify the prospect's role and likely pain points -3. Draft a personalized message referencing specific details -4. Keep the tone professional but conversational`, -}); -``` - -关键点: -* `name` 是该技能的唯一标识字符串(推荐使用 kebab-case)。 -* `label` 是在 UI 中显示的人类可读名称。 -* `content` 包含技能指令——这是 AI 智能体使用的文本。 -* `icon`(可选)设置在 UI 中显示的图标。 -* `description`(可选)提供有关技能用途的更多上下文。 - - - - -Agents are AI assistants that live inside your workspace. Use `defineAgent()` to create agents with a custom system prompt: - -```ts src/agents/example-agent.ts -import { defineAgent } from 'twenty-sdk'; - -export default defineAgent({ - universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', - name: 'sales-assistant', - label: 'Sales Assistant', - description: 'Helps the sales team draft outreach emails and research prospects', - icon: 'IconRobot', - prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', -}); -``` - -关键点: -* `name` is the unique identifier string for the agent (kebab-case recommended). -* `label` is the display name shown in the UI. -* `prompt` is the system prompt that defines the agent's behavior. -* `description` (optional) provides context about what the agent does. -* `icon`(可选)设置在 UI 中显示的图标。 -* `modelId` (optional) overrides the default AI model used by the agent. - - - - -Views are saved configurations for how records of an object are displayed — including which fields are visible, their order, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app: - -```ts src/views/example-view.ts -import { defineView, ViewKey } from 'twenty-sdk'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; - -export default defineView({ - universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - name: 'All example items', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - icon: 'IconList', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', - fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 200, - }, - ], -}); -``` - -关键点: -* `objectUniversalIdentifier` specifies which object this view applies to. -* `key` determines the view type (e.g., `ViewKey.INDEX` for the main list view). -* `fields` controls which columns appear and their order. Each field references a `fieldMetadataUniversalIdentifier`. -* You can also define `filters`, `filterGroups`, `groups`, and `fieldGroups` for more advanced configurations. -* `position` controls the ordering when multiple views exist for the same object. - - - - -Navigation menu items add custom entries to the workspace sidebar. Use `defineNavigationMenuItem()` to link to views, external URLs, or objects: - -```ts src/navigation-menu-items/example-navigation-menu-item.ts -import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk'; -import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; - -export default defineNavigationMenuItem({ - universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', - name: 'example-navigation-menu-item', - icon: 'IconList', - color: 'blue', - position: 0, - type: NavigationMenuItemType.VIEW, - viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, -}); -``` - -关键点: -* `type` determines what the menu item links to: `NavigationMenuItemType.VIEW` for a saved view, or `NavigationMenuItemType.LINK` for an external URL. -* For view links, set `viewUniversalIdentifier`. For external links, set `link`. -* `position` controls the ordering in the sidebar. -* `icon` and `color` (optional) customize the appearance. - - - - -Page layouts let you customize how a record detail page looks — which tabs appear, what widgets are inside each tab, and how they are arranged. Use `definePageLayout()` to ship custom layouts with your app: - -```ts src/page-layouts/example-record-page-layout.ts -import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk'; -import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; -import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; - -export default definePageLayout({ - universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', - name: 'Example Record Page', - type: 'RECORD_PAGE', - objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, - tabs: [ - { - universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', - title: 'Hello World', - position: 50, - icon: 'IconWorld', - layoutMode: PageLayoutTabLayoutMode.CANVAS, - widgets: [ - { - universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', - title: 'Hello World', - type: 'FRONT_COMPONENT', - configuration: { - configurationType: 'FRONT_COMPONENT', - frontComponentUniversalIdentifier: - HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, - }, - }, - ], - }, - ], -}); -``` - -关键点: -* `type` is typically `'RECORD_PAGE'` to customize the detail view of a specific object. -* `objectUniversalIdentifier` specifies which object this layout applies to. -* Each `tab` defines a section of the page with a `title`, `position`, and `layoutMode` (`CANVAS` for free-form layout). -* Each `widget` inside a tab can render a front component, a relation list, or other built-in widget types. -* `position` on tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones. - - -
- -## Public assets (`public/` folder) - -The `public/` folder at the root of your app holds static files — images, icons, fonts, or any other assets your app needs at runtime. These files are automatically included in builds, synced during dev mode, and uploaded to the server. - -Files placed in `public/` are: - -* **Publicly accessible** — once synced to the server, assets are served at a public URL. No authentication is needed to access them. -* **Available in front components** — use asset URLs to display images, icons, or any media inside your React components. -* **Available in logic functions** — reference asset URLs in emails, API responses, or any server-side logic. -* **Used for marketplace metadata** — the `logoUrl` and `screenshots` fields in `defineApplication()` reference files from this folder (e.g., `public/logo.png`). These are displayed in the marketplace when your app is published. -* **Auto-synced in dev mode** — when you add, update, or delete a file in `public/`, it is synced to the server automatically. No restart needed. -* **Included in builds** — `yarn twenty build` bundles all public assets into the distribution output. - -### Accessing public assets with `getPublicAssetUrl` - -Use the `getPublicAssetUrl` helper from `twenty-sdk` to get the full URL of a file in your `public/` directory. It works in both **logic functions** and **front components**. - -**In a logic function:** - -```ts src/logic-functions/send-invoice.ts -import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk'; - -const handler = async (): Promise => { - const logoUrl = getPublicAssetUrl('logo.png'); - const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); - - // Fetch the file content (no auth required — public endpoint) - const response = await fetch(invoiceUrl); - const buffer = await response.arrayBuffer(); - - return { logoUrl, size: buffer.byteLength }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'a1b2c3d4-...', - name: 'send-invoice', - description: 'Sends an invoice with the app logo', - timeoutSeconds: 10, - handler, -}); -``` - -**In a front component:** - -```tsx src/front-components/company-card.tsx -import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk'; - -export default defineFrontComponent(() => { - const logoUrl = getPublicAssetUrl('logo.png'); - - return App logo; -}); -``` - -The `path` argument is relative to your app's `public/` folder. Both `getPublicAssetUrl('logo.png')` and `getPublicAssetUrl('public/logo.png')` resolve to the same URL — the `public/` prefix is stripped automatically if present. - -## Using npm packages - -You can install and use any npm package in your app. Both logic functions and front components are bundled with [esbuild](https://esbuild.github.io/), which inlines all dependencies into the output — no `node_modules` are needed at runtime. - -### Installing a package - -```bash filename="Terminal" -yarn add axios -``` - -Then import it in your code: - -```ts src/logic-functions/fetch-data.ts -import { defineLogicFunction } from 'twenty-sdk'; -import axios from 'axios'; - -const handler = async (): Promise => { - const { data } = await axios.get('https://api.example.com/data'); - - return { data }; -}; - -export default defineLogicFunction({ - universalIdentifier: '...', - name: 'fetch-data', - description: 'Fetches data from an external API', - timeoutSeconds: 10, - handler, -}); -``` - -The same works for front components: - -```tsx src/front-components/chart.tsx -import { defineFrontComponent } from 'twenty-sdk'; -import { format } from 'date-fns'; - -const DateWidget = () => { - return

Today is {format(new Date(), 'MMMM do, yyyy')}

; -}; - -export default defineFrontComponent({ - universalIdentifier: '...', - name: 'date-widget', - component: DateWidget, -}); -``` - -### How bundling works - -The build step (`yarn twenty dev` or `yarn twenty build`) uses esbuild to produce a single self-contained file per logic function and per front component. All imported packages are inlined into the bundle. - -**Logic functions** run in a Node.js environment. Node built-in modules (`fs`, `path`, `crypto`, `http`, etc.) are available and do not need to be installed. - -**Front components** run in a Web Worker. Node built-in modules are **not** available — only browser APIs and npm packages that work in a browser environment. - -Both environments have `twenty-client-sdk/core` and `twenty-client-sdk/metadata` available as pre-provided modules — these are not bundled but resolved at runtime by the server. - -## Scaffolding entities with `yarn twenty add` - -Instead of creating entity files by hand, you can use the interactive scaffolder: - -```bash filename="Terminal" -yarn twenty add -``` - -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: - -```bash filename="Terminal" -yarn twenty add object -yarn twenty add logicFunction -yarn twenty add frontComponent -``` - -### Available entity types - -| 实体类型 | 命令 | Generated file | -| -------------------- | ------------------------------------ | ------------------------------------- | -| 对象 | `yarn twenty add object` | `src/objects/.ts` | -| 字段 | `yarn twenty add field` | `src/fields/.ts` | -| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/.ts` | -| Front component | `yarn twenty add frontComponent` | `src/front-components/.tsx` | -| 角色 | `yarn twenty add role` | `src/roles/.ts` | -| 技能 | `yarn twenty add skill` | `src/skills/.ts` | -| 代理 | `yarn twenty add agent` | `src/agents/.ts` | -| 视图 | `yarn twenty add view` | `src/views/.ts` | -| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/.ts` | -| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/.ts` | - -### What the scaffolder generates - -Each entity type has its own template. For example, `yarn twenty add object` asks for: - -1. **Name (singular)** — e.g., `invoice` -2. **Name (plural)** — e.g., `invoices` -3. **Label (singular)** — auto-populated from the name (e.g., `Invoice`) -4. **Label (plural)** — auto-populated (e.g., `Invoices`) -5. **Create a view and navigation item?** — if you answer yes, the scaffolder also generates a matching view and sidebar link for the new object. - -Other entity types have simpler prompts — most only ask for a name. - -The `field` entity type is more detailed: it asks for the field name, label, type (from a list of all available field types like `TEXT`, `NUMBER`, `SELECT`, `RELATION`, etc.), and the target object's `universalIdentifier`. - -### Custom output path - -Use the `--path` flag to place the generated file in a custom location: - -```bash filename="Terminal" -yarn twenty add logicFunction --path src/custom-folder -``` - -## Typed API clients (twenty-client-sdk) - -The `twenty-client-sdk` package provides two typed GraphQL clients for interacting with the Twenty API from your logic functions and front components. - -| 客户端 | 导入 | 端点 | 是否生成? | -| ------------------- | ---------------------------- | ------------------------ | --------- | -| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql`——工作区数据(记录、对象) | 是,在开发/构建时 | -| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata`——工作区配置、文件上传 | 否,已预构建提供 | - - - - -`CoreApiClient` 是用于查询和变更工作区数据的主要客户端。 It is **generated from your workspace schema** during `yarn twenty dev` or `yarn twenty build`, so it is fully typed to match your objects and fields. - -```ts -import { CoreApiClient } from 'twenty-client-sdk/core'; - -const client = new CoreApiClient(); - -// Query records -const { companies } = await client.query({ - companies: { - edges: { - node: { - id: true, - name: true, - domainName: { - primaryLinkLabel: true, - primaryLinkUrl: true, - }, - }, - }, - }, -}); - -// Create a record -const { createCompany } = await client.mutation({ - createCompany: { - __args: { - data: { - name: 'Acme Corp', - }, - }, - id: true, - name: true, - }, -}); -``` - -该客户端使用选择集语法:传入 `true` 以包含某字段,使用 `__args` 传递参数,并通过嵌套对象表示关系。 你将基于工作区架构获得完整的自动补全和类型检查。 - - -**CoreApiClient is generated at dev/build time.** If you use it without running `yarn twenty dev` or `yarn twenty build` first, it throws an error. The generation happens automatically — the CLI introspects your workspace's GraphQL schema and generates a typed client using `@genql/cli`. - - -#### 使用 CoreSchema 进行类型标注 - -`CoreSchema` provides TypeScript types matching your workspace objects — useful for typing component state or function parameters: - -```ts -import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; -import { useState } from 'react'; - -const [company, setCompany] = useState< - Pick | undefined ->(undefined); - -const client = new CoreApiClient(); -const result = await client.query({ - company: { - __args: { filter: { position: { eq: 1 } } }, - id: true, - name: true, - }, -}); -setCompany(result.company); -``` - - - - -`MetadataApiClient` 随 SDK 一并提供,已预构建(无需生成)。 It queries the `/metadata` endpoint for workspace configuration, applications, and file uploads. - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const metadataClient = new MetadataApiClient(); - -// List first 10 objects in the workspace -const { objects } = await metadataClient.query({ - objects: { - edges: { - node: { - id: true, - nameSingular: true, - namePlural: true, - labelSingular: true, - isCustom: true, - }, - }, - __args: { - filter: {}, - paging: { first: 10 }, - }, - }, -}); -``` - -#### 上传文件 - -`MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields: - -```ts -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import * as fs from 'fs'; - -const metadataClient = new MetadataApiClient(); - -const fileBuffer = fs.readFileSync('./invoice.pdf'); - -const uploadedFile = await metadataClient.uploadFile( - fileBuffer, // file contents as a Buffer - 'invoice.pdf', // filename - 'application/pdf', // MIME type - '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier -); - -console.log(uploadedFile); -// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } -``` - -| 参数 | 类型 | 描述 | -| ---------------------------------- | -------- | ------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | 原始文件内容 | -| `filename` | `string` | 文件名称(用于存储和显示) | -| `contentType` | `string` | MIME type (defaults to `application/octet-stream` if omitted) | -| `fieldMetadataUniversalIdentifier` | `string` | 你的对象上文件类型字段的 `universalIdentifier` | - -关键点: -* 使用字段的 `universalIdentifier`(而不是其工作区特定的 ID),因此你的上传代码可在安装了你的应用的任何工作区中运行。 -* 返回的 `url` 是一个签名 URL,你可以用它来访问已上传的文件。 - - - - - - 当你的代码在 Twenty 上运行(逻辑函数或前端组件)时,平台会以环境变量的形式注入凭据: - - * `TWENTY_API_URL`——Twenty API 的基础 URL - * `TWENTY_APP_ACCESS_TOKEN` — Short-lived key scoped to your application's default function role - - 你无需将这些值传递给客户端——它们会自动从 `process.env` 读取。 API 密钥的权限由你的 `application-config.ts` 中 `defaultRoleUniversalIdentifier` 引用的角色决定。 - - -## Testing your app - -The SDK provides programmatic APIs that let you build, deploy, install, and uninstall your app from test code. Combined with [Vitest](https://vitest.dev/) and the typed API clients, you can write integration tests that verify your app works end-to-end against a real Twenty server. - -### 设置 - -The scaffolded app already includes Vitest. If you set it up manually, install the dependencies: - -```bash filename="Terminal" -yarn add -D vitest vite-tsconfig-paths -``` - -Create a `vitest.config.ts` at the root of your app: - -```ts vitest.config.ts -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - include: ['src/**/*.integration-test.ts'], - setupFiles: ['src/__tests__/setup-test.ts'], - env: { - TWENTY_API_URL: 'http://localhost:2020', - TWENTY_API_KEY: 'your-api-key', - }, - }, -}); -``` - -Create a setup file that verifies the server is reachable before tests run: - -```ts src/__tests__/setup-test.ts -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { beforeAll } from 'vitest'; - -const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; -const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); - -beforeAll(async () => { - // Verify the server is running - const response = await fetch(`${TWENTY_API_URL}/healthz`); - - if (!response.ok) { - throw new Error( - `Twenty server is not reachable at ${TWENTY_API_URL}. ` + - 'Start the server before running integration tests.', - ); - } - - // Write a temporary config for the SDK - fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); - - fs.writeFileSync( - path.join(TEST_CONFIG_DIR, 'config.json'), - JSON.stringify({ - remotes: { - local: { - apiUrl: process.env.TWENTY_API_URL, - apiKey: process.env.TWENTY_API_KEY, - }, - }, - defaultRemote: 'local', - }, null, 2), - ); -}); -``` - -### Programmatic SDK APIs - -The `twenty-sdk/cli` subpath exports functions you can call directly from test code: - -| 函数 | 描述 | -| -------------- | ------------------------------------------- | -| `appBuild` | Build the app and optionally pack a tarball | -| `appDeploy` | Upload a tarball to the server | -| `appInstall` | Install the app on the active workspace | -| `appUninstall` | Uninstall the app from the active workspace | - -Each function returns a result object with `success: boolean` and either `data` or `error`. - -### Writing an integration test - -Here is a full example that builds, deploys, and installs the app, then verifies it appears in the workspace: - -```ts src/__tests__/app-install.integration-test.ts -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; -import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -const APP_PATH = process.cwd(); - -describe('App installation', () => { - beforeAll(async () => { - const buildResult = await appBuild({ - appPath: APP_PATH, - tarball: true, - onProgress: (message: string) => console.log(`[build] ${message}`), - }); - - if (!buildResult.success) { - throw new Error(`Build failed: ${buildResult.error?.message}`); - } - - const deployResult = await appDeploy({ - tarballPath: buildResult.data.tarballPath!, - onProgress: (message: string) => console.log(`[deploy] ${message}`), - }); - - if (!deployResult.success) { - throw new Error(`Deploy failed: ${deployResult.error?.message}`); - } - - const installResult = await appInstall({ appPath: APP_PATH }); - - if (!installResult.success) { - throw new Error(`Install failed: ${installResult.error?.message}`); - } - }); - - afterAll(async () => { - await appUninstall({ appPath: APP_PATH }); - }); - - it('should find the installed app in the workspace', async () => { - const metadataClient = new MetadataApiClient(); - - const result = await metadataClient.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - const installedApp = result.findManyApplications.find( - (app: { universalIdentifier: string }) => - app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, - ); - - expect(installedApp).toBeDefined(); - }); -}); -``` - -### Running tests - -Make sure your local Twenty server is running, then: - -```bash filename="Terminal" -yarn test -``` - -Or in watch mode during development: - -```bash filename="Terminal" -yarn test:watch -``` - -### Type checking - -You can also run type checking on your app without running tests: - -```bash filename="Terminal" -yarn twenty typecheck -``` - -This runs `tsc --noEmit` and reports any type errors. - -## CLI 参考 - -Beyond `dev`, `build`, `add`, and `typecheck`, the CLI provides commands for executing functions, viewing logs, and managing app installations. - -### Executing functions (`yarn twenty exec`) - -Run a logic function manually without triggering it via HTTP, cron, or database event: - -```bash filename="Terminal" -# Execute by function name -yarn twenty exec -n create-new-post-card - -# Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf - -# Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' - -# Execute pre-install or post-install functions -yarn twenty exec --preInstall -yarn twenty exec --postInstall -``` - -### Viewing function logs (`yarn twenty logs`) - -Stream execution logs for your app's logic functions: - -```bash filename="Terminal" -# Stream all function logs -yarn twenty logs - -# Filter by function name -yarn twenty logs -n create-new-post-card - -# Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -``` - - -This is different from `yarn twenty server logs`, which shows the Docker container logs. `yarn twenty logs` shows your app's function execution logs from the Twenty server. - - -### Uninstalling an app (`yarn twenty uninstall`) - -Remove your app from the active workspace: - -```bash filename="Terminal" -yarn twenty uninstall - -# Skip the confirmation prompt -yarn twenty uninstall --yes -``` +* **`yarn twenty dev`** — watches your source files and live-syncs changes to a connected Twenty server. The typed API client is regenerated automatically when the schema changes. +* **`yarn twenty build`** — compiles TypeScript, bundles logic functions and front components with esbuild, and produces a manifest. +* **Pre/post-install hooks** — optional logic functions that run during installation. See [Logic Functions](/l/zh/developers/extend/apps/logic-functions) for details. + +## Next steps + + + + Define objects, fields, roles, and relations. + + + Server-side functions with HTTP, cron, and event triggers. + + + Sandboxed React components inside Twenty's UI. + + + Views, navigation items, and record page layouts. + + + AI skills and agents with custom prompts. + + + CLI commands, testing, assets, remotes, and CI. + + + Deploy to a server or publish to the marketplace. + + diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/cli-and-testing.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/cli-and-testing.mdx new file mode 100644 index 00000000000..edb8006ba42 --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/apps/cli-and-testing.mdx @@ -0,0 +1,434 @@ +--- +title: CLI & Testing +description: CLI commands, testing setup, public assets, npm packages, remotes, and CI configuration. +icon: terminal +--- + +## Public assets (`public/` folder) + +The `public/` folder at the root of your app holds static files — images, icons, fonts, or any other assets your app needs at runtime. These files are automatically included in builds, synced during dev mode, and uploaded to the server. + +Files placed in `public/` are: + +* **Publicly accessible** — once synced to the server, assets are served at a public URL. No authentication is needed to access them. +* **Available in front components** — use asset URLs to display images, icons, or any media inside your React components. +* **Available in logic functions** — reference asset URLs in emails, API responses, or any server-side logic. +* **Used for marketplace metadata** — the `logoUrl` and `screenshots` fields in `defineApplication()` reference files from this folder (e.g., `public/logo.png`). These are displayed in the marketplace when your app is published. +* **Auto-synced in dev mode** — when you add, update, or delete a file in `public/`, it is synced to the server automatically. No restart needed. +* **Included in builds** — `yarn twenty build` bundles all public assets into the distribution output. + +### Accessing public assets with `getPublicAssetUrl` + +Use the `getPublicAssetUrl` helper from `twenty-sdk` to get the full URL of a file in your `public/` directory. It works in both **logic functions** and **front components**. + +**In a logic function:** + +```ts src/logic-functions/send-invoice.ts +import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk/define'; + +const handler = async (): Promise => { + const logoUrl = getPublicAssetUrl('logo.png'); + const invoiceUrl = getPublicAssetUrl('templates/invoice.png'); + + // Fetch the file content (no auth required — public endpoint) + const response = await fetch(invoiceUrl); + const buffer = await response.arrayBuffer(); + + return { logoUrl, size: buffer.byteLength }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'a1b2c3d4-...', + name: 'send-invoice', + description: 'Sends an invoice with the app logo', + timeoutSeconds: 10, + handler, +}); +``` + +**In a front component:** + +```tsx src/front-components/company-card.tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +export default defineFrontComponent(() => { + const logoUrl = getPublicAssetUrl('logo.png'); + + return App logo; +}); +``` + +The `path` argument is relative to your app's `public/` folder. Both `getPublicAssetUrl('logo.png')` and `getPublicAssetUrl('public/logo.png')` resolve to the same URL — the `public/` prefix is stripped automatically if present. + +## Using npm packages + +You can install and use any npm package in your app. Both logic functions and front components are bundled with [esbuild](https://esbuild.github.io/), which inlines all dependencies into the output — no `node_modules` are needed at runtime. + +### Installing a package + +```bash filename="Terminal" +yarn add axios +``` + +Then import it in your code: + +```ts src/logic-functions/fetch-data.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import axios from 'axios'; + +const handler = async (): Promise => { + const { data } = await axios.get('https://api.example.com/data'); + + return { data }; +}; + +export default defineLogicFunction({ + universalIdentifier: '...', + name: 'fetch-data', + description: 'Fetches data from an external API', + timeoutSeconds: 10, + handler, +}); +``` + +The same works for front components: + +```tsx src/front-components/chart.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { format } from 'date-fns'; + +const DateWidget = () => { + return

Today is {format(new Date(), 'MMMM do, yyyy')}

; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'date-widget', + component: DateWidget, +}); +``` + +### How bundling works + +构建步骤使用 esbuild 为每个逻辑函数和每个前端组件生成一个自包含文件。 All imported packages are inlined into the bundle. + +**Logic functions** run in a Node.js environment. Node built-in modules (`fs`, `path`, `crypto`, `http`, etc.) are available and do not need to be installed. + +**Front components** run in a Web Worker. Node built-in modules are **not** available — only browser APIs and npm packages that work in a browser environment. + +Both environments have `twenty-client-sdk/core` and `twenty-client-sdk/metadata` available as pre-provided modules — these are not bundled but resolved at runtime by the server. + +## 测试你的应用 + +该 SDK 提供可编程的 API,使你可以在测试代码中构建、部署、安装和卸载你的应用。 结合 [Vitest](https://vitest.dev/) 和类型化 API 客户端,你可以编写集成测试,在真实的 Twenty 服务器上验证你的应用端到端运行是否正常。 + +### 设置 + +脚手架生成的应用已包含 Vitest。 如果你手动进行设置,请安装这些依赖: + +```bash filename="Terminal" +yarn add -D vitest vite-tsconfig-paths +``` + +在应用根目录下创建一个 `vitest.config.ts`: + +```ts vitest.config.ts +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + include: ['src/**/*.integration-test.ts'], + setupFiles: ['src/__tests__/setup-test.ts'], + env: { + TWENTY_API_URL: 'http://localhost:2020', + TWENTY_API_KEY: 'your-api-key', + }, + }, +}); +``` + +创建一个设置文件,在测试运行前验证服务器可达: + +```ts src/__tests__/setup-test.ts +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { beforeAll } from 'vitest'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test'); + +beforeAll(async () => { + // Verify the server is running + const response = await fetch(`${TWENTY_API_URL}/healthz`); + + if (!response.ok) { + throw new Error( + `Twenty server is not reachable at ${TWENTY_API_URL}. ` + + 'Start the server before running integration tests.', + ); + } + + // Write a temporary config for the SDK + fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true }); + + fs.writeFileSync( + path.join(TEST_CONFIG_DIR, 'config.json'), + JSON.stringify({ + remotes: { + local: { + apiUrl: process.env.TWENTY_API_URL, + apiKey: process.env.TWENTY_API_KEY, + }, + }, + defaultRemote: 'local', + }, null, 2), + ); +}); +``` + +### 可编程的 SDK API + +子路径 `twenty-sdk/cli` 导出了可直接在测试代码中调用的函数: + +| 函数 | 描述 | +| -------------- | ------------------ | +| `appBuild` | 构建应用,并可选地打包为 tar 包 | +| `appDeploy` | 将 tar 包上传到服务器 | +| `appInstall` | 在活动工作区安装该应用 | +| `appUninstall` | 从活动工作区卸载该应用 | + +每个函数都会返回一个结果对象,包含 `success: boolean`,以及 `data` 或 `error` 之一。 + +### 编写集成测试 + +下面是一个完整示例:构建、部署并安装该应用,然后验证它出现在工作区中: + +```ts src/__tests__/app-install.integration-test.ts +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config'; +import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const APP_PATH = process.cwd(); + +describe('App installation', () => { + beforeAll(async () => { + const buildResult = await appBuild({ + appPath: APP_PATH, + tarball: true, + onProgress: (message: string) => console.log(`[build] ${message}`), + }); + + if (!buildResult.success) { + throw new Error(`Build failed: ${buildResult.error?.message}`); + } + + const deployResult = await appDeploy({ + tarballPath: buildResult.data.tarballPath!, + onProgress: (message: string) => console.log(`[deploy] ${message}`), + }); + + if (!deployResult.success) { + throw new Error(`Deploy failed: ${deployResult.error?.message}`); + } + + const installResult = await appInstall({ appPath: APP_PATH }); + + if (!installResult.success) { + throw new Error(`Install failed: ${installResult.error?.message}`); + } + }); + + afterAll(async () => { + await appUninstall({ appPath: APP_PATH }); + }); + + it('should find the installed app in the workspace', async () => { + const metadataClient = new MetadataApiClient(); + + const result = await metadataClient.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const installedApp = result.findManyApplications.find( + (app: { universalIdentifier: string }) => + app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(installedApp).toBeDefined(); + }); +}); +``` + +### 运行测试 + +确保你的本地 Twenty 服务器正在运行,然后: + +```bash filename="Terminal" +yarn test +``` + +或者在开发期间使用监听模式: + +```bash filename="Terminal" +yarn test:watch +``` + +### 类型检查 + +你也可以在不运行测试的情况下对应用进行类型检查: + +```bash filename="Terminal" +yarn twenty typecheck +``` + +这会运行 `tsc --noEmit` 并报告所有类型错误。 + +## CLI 参考 + +除了 `dev`、`build`、`add` 和 `typecheck` 外,CLI 还提供了用于执行函数、查看日志和管理应用安装的命令。 + +### 执行函数(`yarn twenty exec`) + +手动运行逻辑函数,而无需通过 HTTP、定时任务或数据库事件来触发: + +```bash filename="Terminal" +# Execute by function name +yarn twenty exec -n create-new-post-card + +# Execute by universalIdentifier +yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf + +# Pass a JSON payload +yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' + +# Execute the post-install function +yarn twenty exec --postInstall +``` + +### 查看函数日志(`yarn twenty logs`) + +实时流式查看你的应用逻辑函数的执行日志: + +```bash filename="Terminal" +# Stream all function logs +yarn twenty logs + +# Filter by function name +yarn twenty logs -n create-new-post-card + +# Filter by universalIdentifier +yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + + +这与 `yarn twenty server logs` 不同,后者显示的是 Docker 容器日志。 `yarn twenty logs` 会显示来自 Twenty 服务器的应用函数执行日志。 + + +### 卸载应用(`yarn twenty uninstall`) + +将你的应用从活动工作区中移除: + +```bash filename="Terminal" +yarn twenty uninstall + +# Skip the confirmation prompt +yarn twenty uninstall --yes +``` + +## 管理远程 + +“远程”是指你的应用连接到的 Twenty 服务器。 在设置期间,脚手架工具会为你自动创建一个。 你可以随时添加更多远程或在它们之间切换。 + +```bash filename="Terminal" +# Add a new remote (opens a browser for OAuth login) +yarn twenty remote add + +# Connect to a local Twenty server (auto-detects port 2020 or 3000) +yarn twenty remote add --local + +# Add a remote non-interactively (useful for CI) +yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote + +# List all configured remotes +yarn twenty remote list + +# Switch the active remote +yarn twenty remote switch +``` + +你的凭据存储在 `~/.twenty/config.json` 中。 + +## 使用 GitHub Actions 进行 CI + +脚手架工具会在 `.github/workflows/ci.yml` 生成一个开箱即用的 GitHub Actions 工作流。 它会在每次向 `main` 推送以及拉取请求上自动运行你的集成测试。 + +工作流: + +1. 检出你的代码 +2. 使用 `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` 动作启动一个临时的 Twenty 服务器 +3. 使用 `yarn install --immutable` 安装依赖 +4. 运行 `yarn test`,并从该动作的输出中注入 `TWENTY_API_URL` 和 `TWENTY_API_KEY` + +```yaml .github/workflows/ci.yml +name: CI + +on: + push: + branches: + - main + pull_request: {} + +env: + TWENTY_VERSION: latest + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Spawn Twenty instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main + with: + twenty-version: ${{ env.TWENTY_VERSION }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable Corepack + run: corepack enable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --immutable + + - name: Run integration tests + run: yarn test + env: + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} +``` + +你无需配置任何机密——`spawn-twenty-docker-image` 动作会在运行器中直接启动一个临时的 Twenty 服务器,并输出连接详情。 GitHub 会自动提供 `GITHUB_TOKEN` 机密。 + +若要固定为特定的 Twenty 版本而不是 `latest`,请在工作流顶部修改 `TWENTY_VERSION` 环境变量。 diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/data-model.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/data-model.mdx new file mode 100644 index 00000000000..4cfd00167f5 --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/apps/data-model.mdx @@ -0,0 +1,494 @@ +--- +title: 数据模型 +description: Define objects, fields, roles, and application metadata with the Twenty SDK. +icon: database +--- + +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/`)只是代码组织的一种约定,并非必需。 + + + + + +角色封装了对你的工作空间对象与操作的权限。 + +```ts restricted-company-role.ts +import { + defineRole, + PermissionFlag, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export default defineRole({ + universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6', + label: 'My new role', + description: 'A role that can be used in your workspace', + canReadAllObjectRecords: false, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + canReadObjectRecords: true, + canUpdateObjectRecords: true, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + }, + ], + fieldPermissions: [ + { + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + fieldUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier, + canReadFieldValue: false, + canUpdateFieldValue: false, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + + + + +每个应用必须且只能有一个 `defineApplication` 调用,用于描述: + +* **应用的身份**:标识符、显示名称和描述。 +* **权限**:其函数和前端组件所使用的角色。 +* **(可选)变量**:以环境变量形式提供给函数的键值对。 +* **(可选)安装前/安装后函数**:在安装之前或之后运行的逻辑函数。 + +```ts src/application-config.ts +import { defineApplication } from 'twenty-sdk/define'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +备注: +* `universalIdentifier` 字段是你拥有的确定性 ID。 只需生成一次,并在多次同步过程中保持稳定不变。 +* `applicationVariables` 会变成你的函数和前端组件可用的环境变量(例如,`DEFAULT_RECIPIENT_NAME` 可作为 `process.env.DEFAULT_RECIPIENT_NAME` 使用)。 +* `defaultRoleUniversalIdentifier` 必须引用使用 `defineRole()` 定义的角色(见上文)。 +* 在构建清单时会自动检测安装前/安装后函数——无需在 `defineApplication()` 中引用它们。 + +#### 应用市场元数据 + +如果你计划[发布你的应用](/l/zh/developers/extend/apps/publishing),这些可选字段将控制你的应用在应用市场中的展示: + +| 字段 | 描述 | +| ------------------ | -------------------------------------------------------------- | +| `作者` | 作者或公司名称 | +| `类别` | 用于应用市场筛选的应用类别 | +| `logoUrl` | 应用徽标的路径(例如 `public/logo.png`) | +| `screenshots` | 截图路径数组(例如 `public/screenshot-1.png`) | +| `aboutDescription` | 用于“关于”选项卡的更长的 Markdown 描述。 如果省略,市场将使用该软件包在 npm 上的 `README.md`。 | +| `websiteUrl` | 你的网站链接 | +| `termsUrl` | 服务条款链接 | +| `emailSupport` | 支持电子邮件地址 | +| `issueReportUrl` | 问题跟踪器链接 | + +#### 角色和权限 + +`application-config.ts` 中的 `defaultRoleUniversalIdentifier` 字段指定你的应用的逻辑函数和前端组件所使用的默认角色。 详见上文的 `defineRole`。 + +* 作为 `TWENTY_APP_ACCESS_TOKEN` 注入的运行时令牌来源于该角色。 +* 类型化客户端将受限于该角色授予的权限。 +* 遵循最小权限原则:创建一个仅包含你的函数所需权限的专用角色。 + +##### 默认函数角色 + +当你使用脚手架创建新应用时,CLI 会创建一个默认角色文件: + +```ts src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk/define'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + label: 'Default function role', + description: 'Default role for function Twenty client', + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [], + fieldPermissions: [], + permissionFlags: [], +}); +``` + +该角色的 `universalIdentifier` 会在 `application-config.ts` 中被引用为 `defaultRoleUniversalIdentifier`: + +* **\*.role.ts** 定义该角色可以执行的操作。 +* **application-config.ts** 指向该角色,使你的函数继承其权限。 + +备注: +* 从脚手架生成的角色开始,然后按照最小权限原则逐步收紧权限。 +* 将 `objectPermissions` 和 `fieldPermissions` 替换为你的函数所需的对象/字段。 +* `permissionFlags` 控制对平台级能力的访问。 尽量保持最小化。 +* 查看一个可运行示例:[`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts)。 + + + + +自定义对象同时描述工作空间中记录的架构与行为。 使用 `defineObject()` 以内置校验定义对象: + +```ts postCard.object.ts +import { defineObject, FieldType } from 'twenty-sdk/define'; + +enum PostCardStatus { + DRAFT = 'DRAFT', + SENT = 'SENT', + DELIVERED = 'DELIVERED', + RETURNED = 'RETURNED', +} + +export default defineObject({ + universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', + nameSingular: 'postCard', + namePlural: 'postCards', + labelSingular: 'Post Card', + labelPlural: 'Post Cards', + description: 'A post card object', + icon: 'IconMail', + fields: [ + { + universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', + name: 'content', + type: FieldType.TEXT, + label: 'Content', + description: "Postcard's content", + icon: 'IconAbc', + }, + { + universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', + name: 'recipientName', + type: FieldType.FULL_NAME, + label: 'Recipient name', + icon: 'IconUser', + }, + { + universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', + name: 'recipientAddress', + type: FieldType.ADDRESS, + label: 'Recipient address', + icon: 'IconHome', + }, + { + universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', + name: 'status', + type: FieldType.SELECT, + label: 'Status', + icon: 'IconSend', + defaultValue: `'${PostCardStatus.DRAFT}'`, + options: [ + { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, + { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, + { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, + { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, + ], + }, + { + universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', + name: 'deliveredAt', + type: FieldType.DATE_TIME, + label: 'Delivered at', + icon: 'IconCheck', + isNullable: true, + defaultValue: null, + }, + ], +}); +``` + +关键点: + +* 使用 `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` 来指定它们要扩展的对象: + +```ts src/fields/company-loyalty-tier.field.ts +import { defineField, FieldType } from 'twenty-sdk/define'; + +export default defineField({ + universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890', + objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object + name: 'loyaltyTier', + type: FieldType.SELECT, + label: 'Loyalty Tier', + icon: 'IconStar', + options: [ + { value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' }, + { value: 'SILVER', label: 'Silver', position: 1, color: 'gray' }, + { value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' }, + ], +}); +``` + +关键点: +* `objectUniversalIdentifier` 用于标识目标对象。 对于标准对象,请使用从 `twenty-sdk` 导出的 `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`。 +* 在 `defineObject()` 中以内联方式定义字段时,你不需要 `objectUniversalIdentifier`——它会从父对象继承。 +* `defineField()` 是为非通过 `defineObject()` 创建的对象添加字段的唯一方式。 + + + + +关系用于将对象彼此连接。 在 Twenty 中,关系始终是双向的——你需要定义两侧,每一侧都引用另一侧。 + +关系有两种类型: + +| 关系类型 | 描述 | 是否有外键? | +| ------------- | ------------------- | ------------------- | +| `MANY_TO_ONE` | 该对象的多条记录指向目标对象的一条记录 | 是(`joinColumnName`) | +| `ONE_TO_MANY` | 该对象的一条记录拥有目标对象的多条记录 | 否(反向侧) | + +#### 关系如何工作 + +每个关系都需要两个相互引用的字段: + +1. **MANY_TO_ONE** 侧——位于持有外键的对象上 +2. **ONE_TO_MANY** 侧——位于拥有集合的对象上 + +两个字段都使用 `FieldType.RELATION`,并通过 `relationTargetFieldMetadataUniversalIdentifier` 相互交叉引用。 + +#### 示例:Post Card 拥有多个收件人 + +假设一个 `PostCard` 可以发送到多个 `PostCardRecipient` 记录。 每个收件人只隶属于一张 Post Card。 + +**步骤 1:在 PostCard 上定义 ONE_TO_MANY 侧**(“一”侧): + +```ts src/fields/post-card-recipients-on-post-card.field.ts +import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111'; +// Import from the other side +import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field'; + +export default defineField({ + universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCardRecipients', + label: 'Post Card Recipients', + icon: 'IconUsers', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); +``` + +**步骤 2:在 PostCardRecipient 上定义 MANY_TO_ONE 侧**(“多”侧——持有外键): + +```ts src/fields/post-card-on-post-card-recipient.field.ts +import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; +import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object'; +import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object'; + +// Export so the other side can reference it +export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222'; +// Import from the other side +import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field'; + +export default defineField({ + universalIdentifier: POST_CARD_FIELD_ID, + objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + icon: 'IconMail', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, +}); +``` + + +\*\*循环导入:\*\*两个关系字段相互引用彼此的 `universalIdentifier`。 为避免循环导入问题,请在各自文件中将字段 ID 作为具名常量导出,并在另一个文件中导入它们。 构建系统会在编译时解析这些引用。 + + +#### 与标准对象建立关系 + +要与内置的 Twenty 对象(Person、Company 等)建立关系,请使用 `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`: + +```ts src/fields/person-on-self-hosting-user.field.ts +import { + defineField, + FieldType, + RelationType, + OnDeleteAction, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; +import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object'; + +export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333'; +export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444'; + +export default defineField({ + universalIdentifier: PERSON_FIELD_ID, + objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'person', + label: 'Person', + description: 'Person matching with the self hosting user', + isNullable: true, + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'personId', + }, +}); +``` + +#### 关系字段属性 + +| 属性 | 必填 | 描述 | +| ------------------------------------------------- | ---------------- | -------------------------------------------------------------- | +| `类型` | 是 | 必须为 `FieldType.RELATION` | +| `relationTargetObjectMetadataUniversalIdentifier` | 是 | 目标对象的 `universalIdentifier` | +| `relationTargetFieldMetadataUniversalIdentifier` | 是 | 目标对象上匹配字段的 `universalIdentifier` | +| `universalSettings.relationType` | 是 | `RelationType.MANY_TO_ONE` 或 `RelationType.ONE_TO_MANY` | +| `universalSettings.onDelete` | 仅适用于 MANY_TO_ONE | 当被引用的记录被删除时的处理方式:`CASCADE`、`SET_NULL`、`RESTRICT` 或 `NO_ACTION` | +| `universalSettings.joinColumnName` | 仅适用于 MANY_TO_ONE | 外键的数据库列名(例如,`postCardId`) | + +#### 在 defineObject 中内联关系字段 + +你也可以直接在 `defineObject()` 内定义关系字段。 在这种情况下,省略 `objectUniversalIdentifier`——它会从父对象继承: + +```ts +export default defineObject({ + universalIdentifier: '...', + nameSingular: 'postCardRecipient', + // ... + fields: [ + { + universalIdentifier: POST_CARD_FIELD_ID, + type: FieldType.RELATION, + name: 'postCard', + label: 'Post Card', + relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.CASCADE, + joinColumnName: 'postCardId', + }, + }, + // ... other fields + ], +}); +``` + + + +## Scaffolding entities with `yarn twenty add` + +Instead of creating entity files by hand, you can use the interactive scaffolder: + +```bash filename="Terminal" +yarn twenty add +``` + +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: + +```bash filename="Terminal" +yarn twenty add object +yarn twenty add logicFunction +yarn twenty add frontComponent +``` + +### 可用的实体类型 + +| 实体类型 | 命令 | Generated file | +| --------------- | ------------------------------------ | ------------------------------------------------------- | +| 对象 | `yarn twenty add object` | `src/objects/\.ts` | +| 字段 | `yarn twenty add field` | `src/fields/\.ts` | +| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/\.ts` | +| Front component | `yarn twenty add frontComponent` | `src/front-components/\.tsx` | +| 角色 | `yarn twenty add role` | `src/roles/\.ts` | +| 技能 | `yarn twenty add skill` | `src/skills/\.ts` | +| 代理 | `yarn twenty add agent` | `src/agents/\.ts` | +| 视图 | `yarn twenty add view` | `src/views/\.ts` | +| 导航菜单项 | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\.ts` | +| 页面布局 | `yarn twenty add pageLayout` | `src/page-layouts/\.ts` | + +### 脚手架生成的内容 + +每种实体类型都有其自己的模板。 例如,`yarn twenty add object` 会询问: + +1. **名称(单数)**——例如,`invoice` +2. **名称(复数)**——例如,`invoices` +3. **标签(单数)**——根据名称自动填充(例如,`Invoice`) +4. **标签(复数)**——自动填充(例如,`Invoices`) +5. **创建视图和导航项?**——如果你选择是,脚手架还会为新对象生成相应的视图和侧边栏链接。 + +其他实体类型的提示更简单——大多只会询问名称。 + +`field` 实体类型更为详细:它会询问字段名称、标签、类型(从所有可用字段类型列表中选择,如 `TEXT`、`NUMBER`、`SELECT`、`RELATION` 等),以及目标对象的 `universalIdentifier`。 + +### 自定义输出路径 + +使用 `--path` 标志将生成的文件放置在自定义位置: + +```bash filename="Terminal" +yarn twenty add logicFunction --path src/custom-folder +``` diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/front-components.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/front-components.mdx new file mode 100644 index 00000000000..a9fb49a2e0e --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/apps/front-components.mdx @@ -0,0 +1,419 @@ +--- +title: 前端组件 +description: Build React components that render inside Twenty's UI with sandboxed isolation. +icon: window-maximize +--- + +前端组件是直接在 Twenty 的 UI 内渲染的 React 组件。 它们在使用 Remote DOM 的**隔离 Web Worker**中运行——你的代码在沙盒中执行,但会原生渲染到页面中,而非在 iframe 里。 + +## 前端组件可用位置 + +在 Twenty 中,前端组件可在两个位置进行渲染: + +* **侧边栏** — 非无头的前端组件会在右侧侧边栏中打开。 当前端组件从命令菜单触发时,这是默认行为。 +* **小部件(仪表盘和记录页面)** — 前端组件可以作为小部件嵌入页面布局中。 在配置仪表盘或记录页面布局时,用户可以添加前端组件小部件。 + +## 基础示例 + +最快体验前端组件运行方式的方法是将其注册为一个**命令**。 添加一个 `command` 字段并设置 `isPinned: true`,即可让它以快速操作按钮的形式出现在页面右上角——无需页面布局: + +```tsx src/front-components/hello-world.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; + +const HelloWorld = () => { + return ( +
+

Hello from my app!

+

This component renders inside Twenty.

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948', + name: 'hello-world', + description: 'A simple front component', + component: HelloWorld, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345', + shortLabel: 'Hello', + label: 'Hello World', + icon: 'IconBolt', + isPinned: true, + availabilityType: 'GLOBAL', + }, +}); +``` + +使用 `yarn twenty dev` 同步后(或单次运行 `yarn twenty dev --once`),快速操作会出现在页面右上角: + +
+ 右上角的快速操作按钮 +
+ +点击它以内联方式渲染该组件。 + +## 配置字段 + +| 字段 | 必填 | 描述 | +| --------------------- | -- | ----------------------------------------------------------------------------------- | +| `universalIdentifier` | 是 | 该组件的稳定唯一 ID | +| `component` | 是 | 一个 React 组件函数 | +| `name` | 否 | 显示名称 | +| `描述` | 否 | 组件的功能描述 | +| `isHeadless` | 否 | Set to `true` if the component has no visible UI (see below) | +| `命令` | 否 | Register the component as a command (see [command options](#command-options) below) | + +## Placing a front component on a page + +Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a **page layout**. See the [definePageLayout](/l/zh/developers/extend/apps/skills-and-agents#definepagelayout) section for details. + +## 无头与非无头 + +前端组件有两种由 `isHeadless` 选项控制的渲染模式: + +**非无头(默认)** — 该组件会渲染可见的 UI。 从命令菜单触发时,它会在侧边栏中打开。 当 `isHeadless` 为 `false` 或被省略时,这是默认行为。 + +**无头 (`isHeadless: true`)** — 该组件会在后台以不可见的方式挂载。 它不会打开侧边栏。 无头组件旨在用于执行逻辑后自行卸载的操作——例如运行异步任务、导航到某个页面或显示确认模态框。 它们与下文介绍的 SDK Command 组件天然契合。 + +```tsx src/front-components/sync-tracker.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component'; +import { useEffect } from 'react'; + +const SyncTracker = () => { + const recordId = useRecordId(); + + useEffect(() => { + enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' }); + }, [recordId]); + + return null; +}; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'sync-tracker', + description: 'Tracks record views silently', + isHeadless: true, + component: SyncTracker, +}); +``` + +Because the component returns `null`, Twenty skips rendering a container for it — no empty space appears in the layout. The component still has access to all hooks and the host communication API. + +## SDK Command 组件 + +`twenty-sdk` 包提供了四个为无头前端组件设计的 Command 辅助组件。 每个组件都会在挂载时执行一个操作,通过显示 snackbar 通知来处理错误,并在完成后自动卸载该前端组件。 + +从 `twenty-sdk/command` 导入它们: + +* **`Command`** — 通过 `execute` 属性运行异步回调。 +* **`CommandLink`** — 导航到某个应用路径。 属性:`to`、`params`、`queryParams`、`options`。 +* **`CommandModal`** — 打开一个确认模态框。 如果用户确认,则执行 `execute` 回调。 属性:`title`、`subtitle`、`execute`、`confirmButtonText`、`confirmButtonAccent`。 +* **`CommandOpenSidePanelPage`** — 打开特定的侧边栏页面。 属性:`page`、`pageTitle`、`pageIcon`。 + +下面是一个完整示例:无头前端组件使用 `Command` 从命令菜单运行一个操作: + +```tsx src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +另一个示例:使用 `CommandModal` 在执行前请求确认: + +```tsx src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +## Accessing runtime context + +Inside your component, use SDK hooks to access the current user, record, and component instance: + +```tsx src/front-components/record-info.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + useUserId, + useRecordId, + useFrontComponentId, +} from 'twenty-sdk/front-component'; + +const RecordInfo = () => { + const userId = useUserId(); + const recordId = useRecordId(); + const componentId = useFrontComponentId(); + + return ( +
+

User: {userId}

+

Record: {recordId ?? 'No record context'}

+

Component: {componentId}

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012', + name: 'record-info', + component: RecordInfo, +}); +``` + +Available hooks: + +| 钩子 | Returns | 描述 | +| --------------------------------------------- | ------------------ | ---------------------------------------------------------- | +| `useUserId()` | `string` or `null` | The current user's ID | +| `useRecordId()` | `string` or `null` | The current record's ID (when placed on a record page) | +| `useFrontComponentId()` | `string` | This component instance's ID | +| `useFrontComponentExecutionContext(selector)` | 因情况而异 | Access the full execution context with a selector function | + +## Host communication API + +Front components can trigger navigation, modals, and notifications using functions from `twenty-sdk`: + +| 函数 | 描述 | +| ----------------------------------------------- | ----------------------------- | +| `navigate(to, params?, queryParams?, options?)` | Navigate to a page in the app | +| `openSidePanelPage(params)` | Open a side panel | +| `closeSidePanel()` | 关闭侧边栏 | +| `openCommandConfirmationModal(params)` | Show a confirmation dialog | +| `enqueueSnackbar(params)` | Show a toast notification | +| `unmountFrontComponent()` | Unmount the component | +| `updateProgress(progress)` | Update a progress indicator | + +下面是一个示例,使用宿主 API 在操作完成后显示一条 snackbar 并关闭侧边栏: + +```tsx src/front-components/archive-record.tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useRecordId } from 'twenty-sdk/front-component'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + +## Command options + +Adding a `command` field to `defineFrontComponent` registers the component in the command menu (Cmd+K). If `isPinned` is `true`, it also appears as a quick-action button in the top-right corner of the page. + +| 字段 | 必填 | 描述 | +| --------------------------------------- | -- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `universalIdentifier` | 是 | Stable unique ID for the command | +| `标签` | 是 | Full label shown in the command menu (Cmd+K) | +| `shortLabel` | 否 | Shorter label displayed on the pinned quick-action button | +| `图标` | 否 | Icon name displayed next to the label (e.g. `'IconBolt'`, `'IconSend'`) | +| `isPinned` | 否 | When `true`, shows the command as a quick-action button in the top-right corner of the page | +| `availabilityType` | 否 | Controls where the command appears: `'GLOBAL'` (always available), `'RECORD_SELECTION'` (only when records are selected), or `'FALLBACK'` (shown when no other commands match) | +| `availabilityObjectUniversalIdentifier` | 否 | Restrict the command to pages of a specific object type (e.g. only on Company records) | +| `conditionalAvailabilityExpression` | 否 | A boolean expression to dynamically control whether the command is visible (see below) | + +## Conditional availability expressions + +The `conditionalAvailabilityExpression` field lets you control when a command is visible based on the current page context. Import typed variables and operators from `twenty-sdk` to build expressions: + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + pageType, + numberOfSelectedRecords, + objectPermissions, + everyEquals, + isDefined, +} from 'twenty-sdk/front-component'; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'bulk-action', + component: BulkAction, + command: { + universalIdentifier: '...', + label: 'Bulk Update', + availabilityType: 'RECORD_SELECTION', + conditionalAvailabilityExpression: everyEquals( + objectPermissions, + 'canUpdateObjectRecords', + true, + ), + }, +}); +``` + +**Context variables** — these represent the current state of the page: + +| 变量 | 类型 | 描述 | +| ------------------------------ | --------- | ---------------------------------------------------------------- | +| `pageType` | `string` | Current page type (e.g. `'RecordIndexPage'`, `'RecordShowPage'`) | +| `isInSidePanel` | `boolean` | Whether the component is rendered in a side panel | +| `numberOfSelectedRecords` | `数字` | Number of currently selected records | +| `isSelectAll` | `boolean` | Whether "select all" is active | +| `selectedRecords` | `array` | The selected record objects | +| `favoriteRecordIds` | `array` | IDs of favorited records | +| `objectPermissions` | `对象` | Permissions for the current object type | +| `targetObjectReadPermissions` | `对象` | Read permissions for the target object | +| `targetObjectWritePermissions` | `对象` | Write permissions for the target object | +| `featureFlags` | `对象` | Active feature flags | +| `objectMetadataItem` | `对象` | Metadata of the current object type | +| `hasAnySoftDeleteFilterOnView` | `boolean` | Whether the current view has a soft-delete filter | + +**Operators** — combine variables into boolean expressions: + +| Operator | 描述 | +| ----------------------------------- | ----------------------------------------------------------------- | +| `isDefined(value)` | `true` if the value is not null/undefined | +| `isNonEmptyString(value)` | `true` if the value is a non-empty string | +| `includes(array, value)` | `true` if the array contains the value | +| `includesEvery(array, prop, value)` | `true` if every item's property includes the value | +| `every(array, prop)` | `true` if the property is truthy on every item | +| `everyDefined(array, prop)` | `true` if the property is defined on every item | +| `everyEquals(array, prop, value)` | `true` if the property equals the value on every item | +| `some(array, prop)` | `true` if the property is truthy on at least one item | +| `someDefined(array, prop)` | `true` if the property is defined on at least one item | +| `someEquals(array, prop, value)` | `true` if the property equals the value on at least one item | +| `someNonEmptyString(array, prop)` | `true` if the property is a non-empty string on at least one item | +| `none(array, prop)` | `true` if the property is falsy on every item | +| `noneDefined(array, prop)` | `true` if the property is undefined on every item | +| `noneEquals(array, prop, value)` | `true` if the property does not equal the value on any item | + +## Public assets + +Front components can access files from the app's `public/` directory using `getPublicAssetUrl`: + +```tsx +import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define'; + +const Logo = () => Logo; + +export default defineFrontComponent({ + universalIdentifier: '...', + name: 'logo', + component: Logo, +}); +``` + +See the [public assets section](/l/zh/developers/extend/apps/cli-and-testing#public-assets-public-folder) for details. + +## 样式 + +Front components support multiple styling approaches. You can use: + +* **Inline styles** — `style={{ color: 'red' }}` +* **Twenty UI components** — import from `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar, and more) +* **Emotion** — CSS-in-JS with `@emotion/react` +* **Styled-components** — `styled.div` patterns +* **Tailwind CSS** — utility classes +* **Any CSS-in-JS library** compatible with React + +```tsx +import { defineFrontComponent } from 'twenty-sdk/define'; +import { Button, Tag, Status } from 'twenty-sdk/ui'; + +const StyledWidget = () => { + return ( +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456', + name: 'styled-widget', + component: StyledWidget, +}); +``` diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/getting-started.mdx index 51877dc67c3..72dd9bed778 100644 --- a/packages/twenty-docs/l/zh/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/zh/developers/extend/apps/getting-started.mdx @@ -1,13 +1,12 @@ --- title: 开始使用 +icon: rocket description: 几分钟内创建你的第一个 Twenty 应用。 --- - -应用目前处于 Alpha 阶段。 该功能可用,但仍在演进中。 - +## 什么是应用? -应用可通过自定义对象、字段、逻辑函数、AI 技能和 UI 组件来扩展 Twenty——全部以代码进行管理。 +应用可通过自定义对象、字段、逻辑函数、前端组件、AI 技能等来扩展 Twenty——全部以代码进行管理。 无需通过 UI 配置所有内容,你可以用 TypeScript 定义数据模型和逻辑,并将其部署到一个或多个工作空间。 ## 先决条件 @@ -17,7 +16,9 @@ description: 几分钟内创建你的第一个 Twenty 应用。 * **Yarn 4** — 通过 Corepack 随 Node.js 提供。 通过运行 `corepack enable` 启用它 * **Docker** — [在此下载](https://www.docker.com/products/docker-desktop/)。 运行本地 Twenty 实例所必需。 如果你已经有一个正在运行的 Twenty 服务器,则不需要。 -## 步骤 1:为你的应用创建脚手架 +## 创建你的第一个应用 + +### 为你的应用创建脚手架 打开终端并运行: @@ -29,18 +30,7 @@ npx create-twenty-app@latest my-twenty-app 这会创建一个名为 `my-twenty-app` 的新文件夹,其中包含你所需的一切。 - -脚手架工具支持以下标志: - -* `--minimal` — 仅创建必要文件,不包含示例(默认) -* `--exhaustive` — 创建所有示例实体 -* `--name ` — 设置应用名称(跳过提示) -* `--display-name ` — 设置显示名称(跳过提示) -* `--description ` — 设置描述(跳过提示) -* `--skip-local-instance` — 跳过本地服务器设置提示 - - -## 步骤 2:设置本地 Twenty 实例 +### 设置本地 Twenty 实例 脚手架工具会询问: @@ -53,7 +43,7 @@ npx create-twenty-app@latest my-twenty-app 是否启动本地实例? -## 步骤 3:登录你的工作区 +### 登录你的工作区 接下来,将打开一个浏览器窗口,显示 Twenty 登录页面。 使用预置的演示账户登录: @@ -64,7 +54,7 @@ npx create-twenty-app@latest my-twenty-app Twenty 登录界面 -## 步骤 4:授权该应用 +### 授权该应用 登录后,你会看到一个授权界面。 这使你的应用可以与工作区交互。 @@ -80,7 +70,7 @@ npx create-twenty-app@latest my-twenty-app 应用脚手架创建成功 -## 步骤 5:开始开发 +### 开始开发 进入你的新应用文件夹并启动开发服务器: @@ -105,7 +95,22 @@ yarn twenty dev --verbose 开发模式终端输出 -## 步骤 6:在 Twenty 中查看你的应用 +#### 使用 `yarn twenty dev --once` 进行一次性同步 + +如果不希望有监视器在后台运行(例如在 CI 流水线、git 钩子或脚本化工作流中),请传入 `--once` 标志。 它运行与 `yarn twenty dev` 相同的流水线 — 构建清单、打包文件、上传、同步、重新生成类型化 API 客户端 — 但会在**同步完成后立即退出**: + +```bash filename="Terminal" +yarn twenty dev --once +``` + +| 命令 | 行为 | 适用场景 | +| ------------------------ | ----------------------------------------- | -------------------------------------- | +| `yarn twenty dev` | 监视你的源文件,并在每次更改时重新同步。 会持续运行,直到你停止它。 | 交互式本地开发 — 你需要实时状态面板和即时反馈循环。 | +| `yarn twenty dev --once` | 执行一次构建与同步,然后在成功时以代码 `0` 退出,失败时以代码 `1` 退出。 | 脚本、CI、pre-commit 钩子、AI 代理,以及任何非交互式工作流。 | + +两种模式都需要一个以开发模式运行的 Twenty 服务器和一个已认证的远程端 — 相同的先决条件同样适用。 + +### 在 Twenty 中查看你的应用 在浏览器中打开 [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer)。 前往 **设置 > 应用**,并选择 **开发者** 选项卡。 你应当在 **你的应用** 下看到你的应用: @@ -133,13 +138,28 @@ yarn twenty dev --verbose 一切就绪! 编辑 `src/` 中的任意文件,更改会被自动检测到。 -前往[构建应用](/l/zh/developers/extend/apps/building),查看关于创建对象、逻辑函数、前端组件、技能等的详细指南。 +--- + +## 你可以构建的内容 + +应用由**实体**组成——每个实体定义为一个包含单一 `export default` 的 TypeScript 文件: + +| 实体 | 作用 | +| ---------- | ------------------------------------------- | +| **对象与字段** | 使用类型化字段定义自定义数据模型(如 Post Card、Invoice) | +| **逻辑函数** | 由 HTTP 路由、cron 调度或数据库事件触发的服务端 TypeScript 函数 | +| **前端组件** | 在 Twenty 的 UI 内渲染的 React 组件(侧边面板、小部件、命令菜单) | +| **技能与智能体** | AI 能力——可复用的指令和自主助手 | +| **视图与导航** | 为你的对象预配置的列表视图和侧边栏菜单项 | +| **页面布局** | 带有选项卡和小部件的自定义记录详情页 | + +前往[构建应用](/l/zh/developers/extend/apps/building),查看每种实体类型的详细指南。 --- ## 项目结构 -脚手架工具会生成以下文件结构(以 `--exhaustive` 模式展示,其中包含每种实体类型的示例): +脚手架工具会生成以下文件结构: ```text filename="my-twenty-app/" my-twenty-app/ @@ -148,231 +168,82 @@ my-twenty-app/ .gitignore .nvmrc .yarnrc.yml - .yarn/ - install-state.gz .oxlintrc.json tsconfig.json - tsconfig.spec.json # TypeScript config for tests - vitest.config.ts # Vitest test runner configuration + tsconfig.spec.json # TypeScript config for tests + vitest.config.ts # Vitest test runner configuration LLMS.md README.md .github/ └── workflows/ - └── ci.yml # GitHub Actions CI workflow - public/ # Public assets (images, fonts, etc.) + └── ci.yml # GitHub Actions CI workflow + public/ # Public assets (images, fonts, etc.) src/ - ├── application-config.ts # Required — main application configuration - ├── __tests__/ - │ ├── setup-test.ts # Test setup (server health check, config) - │ └── app-install.integration-test.ts # Example integration test - ├── roles/ - │ └── default-role.ts # Default role for logic functions - ├── objects/ - │ └── example-object.ts # Example custom object definition - ├── fields/ - │ └── example-field.ts # Example standalone field definition - ├── logic-functions/ - │ ├── hello-world.ts # Example logic function - │ ├── create-hello-world-company.ts # Example logic function using CoreApiClient - │ ├── pre-install.ts # Runs before installation - │ └── post-install.ts # Runs after installation - ├── front-components/ - │ └── hello-world.tsx # Example front component - ├── page-layouts/ - │ └── example-record-page-layout.ts # Example page layout with front component - ├── views/ - │ └── example-view.ts # Example saved view definition - ├── navigation-menu-items/ - │ └── example-navigation-menu-item.ts # Example sidebar navigation link - ├── skills/ - │ └── example-skill.ts # Example AI agent skill definition - └── agents/ - └── example-agent.ts # Example AI agent definition + ├── application-config.ts # Required — main application configuration + ├── default-role.ts # Default role for logic functions + ├── constants/ + │ └── universal-identifiers.ts # Auto-generated UUIDs and app metadata + └── __tests__/ + ├── setup-test.ts # Test setup (server health check, config) + └── app-install.integration-test.ts # Integration test ``` -默认情况下(`--minimal`),仅创建核心文件:`application-config.ts`、`roles/default-role.ts`、`logic-functions/pre-install.ts` 和 `logic-functions/post-install.ts`。 使用 `--exhaustive` 可包含上面展示的所有示例文件。 +### 从示例开始 + +若要从一个更完整的示例开始(包含自定义对象、字段、逻辑函数、前端组件等),请使用 `--example` 标志: + +```bash filename="Terminal" +npx create-twenty-app@latest my-twenty-app --example postcard +``` + +示例来自 GitHub 上的 [twenty-apps/examples](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples) 目录。 你也可以使用 `yarn twenty add` 为现有项目生成单个实体的脚手架(参见[构建应用](/l/zh/developers/extend/apps/building#scaffolding-entities-with-yarn-twenty-add))。 ### 关键文件 -| 文件 / 文件夹 | 目的 | -| ---------------------------- | ------------------------------------------------------------------ | -| `package.json` | 声明应用的名称、版本和依赖。 包含一个 `twenty` 脚本,因此你可以运行 `yarn twenty help` 查看所有命令。 | -| `src/application-config.ts` | **必需。** 应用的主配置文件。 | -| `src/roles/` | 定义角色,用于控制逻辑函数的访问权限。 | -| `src/logic-functions/` | 由路由、cron 调度或数据库事件触发的服务端函数。 | -| `src/front-components/` | 在 Twenty 的 UI 中渲染的 React 组件。 | -| `src/objects/` | 用于扩展数据模型的自定义对象定义。 | -| `src/fields/` | 添加到现有对象的自定义字段。 | -| `src/views/` | 已保存的视图配置。 | -| `src/navigation-menu-items/` | 侧边栏导航中的自定义链接。 | -| `src/skills/` | 用于扩展 Twenty 的 AI 代理的技能. | -| `src/agents/` | 具有自定义提示词的 AI 智能体。 | -| `src/page-layouts/` | 记录视图的自定义页面布局。 | -| `src/__tests__/` | 集成测试(设置 + 示例测试)。 | -| `public/` | 随应用一起提供的静态资源(图像、字体)。 | +| 文件 / 文件夹 | 目的 | +| ---------------------------------------- | ------------------------------------------------------------------ | +| `package.json` | 声明应用的名称、版本和依赖。 包含一个 `twenty` 脚本,因此你可以运行 `yarn twenty help` 查看所有命令。 | +| `src/application-config.ts` | **必需。** 应用的主配置文件。 | +| `src/default-role.ts` | 默认角色,用于控制你的逻辑函数可访问的内容。 | +| `src/constants/universal-identifiers.ts` | 自动生成的 UUID 和应用元数据(显示名称、描述)。 | +| `src/__tests__/` | 集成测试(设置 + 示例测试)。 | +| `public/` | 随应用一起提供的静态资源(图像、字体)。 | -## 管理远程 +## 本地开发服务器 -“远程”是指你的应用连接到的 Twenty 服务器。 在设置期间,脚手架工具会为你自动创建一个。 你可以随时添加更多远程或在它们之间切换。 +脚手架工具已经为你启动了一个本地 Twenty 服务器。 稍后要进行管理,请使用 `yarn twenty server`: -```bash filename="Terminal" -# Add a new remote (opens a browser for OAuth login) -yarn twenty remote add +| 命令 | 描述 | +| -------------------------------------- | -------------------- | +| `yarn twenty server start` | 启动本地服务器(按需拉取镜像) | +| `yarn twenty server start --port 3030` | 在自定义端口启动 | +| `yarn twenty server start --test` | 在 2021 端口启动一个独立的测试实例 | +| `yarn twenty server stop` | 停止服务器(保留数据) | +| `yarn twenty server status` | 显示服务器状态、URL 和凭据 | +| `yarn twenty server logs` | 流式输出服务器日志 | +| `yarn twenty server logs --lines 100` | 显示最近 100 行日志 | +| `yarn twenty server reset` | 删除所有数据并全新开始 | -# Connect to a local Twenty server (auto-detects port 2020 or 3000) -yarn twenty remote add --local +数据在重启后会保留,存储于两个 Docker 卷中(`twenty-app-dev-data` 用于 PostgreSQL,`twenty-app-dev-storage` 用于文件)。 使用 `reset` 清空所有内容并重新开始。 -# Add a remote non-interactively (useful for CI) -yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote +### 运行测试实例 -# List all configured remotes -yarn twenty remote list +向任何 `server` 命令传递 `--test` 以管理第二个、完全隔离的实例——这对于运行集成测试或在不影响主开发数据的情况下进行试验非常有用。 -# Switch the active remote -yarn twenty remote switch -``` +| 命令 | 描述 | +| ---------------------------------- | ------------------- | +| `yarn twenty server start --test` | 启动测试实例 (默认端口为 2021) | +| `yarn twenty server stop --test` | 停止测试实例 | +| `yarn twenty server status --test` | 显示测试实例状态、URL 和凭据 | +| `yarn twenty server logs --test` | 流式输出测试实例日志 | +| `yarn twenty server reset --test` | 清除测试数据并全新开始 | -你的凭据存储在 `~/.twenty/config.json` 中。 - -## 本地开发服务器(`yarn twenty server`) - -CLI 可以管理在 Docker 中运行的本地 Twenty 服务器。 这与使用 `create-twenty-app` 搭建应用时自动启动的服务器相同,但你也可以手动管理它。 - -### 启动服务器 - -```bash filename="Terminal" -yarn twenty server start -``` - -这将拉取 `twentycrm/twenty-app-dev:latest` Docker 镜像(如果尚未存在),创建名为 `twenty-app-dev` 的容器,并在端口 **2020** 上启动它。 CLI 会等待服务器通过健康检查后再返回。 - -会创建两个 Docker 卷,以在重启之间持久化数据: - -* `twenty-app-dev-data` — PostgreSQL 数据库 -* `twenty-app-dev-storage` — 文件存储 - -如果端口 2020 已被占用,你可以在其他端口上启动: - -```bash filename="Terminal" -yarn twenty server start --port 3030 -``` - -CLI 会自动配置容器内部的 `NODE_PORT` 和 `SERVER_URL` 以匹配所选端口,从而使逻辑函数、OAuth 以及所有其他内部网络正常工作。 - -启动后,该服务器会在你的 CLI 配置中自动注册为 `local` 远程。 - -### 检查服务器状态 - -```bash filename="Terminal" -yarn twenty server status -``` - -显示服务器是否在运行、其 URL,以及默认登录凭据(`tim@apple.dev` / `tim@apple.dev`)。 - -### 查看服务器日志 - -```bash filename="Terminal" -yarn twenty server logs -``` - -持续输出容器日志。 使用 `--lines` 控制显示的最近日志行数: - -```bash filename="Terminal" -yarn twenty server logs --lines 100 -``` - -### 停止服务器 - -```bash filename="Terminal" -yarn twenty server stop -``` - -停止容器。 你的数据会保存在 Docker 卷中——下次 `start` 会从上次中断处继续。 - -### 重置服务器 - -```bash filename="Terminal" -yarn twenty server reset -``` - -移除容器并删除这两个 Docker 卷,清除所有数据。 下一次 `start` 会创建一个全新实例。 +测试实例在其独立的 Docker 容器 (`twenty-app-dev-test`) 中运行,配有专用的卷 (`twenty-app-dev-test-data`, `twenty-app-dev-test-storage`) 和配置,因此可以与主实例并行运行且不会发生冲突。 将 `--test` 与 `--port` 一起使用以覆盖默认的 2021 端口。 服务器需要 Docker 处于运行状态。 如果看到 "Docker not running" 错误,请确保 Docker Desktop(或 Docker 守护进程)已启动。 -### 命令参考 - -| 命令 | 描述 | -| -------------------------------------- | --------------- | -| `yarn twenty server start` | 启动本地服务器(按需拉取镜像) | -| `yarn twenty server start --port 3030` | 在自定义端口启动 | -| `yarn twenty server stop` | 停止服务器(保留数据) | -| `yarn twenty server status` | 显示服务器状态、URL 和凭据 | -| `yarn twenty server logs` | 流式输出服务器日志 | -| `yarn twenty server logs --lines 100` | 显示最近 100 行日志 | -| `yarn twenty server reset` | 删除所有数据并全新开始 | - -## 使用 GitHub Actions 进行 CI - -脚手架工具会在 `.github/workflows/ci.yml` 生成一个开箱即用的 GitHub Actions 工作流。 它会在每次向 `main` 推送以及拉取请求上自动运行你的集成测试。 - -工作流: - -1. 检出你的代码 -2. 使用 `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` 动作启动一个临时的 Twenty 服务器 -3. 使用 `yarn install --immutable` 安装依赖 -4. 运行 `yarn test`,并从该动作的输出中注入 `TWENTY_API_URL` 和 `TWENTY_API_KEY` - -```yaml .github/workflows/ci.yml -name: CI - -on: - push: - branches: - - main - pull_request: {} - -env: - TWENTY_VERSION: latest - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Spawn Twenty instance - id: twenty - uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main - with: - twenty-version: ${{ env.TWENTY_VERSION }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Enable Corepack - run: corepack enable - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'yarn' - - - name: Install dependencies - run: yarn install --immutable - - - name: Run integration tests - run: yarn test - env: - TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} - TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }} -``` - -你无需配置任何机密——`spawn-twenty-docker-image` 动作会在运行器中直接启动一个临时的 Twenty 服务器,并输出连接详情。 GitHub 会自动提供 `GITHUB_TOKEN` 机密。 - -若要固定为特定的 Twenty 版本而不是 `latest`,请在工作流顶部修改 `TWENTY_VERSION` 环境变量。 - ## 手动设置(不使用脚手架) 如果你不想使用 `create-twenty-app`,而是自行完成设置,可以分两步进行。 diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/layout.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/layout.mdx new file mode 100644 index 00000000000..d6f6d85e221 --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/apps/layout.mdx @@ -0,0 +1,131 @@ +--- +title: 布局 +description: Define views, navigation menu items, and page layouts to shape how your app appears in Twenty. +icon: table-columns +--- + +Layout entities control how your app surfaces inside Twenty's UI — what lives in the sidebar, which saved views ship with the app, and how a record detail page is arranged. + +## Layout concepts + +| Concept | What it controls | 实体 | +| ------------------------ | --------------------------------------------------------------------------------- | -------------------------- | +| **View** | A saved list configuration for an object — visible fields, order, filters, groups | `defineView` | +| **Navigation Menu Item** | An entry in the left sidebar that links to a view or an external URL | `defineNavigationMenuItem` | +| **Page Layout** | The tabs and widgets that make up a record's detail page | `definePageLayout` | + +Views, navigation items, and page layouts reference each other by `universalIdentifier`: + +* A **navigation menu item** of type `VIEW` points at a `defineView` identifier, so the sidebar link opens that saved view. +* A **page layout** of type `RECORD_PAGE` targets an object and can embed [front components](/l/zh/developers/extend/apps/front-components) inside its tabs as widgets. + + + + +Views are saved configurations for how records of an object are displayed — including which fields are visible, their order, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app: + +```ts src/views/example-view.ts +import { defineView, ViewKey } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; + +export default defineView({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'All example items', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + icon: 'IconList', + key: ViewKey.INDEX, + position: 0, + fields: [ + { + universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0', + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + size: 200, + }, + ], +}); +``` + +关键点: +* `objectUniversalIdentifier` specifies which object this view applies to. +* `key` determines the view type (e.g., `ViewKey.INDEX` for the main list view). +* `fields` controls which columns appear and their order. Each field references a `fieldMetadataUniversalIdentifier`. +* You can also define `filters`, `filterGroups`, `groups`, and `fieldGroups` for more advanced configurations. +* `position` controls the ordering when multiple views exist for the same object. + + + + +Navigation menu items add custom entries to the workspace sidebar. Use `defineNavigationMenuItem()` to link to views, external URLs, or objects: + +```ts src/navigation-menu-items/example-navigation-menu-item.ts +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; +import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view'; + +export default defineNavigationMenuItem({ + universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c', + name: 'example-navigation-menu-item', + icon: 'IconList', + color: 'blue', + position: 0, + type: NavigationMenuItemType.VIEW, + viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER, +}); +``` + +关键点: +* `type` determines what the menu item links to: `NavigationMenuItemType.VIEW` for a saved view, or `NavigationMenuItemType.LINK` for an external URL. +* For view links, set `viewUniversalIdentifier`. For external links, set `link`. +* `position` controls the ordering in the sidebar. +* `icon` and `color` (optional) customize the appearance. + + + + +Page layouts let you customize how a record detail page looks — which tabs appear, what widgets are inside each tab, and how they are arranged. Use `definePageLayout()` to ship custom layouts with your app: + +```ts src/page-layouts/example-record-page-layout.ts +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; +import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; +import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world'; + +export default definePageLayout({ + universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134', + name: 'Example Record Page', + type: 'RECORD_PAGE', + objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, + tabs: [ + { + universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5', + title: 'Hello World', + position: 50, + icon: 'IconWorld', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d', + title: 'Hello World', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); +``` + +关键点: +* `type` is typically `'RECORD_PAGE'` to customize the detail view of a specific object. +* `objectUniversalIdentifier` specifies which object this layout applies to. +* Each `tab` defines a section of the page with a `title`, `position`, and `layoutMode` (`CANVAS` for free-form layout). +* Each `widget` inside a tab can render a front component, a relation list, or other built-in widget types. +* `position` on tabs controls their order. Use higher values (e.g., 50) to place custom tabs after built-in ones. + + + diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/logic-functions.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/logic-functions.mdx new file mode 100644 index 00000000000..53066ee68d0 --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/apps/logic-functions.mdx @@ -0,0 +1,560 @@ +--- +title: 逻辑函数 +description: Define server-side TypeScript functions with HTTP, cron, and database event triggers. +icon: bolt +--- + +Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents. + + + + +每个函数文件都使用 `defineLogicFunction()` 导出包含处理程序和可选触发器的配置。 + +```ts src/logic-functions/createPostCard.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define'; +import { CoreApiClient, type Person } from 'twenty-client-sdk/core'; + +const handler = async (params: RoutePayload) => { + const client = new CoreApiClient(); + const name = 'name' in params.queryStringParameters + ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' + : 'Hello world'; + + const result = await client.mutation({ + createPostCard: { + __args: { data: { name } }, + id: true, + name: true, + }, + }); + return result; +}; + +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'create-new-post-card', + timeoutSeconds: 2, + handler, + httpRouteTriggerSettings: { + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: true, + }, + /*databaseEventTriggerSettings: { + eventName: 'people.created', + },*/ + /*cronTriggerSettings: { + pattern: '0 0 1 1 *', + },*/ +}); +``` + +可用的触发器类型: +* **httpRoute**:在 **`/s/` 端点**下通过 HTTP 路径和方法公开你的函数: +> 例如 `path: '/post-card/create'` 可在 `https://your-twenty-server.com/s/post-card/create` 调用 +* **cron**:使用 CRON 表达式按计划运行你的函数。 +* **databaseEvent**:在工作空间对象生命周期事件上运行。 当事件操作为 `updated` 时,可以在 `updatedFields` 数组中指定要监听的特定字段。 如果未定义或为空,任何更新都会触发该函数。 +> 例如 `person.updated`、`*.created`、`company.*` + + +你也可以使用 CLI 手动执行函数: + +```bash filename="Terminal" +yarn twenty exec -n create-new-post-card -p '{"key": "value"}' +``` + +```bash filename="Terminal" +yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +``` + +你可以通过以下方式查看日志: + +```bash filename="Terminal" +yarn twenty logs +``` + + +#### 路由触发器负载 + +当路由触发器调用你的逻辑函数时,它会接收一个遵循 +[AWS HTTP API v2 格式](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html)的 `RoutePayload` 对象。 +从 `twenty-sdk` 导入 `RoutePayload` 类型: + +```ts +import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; + +const handler = async (event: RoutePayload) => { + const { headers, queryStringParameters, pathParameters, body } = event; + const { method, path } = event.requestContext.http; + + return { message: 'Success' }; +}; +``` + +`RoutePayload` 类型具有以下结构: + + | 属性 | 类型 | 描述 | 示例 | + | ---------------------------- | ------------------------------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------- | + | `headers` | `Record\` | HTTP 请求头(仅限 `forwardedRequestHeaders` 中列出的那些) | 见下文 | + | `queryStringParameters` | `Record\` | 查询字符串参数(多个值以逗号连接) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` | + | `pathParameters` | `Record\` | 从路由模式中提取的路径参数 | `/users/:id`,`/users/123` -> `{ id: '123' }` | + | `body` | `object \| null` | 已解析的请求体(JSON) | `{ id: 1 }` -> `{ id: 1 }` | + | `isBase64Encoded` | `boolean` | 请求体是否为 base64 编码 | | + | `requestContext.http.method` | `string` | HTTP 方法(GET、POST、PUT、PATCH、DELETE) | | + | `requestContext.http.path` | `string` | 原始请求路径 | | + + +#### forwardedRequestHeaders + +出于安全原因,默认**不会**将传入请求的 HTTP 请求头传递给你的逻辑函数。 +如需访问特定请求头,请在 `forwardedRequestHeaders` 数组中显式列出: + +```ts +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'webhook-handler', + handler, + httpRouteTriggerSettings: { + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, +}); +``` + +在你的处理程序中,可以这样访问被转发的请求头: + +```ts +const handler = async (event: RoutePayload) => { + const signature = event.headers['x-webhook-signature']; + const contentType = event.headers['content-type']; + + // Validate webhook signature... + return { received: true }; +}; +``` + + +请求头名称会被规范化为小写。 请使用小写键访问它们(例如,`event.headers['content-type']`)。 + + +#### 将函数作为工具公开 + +逻辑函数可以作为供 AI 智能体和工作流使用的**工具**对外提供。 当函数被标记为工具时,Twenty 的 AI 功能即可发现它,并可在工作流自动化中使用。 + +要将逻辑函数标记为工具,请设置 `isTool: true`: + +```ts src/logic-functions/enrich-company.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk/define'; +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const handler = async (params: { companyName: string; domain?: string }) => { + const client = new CoreApiClient(); + + const result = await client.mutation({ + createTask: { + __args: { + data: { + title: `Enrich data for ${params.companyName}`, + body: `Domain: ${params.domain ?? 'unknown'}`, + }, + }, + id: true, + }, + }); + + return { taskId: result.createTask.id }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + name: 'enrich-company', + description: 'Enrich a company record with external data', + timeoutSeconds: 10, + handler, + isTool: true, +}); +``` + +关键点: + +* 你可以将 `isTool` 与触发器结合使用——一个函数既可以作为工具(由 AI 智能体调用),也可以同时由事件触发。 +* **`toolInputSchema`**(可选):描述函数可接受参数的 JSON Schema 对象。 该模式会通过对源代码的静态分析自动推导,但你也可以显式设置: + +```ts +export default defineLogicFunction({ + ..., + toolInputSchema: { + type: 'object', + properties: { + companyName: { + type: 'string', + description: 'The name of the company to enrich', + }, + domain: { + type: 'string', + description: 'The company website domain (optional)', + }, + }, + required: ['companyName'], + }, +}); +``` + + +**写一个好的 `description`。** AI 智能体会依赖该函数的 `description` 字段来决定何时使用该工具。 明确说明该工具的作用以及应在何时调用。 + + + + + +安装后函数是在你的应用安装到工作区后自动运行的逻辑函数。 服务器会在应用的元数据已同步并已生成 SDK 客户端**之后**执行它,因此工作区已完全可用,且新架构已就绪。 常见用例包括预置默认数据、创建初始记录、配置工作区设置,或在第三方服务上预配资源。 + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + shouldRunSynchronously: false, + handler, +}); +``` + +你也可以随时使用 CLI 手动执行安装后函数: + +```bash filename="Terminal" +yarn twenty exec --postInstall +``` + +关键点: +* 安装后函数使用 `definePostInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。 +* 处理程序会接收一个 `InstallPayload`,其为 `{ previousVersion?: string; newVersion: string }` —— `newVersion` 是正在安装的版本,而 `previousVersion` 是先前已安装的版本(在全新安装时为 `undefined`)。 使用这些值来区分全新安装与升级,并运行特定版本的迁移逻辑。 +* **钩子何时运行**:默认情况下,仅在全新安装时运行。 如果还希望在应用从旧版本升级时运行,请传入 `shouldRunOnVersionUpgrade: true`。 若省略,该标志默认为 `false`,升级将跳过该钩子。 +* **执行模型 — 默认异步,可选择同步**:`shouldRunSynchronously` 标志控制安装后*如何*执行。 + * `shouldRunSynchronously: false` *(默认)* — 该钩子会**加入消息队列**,设置 `retryLimit: 3`,并在工作线程中异步运行。 作业一入列,安装响应即返回,因此缓慢或失败的处理程序不会阻塞调用方。 工作线程最多会重试三次。 **将其用于长时间运行的作业**——预填充大型数据集、调用缓慢的第三方 API、预配外部资源,以及任何可能超出合理 HTTP 响应窗口的任务。 + * `shouldRunSynchronously: true` — 该钩子会在安装流程中**内联执行**(与安装前使用相同的执行器)。 安装请求将阻塞直至处理程序完成;若抛出异常,安装调用方将收到 `POST_INSTALL_ERROR`。 不进行自动重试。 **用于需要在响应前完成的快速工作**——例如向用户返回验证错误,或进行安装调用返回后客户端将立即依赖的快速设置。 请注意,运行安装后时,元数据迁移已应用完成,因此同步模式下的失败**不会**回滚架构更改——它只会暴露错误。 +* 确保你的处理程序是幂等的。 在异步模式下,队列最多可重试三次;在任一模式下,当 `shouldRunOnVersionUpgrade: true` 时,该钩子在升级时可能再次运行。 +* 在处理程序内可使用环境变量 `APPLICATION_ID`、`APP_ACCESS_TOKEN` 和 `API_URL`(与其他逻辑函数相同),因此你可以使用作用域限定到你应用的应用访问令牌调用 Twenty API。 +* 每个应用仅允许一个安装后函数。 如果检测到多个,清单构建将报错。 +* 构建期间,函数的 `universalIdentifier`、`shouldRunOnVersionUpgrade` 和 `shouldRunSynchronously` 会自动附加到应用清单的 `postInstallLogicFunction` 字段下——你无需在 `defineApplication()` 中引用它们。 +* 默认超时时间设置为 300 秒(5 分钟),以便支持更长的设置任务,如数据填充。 +* **开发模式下不执行**:当应用在本地注册(通过 `yarn twenty dev`)时,服务器会完全跳过安装流程,并通过 CLI 监视器直接同步文件——因此无论 `shouldRunSynchronously` 如何,安装后在开发模式下都不会运行。 使用 `yarn twenty exec --postInstall` 在运行中的工作区上手动触发它。 + + + + +安装前函数是在安装期间自动运行的逻辑函数,**在应用工作区元数据迁移之前**。 它与安装后共享相同的负载结构(`InstallPayload`),但在安装流程中位置更早,因此可以准备即将到来的迁移所依赖的状态——典型用例如备份数据、验证与新架构的兼容性,或归档即将被重构或删除的记录。 + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; + +const handler = async (payload: InstallPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +你也可以随时使用 CLI 手动执行安装前函数: + +```bash filename="Terminal" +yarn twenty exec --preInstall +``` + +关键点: +* 安装前函数使用 `definePreInstallLogicFunction()`——与安装后相同的专用配置,只是附加到不同的生命周期阶段。 +* 安装前和安装后处理程序接收相同的 `InstallPayload` 类型:`{ previousVersion?: string; newVersion: string }`。 导入一次,可在两个钩子中复用。 +* **钩子何时运行**:位于工作区元数据迁移(`synchronizeFromManifest`)之前。 在执行之前,服务器会运行一次纯增量的“精简同步”,将**新**版本的安装前函数注册到工作区元数据中——不会触及其他任何内容——然后再执行它。 由于此次同步仅为增量操作,当你的处理程序运行时,上一版本的对象、字段和数据仍完好无损:你可以安全地读取并备份迁移前的状态。 +* **执行模型**:安装前以**同步**方式执行,并且**会阻塞安装**。 如果处理程序抛出异常,安装会在任何架构更改应用之前被中止——工作区将保持在上一版本且处于一致状态。 这是有意为之:安装前是你拒绝高风险升级的最后机会。 +* 与安装后相同,每个应用仅允许一个安装前函数。 在构建期间,它会自动附加到应用清单的 `preInstallLogicFunction` 下。 +* **开发模式下不执行**:与安装后相同——对于本地注册的应用将完全跳过安装流程,因此在 `yarn twenty dev` 下不会运行安装前。 使用 `yarn twenty exec --preInstall` 手动触发它。 + + + + +两个钩子都属于同一安装流程,并接收相同的 `InstallPayload`。 区别在于它们相对于工作区元数据迁移**何时**运行,这会影响它们可以安全访问的数据范围。 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ install flow │ +│ │ +│ upload package → [pre-install] → metadata migration → │ +│ generate SDK → [post-install] │ +│ │ +│ old schema visible new schema visible │ +└─────────────────────────────────────────────────────────────┘ +``` + +安装前始终为**同步**(会阻塞安装并可中止它)。 安装后**默认异步**——在工作线程中入列并自动重试——但可通过 `shouldRunSynchronously: true` 选择同步执行。 关于各模式的使用场景,请参见上方的 `definePostInstallLogicFunction` 折叠面板。 + +**对于需要新架构已存在的任何事项,请使用 `post-install`。** 这是最常见的情况: + +* 针对新添加的对象和字段预填充默认数据(创建初始记录、默认视图、演示内容)。 +* 在应用已有凭据的前提下,向第三方服务注册 Webhook。 +* 调用你自己的 API 完成依赖已同步元数据的设置。 +* 用于在每次升级时对状态进行对账的幂等“确保其存在”逻辑——结合 `shouldRunOnVersionUpgrade: true` 使用。 + +示例——在安装后预填充一个默认的 `PostCard` 记录: + +```ts src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion }: InstallPayload): Promise => { + if (previousVersion) return; // fresh installs only + + const client = createClient(); + await client.postCard.create({ + data: { title: 'Welcome to Postcard', content: 'Your first card!' }, + }); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210', + name: 'post-install', + description: 'Seeds a welcome post card after install.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: false, + handler, +}); +``` + +**当迁移可能破坏或损坏现有数据时,请使用 `pre-install`。** 由于安装前在*先前*架构上运行,且其失败会回滚升级,因此凡是有风险的操作都应放在这里: + +* **备份即将被删除或重构的数据**——例如,你在 v2 中移除某个字段,需要在迁移运行前将其值复制到另一个字段或导出到存储中。 +* **归档会被新约束判为无效的记录**——例如某个字段将变为 `NOT NULL`,你需要先删除或修正具有空值的行。 +* **验证兼容性;若当前数据无法干净迁移则拒绝升级**——从处理程序中抛出异常,安装将中止且不会应用任何更改。 这比在迁移中途才发现不兼容要更安全。 +* **在会导致关联丢失的架构更改之前**对数据进行重命名或重新设置键。 + +示例——在破坏性迁移之前归档记录: + +```ts src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define'; +import { createClient } from './generated/client'; + +const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise => { + // Only the 1.x → 2.x upgrade drops the legacy `notes` field. + if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) { + return; + } + + const client = createClient(); + const legacyRecords = await client.postCard.findMany({ + where: { notes: { isNotNull: true } }, + }); + + if (legacyRecords.length === 0) return; + + // Copy legacy `notes` into the new `description` field before the migration + // drops the `notes` column. If this fails, the upgrade is aborted and the + // workspace stays on v1 with all data intact. + await Promise.all( + legacyRecords.map((record) => + client.postCard.update({ + where: { id: record.id }, + data: { description: record.notes }, + }), + ), + ); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab', + name: 'pre-install', + description: 'Backs up legacy notes into description before the v2 migration.', + timeoutSeconds: 300, + shouldRunOnVersionUpgrade: true, + handler, +}); +``` + +**经验法则:** + +| You want to... | 使用 | +| ----------------------- | ------------------------------------------------------------- | +| 预填充默认数据、配置工作区、注册外部资源 | `post-install` | +| 运行不应阻塞安装响应的长时间预填充或第三方调用 | `post-install` (默认 — `shouldRunSynchronously: false`,由工作线程重试) | +| 运行安装调用返回后调用方将立即依赖的快速设置 | `post-install`,配合 `shouldRunSynchronously: true` | +| 读取或备份即将被迁移丢失的数据 | `pre-install` | +| 拒绝会损坏现有数据的升级 | `pre-install`(从处理程序中抛出异常) | +| 在每次升级时执行对账 | `post-install` 配合 `shouldRunOnVersionUpgrade: true` | +| 仅在首次安装时执行一次性设置 | `post-install` 配合 `shouldRunOnVersionUpgrade: false`(默认) | + + +如有不确定,默认选择**安装后(post-install)**。 仅当迁移本身具有破坏性,且你需要在其丢失之前拦截先前状态时,才使用安装前。 + + + + + +## 类型化 API 客户端(`twenty-client-sdk`) + +`twenty-client-sdk` 包提供了两个类型化的 GraphQL 客户端,供你的逻辑函数和前端组件与 Twenty API 交互。 + +| 客户端 | 导入 | 端点 | 是否生成? | +| ------------------- | ---------------------------- | ------------------------ | --------- | +| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql`——工作区数据(记录、对象) | 是,在开发/构建时 | +| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata`——工作区配置、文件上传 | 否,已预构建提供 | + + + + +`CoreApiClient` 是用于查询和变更工作区数据的主要客户端。 它会在执行 `yarn twenty dev` 或 `yarn twenty build` 时**根据你的工作区架构生成**,因此完全类型化以匹配你的对象和字段。 + +```ts +import { CoreApiClient } from 'twenty-client-sdk/core'; + +const client = new CoreApiClient(); + +// Query records +const { companies } = await client.query({ + companies: { + edges: { + node: { + id: true, + name: true, + domainName: { + primaryLinkLabel: true, + primaryLinkUrl: true, + }, + }, + }, + }, +}); + +// Create a record +const { createCompany } = await client.mutation({ + createCompany: { + __args: { + data: { + name: 'Acme Corp', + }, + }, + id: true, + name: true, + }, +}); +``` + +该客户端使用选择集语法:传入 `true` 以包含某字段,使用 `__args` 传递参数,并通过嵌套对象表示关系。 你将基于工作区架构获得完整的自动补全和类型检查。 + + +**CoreApiClient 在开发/构建时生成。** 如果在未先运行 `yarn twenty dev` 或 `yarn twenty build` 的情况下尝试使用它,将会抛出错误。 该生成过程是自动完成的——CLI 会自省你的工作区 GraphQL 架构,并使用 `@genql/cli` 生成类型化客户端。 + + +#### 使用 CoreSchema 进行类型标注 + +`CoreSchema` 提供与工作区对象相匹配的 TypeScript 类型,可用于为组件状态或函数参数进行类型标注: + +```ts +import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core'; +import { useState } from 'react'; + +const [company, setCompany] = useState< + Pick | undefined +>(undefined); + +const client = new CoreApiClient(); +const result = await client.query({ + company: { + __args: { filter: { position: { eq: 1 } } }, + id: true, + name: true, + }, +}); +setCompany(result.company); +``` + + + + +`MetadataApiClient` 随 SDK 一并提供,已预构建(无需生成)。 它会查询 `/metadata` 端点以获取工作区配置、应用和文件上传。 + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; + +const metadataClient = new MetadataApiClient(); + +// List first 10 objects in the workspace +const { objects } = await metadataClient.query({ + objects: { + edges: { + node: { + id: true, + nameSingular: true, + namePlural: true, + labelSingular: true, + isCustom: true, + }, + }, + __args: { + filter: {}, + paging: { first: 10 }, + }, + }, +}); +``` + +#### 上传文件 + +`MetadataApiClient` 包含一个 `uploadFile` 方法,用于将文件附加到文件类型字段: + +```ts +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import * as fs from 'fs'; + +const metadataClient = new MetadataApiClient(); + +const fileBuffer = fs.readFileSync('./invoice.pdf'); + +const uploadedFile = await metadataClient.uploadFile( + fileBuffer, // file contents as a Buffer + 'invoice.pdf', // filename + 'application/pdf', // MIME type + '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier +); + +console.log(uploadedFile); +// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } +``` + +| 参数 | 类型 | 描述 | +| ---------------------------------- | -------- | -------------------------------------------- | +| `fileBuffer` | `Buffer` | 原始文件内容 | +| `filename` | `string` | 文件名称(用于存储和显示) | +| `contentType` | `string` | MIME 类型(如果省略,默认为 `application/octet-stream`) | +| `fieldMetadataUniversalIdentifier` | `string` | 你的对象上文件类型字段的 `universalIdentifier` | + +关键点: +* 使用字段的 `universalIdentifier`(而不是其工作区特定的 ID),因此你的上传代码可在安装了你的应用的任何工作区中运行。 +* 返回的 `url` 是一个签名 URL,你可以用它来访问已上传的文件。 + + + + + + 当你的代码在 Twenty 上运行(逻辑函数或前端组件)时,平台会以环境变量的形式注入凭据: + + * `TWENTY_API_URL`——Twenty API 的基础 URL + * `TWENTY_APP_ACCESS_TOKEN`——作用域限定为你的应用默认函数角色的短期密钥 + + 你无需将这些值传递给客户端——它们会自动从 `process.env` 读取。 API 密钥的权限由你的 `application-config.ts` 中 `defaultRoleUniversalIdentifier` 引用的角色决定。 + diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx index 56f2e7a4241..2854f77a3dd 100644 --- a/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx @@ -1,12 +1,9 @@ --- title: 发布 +icon: 上传 description: 将你的 Twenty 应用分发到应用市场,或进行内部部署。 --- - - 应用目前处于 Alpha 阶段。 该功能可用,但仍在演进中。 - - ## 概览 一旦你的应用已[在本地构建并完成测试](/l/zh/developers/extend/apps/building),你可以通过两种方式进行分发: @@ -52,7 +49,11 @@ yarn twenty deploy ### 共享已部署的应用 -通过 tar 包分发的应用不会出现在公共市场中,因此同一服务器上的其他工作区无法通过浏览发现它们。 要共享已部署的应用: + +在多个工作区之间共享私有(tarball)应用是一项 **Enterprise** 功能。 在您的工作区拥有有效的 Enterprise 密钥之前,**Distribution** 选项卡将显示升级提示,而不是共享控件。 请前往 [设置 > 管理面板 > Enterprise](/settings/admin-panel#enterprise) 以启用。 + + +通过 tar 包分发的应用不会出现在公共市场中,因此同一服务器上的其他工作区无法通过浏览发现它们。 一旦您的工作区升级到企业版计划,您就可以像这样分享已部署的应用: 1. 前往 **Settings > Applications > Registrations** 并打开你的应用 2. 在 **Distribution** 选项卡中,点击 **Copy share link** @@ -60,20 +61,73 @@ yarn twenty deploy 该分享链接使用服务器的基础 URL(不包含任何工作区子域),因此适用于该服务器上的任意工作区。 - -Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it. - - ### 版本管理 +在更新已部署的 tarball 应用时,服务器要求 `package.json` 中的 `version` 必须**严格高于**(按[语义化版本](https://semver.org)排序)当前已部署的版本。 在 tar 包存储之前,重新部署相同版本或推送更低版本都会被拒绝 — 你会在 CLI 中看到 `VERSION_ALREADY_EXISTS` 错误。 + 要发布更新: -1. 更新 `package.json` 中的 `version` 字段 +1. 将 `package.json` 中的 `version` 字段递增(例如:`1.2.3` → `1.2.4`、`1.3.0` 或 `2.0.0`) 2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`) 3. 已安装该应用的工作区会在其设置中看到可用的升级 + +预发布标签按预期工作:将 `1.0.0-rc.1` 递增为 `1.0.0-rc.2` 是允许的,并且像 `1.0.0` 这样的正式发布会被正确识别为高于 `1.0.0-rc.5`。 `package.json` 中的版本本身必须是有效的 SemVer 字符串。 + + {/* TODO: add screenshot of the Upgrade button */} +## 自动化 CI/CD(脚手架生成的工作流) + +使用 `create-twenty-app` 生成的应用开箱即带有两个 GitHub Actions 工作流,位于 `.github/workflows/`。 当你将仓库推送到 GitHub 后即可运行——CI 无需额外设置,CD 只需要一个机密。 + +### CI — `ci.yml` + +它会在每次向 `main` 推送以及拉取请求上自动运行你的集成测试。 + +**作用:** + +1. 检出你的应用源代码。 +2. 使用 `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main` 组合 action 启动一个隔离的 Twenty 测试实例(相当于 CI 中的 `yarn twenty server start --test`)。 +3. 启用 Corepack,从你的 `.nvmrc` 设置 Node.js,并使用 `yarn install --immutable` 安装依赖。 +4. 运行 `yarn test`,并从启动的实例传入 `TWENTY_API_URL` 和 `TWENTY_API_KEY`,以便你的测试可以与真实服务器通信。 + +**配置选项:** + +* `TWENTY_VERSION`(环境变量,默认 `latest`)— 通过在 `ci.yml` 中编辑它来固定 CI 使用的 Twenty 服务器版本。 +* 并发按 `github.ref` 分组,并会在有新的推送时取消进行中的运行。 + +不需要任何机密——测试实例是临时的,只在作业持续期间存在。 + +### CD — `cd.yml` + +在每次向 `main` 推送时将你的应用部署到已配置的 Twenty 服务器;当为拉取请求添加 `deploy` 标签时,也可从该拉取请求进行部署。 + +**作用:** + +1. 检出 PR 的 head(针对已加标签的 PR),或被推送的提交。 +2. 运行 `twentyhq/twenty/.github/actions/deploy-twenty-app@main`——相当于 CI 中的 `yarn twenty deploy`。 +3. 运行 `twentyhq/twenty/.github/actions/install-twenty-app@main`,将新部署的版本安装到目标工作区。 + +**必需的配置:** + +| 设置 | 位置 | 目的 | +| ----------------------- | -------------------------------------------------------- | ---------------------------------------- | +| `TWENTY_DEPLOY_URL` | `cd.yml` 中的 `env`(默认值为 `http://localhost:3000`) | 要部署到的 Twenty 服务器。 首次使用前将其更改为你真实的服务器 URL。 | +| `TWENTY_DEPLOY_API_KEY` | GitHub 仓库 **Settings → Secrets and variables → Actions** | 在目标服务器上具有部署权限的 API 密钥。 | + + +默认的 `TWENTY_DEPLOY_URL` 值 `http://localhost:3000` 只是占位符——从 GitHub 托管的 runner 无法访问任何资源。 在启用 CD 之前,将其更新为你服务器的公网 URL(或使用具有网络访问权限的自托管 runner)。 + + +**从 PR 触发预览部署:** + +为拉取请求添加 `deploy` 标签。 在 `cd.yml` 中的 `if:` 守卫会使用该 PR 的 head 提交为其运行作业,使你能在合并前在目标服务器上验证更改。 + +### 固定可复用的 actions + +两个工作流都引用了位于 `@main` 的可复用 actions,因此会自动获取 `twentyhq/twenty` 仓库中的 action 更新。 如果你希望构建具有确定性,请在每个 `uses:` 行中将 `@main` 替换为某个提交的 SHA 或发行标签。 + ## 发布到 npm 发布到 npm 可让你的应用在 Twenty 应用市场中被发现。 任何 Twenty 工作区都可以直接通过 UI 浏览、安装和升级应用市场中的应用。 @@ -81,7 +135,7 @@ Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > E ### 要求 * 一个 [npm](https://www.npmjs.com) 账户 -* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`) +* 你在 `package.json` 的 `keywords` 数组中的 `twenty-app` 关键字(需要手动添加 — 在 `create-twenty-app` 模板中默认不包含) ```json filename="package.json" { @@ -189,3 +243,12 @@ You can also install apps from the command line: ```bash filename="Terminal" yarn twenty install ``` + + +服务器在安装时强制执行 SemVer 版本控制,与部署时的规则一致: + +* 尝试安装与工作区中已安装版本相同的版本将被拒绝,并返回 `APP_ALREADY_INSTALLED` 错误。 +* 尝试安装低于当前已安装版本的版本将被拒绝,并返回 `CANNOT_DOWNGRADE_APPLICATION` 错误。 + +若要安装较新的版本,请先部署或发布它,然后重新运行 `yarn twenty install`。 + diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/skills-and-agents.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/skills-and-agents.mdx new file mode 100644 index 00000000000..cf3556d861e --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/apps/skills-and-agents.mdx @@ -0,0 +1,69 @@ +--- +title: 技能与智能体 +description: Define AI skills and agents for your app. +icon: robot +--- + + + Skills and agents are currently in alpha. 该功能可用,但仍在演进中。 + + +Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts. + + + + +技能定义了可复用的指令和能力,AI 智能体可在你的工作区中使用。 使用 `defineSkill()` 定义带内置校验的技能: + +```ts src/skills/example-skill.ts +import { defineSkill } from 'twenty-sdk/define'; + +export default defineSkill({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'sales-outreach', + label: 'Sales Outreach', + description: 'Guides the AI agent through a structured sales outreach process', + icon: 'IconBrain', + content: `You are a sales outreach assistant. When reaching out to a prospect: +1. Research the company and recent news +2. Identify the prospect's role and likely pain points +3. Draft a personalized message referencing specific details +4. Keep the tone professional but conversational`, +}); +``` + +关键点: +* `name` 是该技能的唯一标识字符串(推荐使用 kebab-case)。 +* `label` 是在 UI 中显示的人类可读名称。 +* `content` 包含技能指令——这是 AI 智能体使用的文本。 +* `icon`(可选)设置在 UI 中显示的图标。 +* `description`(可选)提供有关技能用途的更多上下文。 + + + + +Agents are AI assistants that live inside your workspace. Use `defineAgent()` to create agents with a custom system prompt: + +```ts src/agents/example-agent.ts +import { defineAgent } from 'twenty-sdk/define'; + +export default defineAgent({ + universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + name: 'sales-assistant', + label: 'Sales Assistant', + description: 'Helps the sales team draft outreach emails and research prospects', + icon: 'IconRobot', + prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.', +}); +``` + +关键点: +* `name` is the unique identifier string for the agent (kebab-case recommended). +* `label` is the display name shown in the UI. +* `prompt` is the system prompt that defines the agent's behavior. +* `description` (optional) provides context about what the agent does. +* `icon`(可选)设置在 UI 中显示的图标。 +* `modelId` (optional) overrides the default AI model used by the agent. + + + diff --git a/packages/twenty-docs/l/zh/developers/extend/oauth.mdx b/packages/twenty-docs/l/zh/developers/extend/oauth.mdx new file mode 100644 index 00000000000..7023de54854 --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/oauth.mdx @@ -0,0 +1,189 @@ +--- +title: OAuth +icon: 键 +description: Authorization code flow with PKCE and client credentials for server-to-server access. +--- + +Twenty implements OAuth 2.0 with authorization code + PKCE for user-facing apps and client credentials for server-to-server access. Clients are registered dynamically via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) — no manual setup in a dashboard. + +## When to Use OAuth + +| 场景 | Auth Method | +| --------------------------------------- | -------------------------------------------------------------------------------- | +| Internal scripts, automation | [API Key](/l/zh/developers/extend/api#authentication) | +| External app acting on behalf of a user | **OAuth — Authorization Code** | +| Server-to-server, no user context | **OAuth — Client Credentials** | +| Twenty App with UI extensions | [Apps](/l/zh/developers/extend/apps/getting-started) (OAuth is handled automatically) | + +## Register a Client + +Twenty supports **dynamic client registration** per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). No manual setup needed — register programmatically: + +```bash +POST /oauth/register +Content-Type: application/json + +{ + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"], + "grant_types": ["authorization_code"], + "token_endpoint_auth_method": "client_secret_post" +} +``` + +**Response:** + +```json +{ + "client_id": "abc123", + "client_secret": "secret456", + "client_name": "My Integration", + "redirect_uris": ["https://myapp.com/callback"] +} +``` + + +Store the `client_secret` securely — it cannot be retrieved later. + + +## 范围 + +| Scope | 访问 | +| ------ | ---------------------------------------------------- | +| `api` | Full read/write access to the Core and Metadata APIs | +| `个人资料` | Read the authenticated user's profile information | + +Request scopes as a space-separated string: `scope=api profile` + +## Authorization Code Flow + +Use this flow when your app acts on behalf of a Twenty user. + +### 1. Redirect the user to authorize + +``` +GET /oauth/authorize? + client_id=YOUR_CLIENT_ID& + response_type=code& + redirect_uri=https://myapp.com/callback& + scope=api& + state=random_state_value& + code_challenge=CHALLENGE& + code_challenge_method=S256 +``` + +| 参数 | 必填 | 描述 | +| ----------------------- | -- | ------------------------------------------------------------ | +| `client_id` | 是 | Your registered client ID | +| `response_type` | 是 | Must be `code` | +| `redirect_uri` | 是 | Must match a registered redirect URI | +| `scope` | 否 | Space-separated scopes (defaults to `api`) | +| `状态` | 推荐 | Random string to prevent CSRF attacks | +| `code_challenge` | 推荐 | PKCE challenge (SHA-256 hash of verifier, base64url-encoded) | +| `code_challenge_method` | 推荐 | Must be `S256` when using PKCE | + +The user sees a consent screen and approves or denies access. + +### 2. Handle the callback + +After authorization, Twenty redirects back to your `redirect_uri`: + +``` +https://myapp.com/callback?code=AUTH_CODE&state=random_state_value +``` + +Verify that `state` matches what you sent. + +### 3. Exchange the code for tokens + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=authorization_code& +code=AUTH_CODE& +redirect_uri=https://myapp.com/callback& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +code_verifier=YOUR_PKCE_VERIFIER +``` + +**Response:** + +```json +{ + "access_token": "eyJhbG...", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "dGhpcyBpcyBh..." +} +``` + +### 4. Use the access token + +```bash +GET /rest/companies +Authorization: Bearer ACCESS_TOKEN +``` + +### 5. Refresh when expired + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=refresh_token& +refresh_token=YOUR_REFRESH_TOKEN& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET +``` + +## Client Credentials Flow + +For server-to-server integrations with no user interaction: + +```bash +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +grant_type=client_credentials& +client_id=YOUR_CLIENT_ID& +client_secret=YOUR_CLIENT_SECRET& +scope=api +``` + +The returned token has workspace-level access, not tied to any specific user. + +## Server Discovery + +Twenty publishes its OAuth configuration at a standard discovery endpoint: + +``` +GET /.well-known/oauth-authorization-server +``` + +This returns all endpoints, supported grant types, scopes, and capabilities — useful for building generic OAuth clients. + +## API Endpoints Summary + +| 端点 | 目的 | +| ----------------------------------------- | --------------------------- | +| `/.well-known/oauth-authorization-server` | Server metadata discovery | +| `/oauth/register` | Dynamic client registration | +| `/oauth/authorize` | User authorization | +| `/oauth/token` | Token exchange and refresh | + +| 环境 | 基础 URL | +| ------- | ------------------------ | +| **云端** | `https://api.twenty.com` | +| **自托管** | `https://{your-domain}` | + +## OAuth vs API Keys + +| | API 密钥 | OAuth | +| ------------------ | ----------------------- | -------------------------------------- | +| **Setup** | Generate in Settings | Register a client, implement flow | +| **User context** | None (workspace-level) | Specific user's permissions | +| **Best for** | Scripts, internal tools | External apps, multi-user integrations | +| **Token rotation** | 手动 | Automatic via refresh tokens | +| **Scoped access** | Full API access | Granular via scopes | diff --git a/packages/twenty-docs/l/zh/developers/extend/webhooks.mdx b/packages/twenty-docs/l/zh/developers/extend/webhooks.mdx index a6546b02b30..0198b11e016 100644 --- a/packages/twenty-docs/l/zh/developers/extend/webhooks.mdx +++ b/packages/twenty-docs/l/zh/developers/extend/webhooks.mdx @@ -1,11 +1,12 @@ --- title: Webhooks -description: 当您的 CRM 中发生事件时接收实时通知。 +icon: satellite-dish +description: Get notified when records change — HTTP POST to your endpoint on every create, update, or delete. --- import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; -当 Twenty 中发生事件时,Webhook 会实时将数据推送到您的系统 — 无需轮询。 使用它们来保持外部系统同步、触发自动化或发送警报。 +Twenty sends an HTTP POST to your URL whenever a record is created, updated, or deleted. All object types are covered, including custom objects. ## 创建 Webhook diff --git a/packages/twenty-docs/l/zh/developers/introduction.mdx b/packages/twenty-docs/l/zh/developers/introduction.mdx index 6906d4334b0..ac6b6272e13 100644 --- a/packages/twenty-docs/l/zh/developers/introduction.mdx +++ b/packages/twenty-docs/l/zh/developers/introduction.mdx @@ -1,23 +1,28 @@ --- -title: 开始使用 -description: 欢迎来到 Twenty 开发者文档,这是您用于扩展、自托管以及为 Twenty 做出贡献的资源。 +title: 开发者 +description: Build apps, use the API, self-host, or contribute to the codebase. --- import { CardTitle } from "/snippets/card-title.mdx" - - 扩展 - 使用 API、网络钩子和自定义应用构建集成。 + + Apps + Extend Twenty with custom objects, server-side logic, UI components, and AI agents — all as TypeScript packages. - - 自托管 - 在您自己的基础设施上部署并管理 Twenty。 + + API + REST and GraphQL APIs, webhooks, and OAuth. - - 贡献 - 加入我们的开源社区并为 Twenty 做出贡献。 + + Self-Host + Run Twenty on your own infrastructure. + + + + Contribute + Set up the monorepo locally and submit PRs. diff --git a/packages/twenty-docs/l/zh/developers/self-host/capabilities/cloud-providers.mdx b/packages/twenty-docs/l/zh/developers/self-host/capabilities/cloud-providers.mdx index 4c7ee10b831..2b47e1a32d8 100644 --- a/packages/twenty-docs/l/zh/developers/self-host/capabilities/cloud-providers.mdx +++ b/packages/twenty-docs/l/zh/developers/self-host/capabilities/cloud-providers.mdx @@ -1,5 +1,6 @@ --- title: 其他方法 +icon: cloud --- diff --git a/packages/twenty-docs/l/zh/developers/self-host/capabilities/docker-compose.mdx b/packages/twenty-docs/l/zh/developers/self-host/capabilities/docker-compose.mdx index c41d30a6faf..580372236d0 100644 --- a/packages/twenty-docs/l/zh/developers/self-host/capabilities/docker-compose.mdx +++ b/packages/twenty-docs/l/zh/developers/self-host/capabilities/docker-compose.mdx @@ -1,5 +1,6 @@ --- -title: 1-点击使用Docker Compose +title: Docker Compose +icon: docker --- diff --git a/packages/twenty-docs/l/zh/developers/self-host/capabilities/setup.mdx b/packages/twenty-docs/l/zh/developers/self-host/capabilities/setup.mdx index ec17d07297c..2f96046858d 100644 --- a/packages/twenty-docs/l/zh/developers/self-host/capabilities/setup.mdx +++ b/packages/twenty-docs/l/zh/developers/self-host/capabilities/setup.mdx @@ -1,5 +1,6 @@ --- title: 设置 +icon: gear --- # 配置管理 diff --git a/packages/twenty-docs/l/zh/developers/self-host/capabilities/troubleshooting.mdx b/packages/twenty-docs/l/zh/developers/self-host/capabilities/troubleshooting.mdx index 31fea02bc3d..5a33241de3a 100644 --- a/packages/twenty-docs/l/zh/developers/self-host/capabilities/troubleshooting.mdx +++ b/packages/twenty-docs/l/zh/developers/self-host/capabilities/troubleshooting.mdx @@ -1,5 +1,6 @@ --- title: 故障排除 +icon: wrench --- ## 故障排除 diff --git a/packages/twenty-docs/l/zh/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/zh/developers/self-host/capabilities/upgrade-guide.mdx index 794d884ff04..db8d2d858e6 100644 --- a/packages/twenty-docs/l/zh/developers/self-host/capabilities/upgrade-guide.mdx +++ b/packages/twenty-docs/l/zh/developers/self-host/capabilities/upgrade-guide.mdx @@ -1,5 +1,6 @@ --- title: 升级指南 +icon: arrow-up-right-dots --- ## 通用指南 @@ -16,364 +17,14 @@ title: 升级指南 3. 用 `docker compose up -d` 重新启动 Twenty -如果您想要升级实例多个版本,例如从 v0.33.0 升级到 v0.35.0,您需要按顺序升级您的实例,例如从 v0.33.0 升级到 v0.34.0,然后从 v0.34.0 到 v0.35.0。 - **确保每个升级后的版本都具有未损坏的备份。** ## 特定版本的升级步骤 -## v1.0 +## After v1.21 -Hello Twenty v1.0! 🎉 +We know support sequential upgrades. You don't need to go through each version one by one. -## v0.60 +## Before v1.21 -### 性能增强 - -与元数据 API 的所有交互已优化,以获得更好的性能,尤其是对象元数据操作和工作区创建操作。 - -我们重构了缓存策略,以在可能的情况下优先于数据库查询显著提高元数据 API 操作的性能。 - -如果在升级后遇到任何运行时问题,可能需要清空您的缓存以确保其与最新更改同步。 在 twenty-server 容器中运行此命令: - -```bash -yarn command:prod cache:flush -``` - -### v0.55 - -升级您的 Twenty 实例以使用 v0.55 映像 - -您不再需要运行任何命令,新映像将自动处理所需的所有迁移。 - -### `User does not have permission` error - -如果在大多数请求中出现授权错误,升级后可能需要清空您的缓存以重新计算最新的权限。 - -在你的 `twenty-server` 容器中,运行: - -```bash -yarn command:prod cache:flush -``` - -此问题仅适用于此 Twenty 版本,未来的升级不应需要此操作。 - -### v0.54 - -自版本 `0.53` 开始,不需要手动操作。 - -#### 元数据模式弃用 - -我们已将 `metadata` 模式合并到 `core` 模式以简化从 `TypeORM` 数据的检索。 -我们在 `upgrade` 命令中合并了 `migrate` 命令步骤。 我们不建议在任何 server/worker 容器内手动运行 `migrate`。 - -### 自 v0.53 - -从 `0.53` 起,升级在 `DockerFile` 内以编程方式完成,这意味着从现在起,您不必再手动运行任何命令。 - -确保保持顺序升级您的实例,不要跳过任何大版本(例如,允许从 `0.43.3` 升级到 `0.44.0`,但不能从 `0.43.1` 升级到 `0.45.0`),否则可能导致工作区版本不同步,可能导致运行时错误和功能缺失。 - -要检查是否正确迁移了工作区,您可以在数据库中查看其版本在 `core.workspace` 表中。 - -它应始终在您当前 Twenty 实例 `major.minor` 版本的范围内,您可以在管理面板中查看您的实例版本(在 `/settings/admin-panel`,如果您的用户在数据库中设置了 `canAccessFullAdminPanel` 属性为真则可访问)或通过在 `twenty-server` 容器中运行 `echo $APP_VERSION`。 - -要修复不同步的工作区版本,你将需要从相应的 Twenty 版本按照相关的升级指南顺序升级,等等,直到到达所需的版本。 - -#### `auditLog` 移除 - -我们已移除了 auditLog 标准对象,这意味着在此迁移后,备份的大小可能显著减少。 - -### v0.51 to v0.52 - -升级您的 Twenty 实例以使用 v0.52 映像 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### 我的工作区被锁定在 `0.52.0` 和 `0.52.6` 之间的版本 - -不幸的是,`0.52.0` 和 `0.52.6` 已从 dockerHub 完全移除。 -您将需要在数据库中手动更新工作区版本到 `0.51.0`,并使用 Twenty 版本 `0.52.11` 进行升级,按照其上方的升级指南。 - -### v0.50 to v0.51 - -升级您的 Twenty 实例以使用 v0.51 映像 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.44.0 to v0.50.0 - -升级您的 Twenty 实例以使用 v0.50.0 映像 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -#### Docker-compose.yml 修改 - -此版本包括对 `docker-compose.yml` 的修改,以赋予 `worker` 服务对 `server-local-data` 卷的访问权限。 -请使用 [v0.50.0 docker-compose.yml](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml) 更新本地主机上的 `docker-compose.yml` - -### v0.43.0 to v0.44.0 - -升级您的 Twenty 实例以使用 v0.44.0 映像 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -### v0.42.0 to v0.43.0 - -升级您的 Twenty 实例以使用 v0.43.0 映像 - -``` -yarn database:migrate:prod -yarn command:prod upgrade -``` - -在此版本中,我们还在 docker-compose.yml 中切换到了 postgres:16 映像。 - -#### (选项 1)数据库迁移 - -保留现有的 postgres-spilo 图像是可以的,但您需要在 docker-compose.yml 中将版本冻结为 0.43.0。 - -#### (选项 2)数据库迁移 - -如果您想将数据库迁移到新的 postgres:16 图像,请按照以下步骤操作: - -1. 从旧的 postgres-spilo 容器中导出数据库 - -``` -docker exec -it twenty-db-1 sh -pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql -exit -docker cp twenty-db-1:/home/postgres/databases_backup.sql . -``` - -确保您的转储文件不为空。 - -2. 将您的 docker-compose.yml 升级为与 [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) 文件中的 postgres:16 映像相同。 - -3. 将新 postgres:16 容器中的数据库恢复 - -``` -docker cp databases_backup.sql twenty-db-1:/databases_backup.sql -docker exec -it twenty-db-1 sh -psql -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} -f databases_backup.sql -exit -``` - -### v0.41.0 to v0.42.0 - -升级您的 Twenty 实例以使用 v0.42.0 映像 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.42 -``` - -**环境变量** - -* 移除:`FRONT_PORT`,`FRONT_PROTOCOL`,`FRONT_DOMAIN`,`PORT` -* 新增:`FRONTEND_URL`,`NODE_PORT`,`MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`,`MESSAGING_PROVIDER_MICROSOFT_ENABLED`,`CALENDAR_PROVIDER_MICROSOFT_ENABLED`,`IS_MICROSOFT_SYNC_ENABLED` - -### v0.40.0 to v0.41.0 - -升级您的 Twenty 实例以使用 v0.41.0 映像 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.41 -``` - -**环境变量** - -* 移除:`AUTH_MICROSOFT_TENANT_ID` - -### v0.35.0 to v0.40.0 - -升级您的 Twenty 实例以使用 v0.40.0 映像 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.40 -``` - -**环境变量** - -* 新增:`IS_EMAIL_VERIFICATION_REQUIRED`,`EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`,`WORKFLOW_EXEC_THROTTLE_LIMIT`,`WORKFLOW_EXEC_THROTTLE_TTL` - -### v0.34.0 to v0.35.0 - -升级您的 Twenty 实例以使用 v0.35.0 映像 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.35 -``` - -`yarn database:migrate:prod` 命令将迁移应用于数据库结构(核心和元数据模式) -`yarn command:prod upgrade-0.35` 负责所有工作区的数据迁移。 - -**环境变量** - -* 我们将 `ENABLE_DB_MIGRATIONS` 替换为 `DISABLE_DB_MIGRATIONS`(默认值现在为 `false`,您可能不必设置任何东西) - -### v0.33.0 to v0.34.0 - -升级您的 Twenty 实例以使用 v0.34.0 映像 - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.34 -``` - -`yarn database:migrate:prod` 命令将迁移应用于数据库结构(核心和元数据模式) -`yarn command:prod upgrade-0.34` 负责所有工作区的数据迁移。 - -**环境变量** - -* 移除:`FRONT_BASE_URL` -* 新增:`FRONT_DOMAIN`,`FRONT_PROTOCOL`,`FRONT_PORT` - -我们更新了前端 URL 的处理方式。 -您现在可以使用 `FRONT_DOMAIN`,`FRONT_PROTOCOL` 和 `FRONT_PORT` 变量设置前端 URL。 -如果未设置 FRONT_DOMAIN,前端 URL 将回退到 `SERVER_URL`。 - -### v0.32.0 to v0.33.0 - -升级您的 Twenty 实例以使用 v0.33.0 映像 - -``` -yarn command:prod cache:flush -yarn database:migrate:prod -yarn command:prod upgrade-0.33 -``` - -`yarn command:prod cache:flush` 命令将清空 Redis 缓存。 -`yarn database:migrate:prod` 命令将迁移应用于数据库结构(核心和元数据模式) -`yarn command:prod upgrade-0.33` 负责所有工作区的数据迁移。 - -从该版本开始,用于 DB 的 twenty-postgres 图像已经弃用,并改用 twenty-postgres-spilo。 -如果要继续使用 twenty-postgres 映像,只需在 docker-compose.yml 中将 `twentycrm/twenty-postgres:${TAG}` 替换为 `twentycrm/twenty-postgres` 即可。 - -### v0.31.0 to v0.32.0 - -升级您的 Twenty 实例以使用 v0.32.0 映像 - -**模式和数据迁移** - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.32 -``` - -`yarn database:migrate:prod` 命令将迁移应用于数据库结构(核心和元数据模式) -`yarn command:prod upgrade-0.32` 负责所有工作区的数据迁移。 - -**环境变量** - -我们更新了 Redis 连接的处理方式。 - -* 移除:`REDIS_HOST`,`REDIS_PORT`,`REDIS_USERNAME`,`REDIS_PASSWORD` -* 新增:`REDIS_URL` - -更新您的 `.env` 文件以使用新 `REDIS_URL` 变量,而不是各个 Redis 连接参数。 - -我们也简化了我们处理 JWT 令牌的方式。 - -* 移除:`ACCESS_TOKEN_SECRET`,`LOGIN_TOKEN_SECRET`,`REFRESH_TOKEN_SECRET`,`FILE_TOKEN_SECRET` -* 新增:`APP_SECRET` - -更新您的 `.env` 文件以使用新 `APP_SECRET` 变量,而不是各个令牌密钥(您可以使用以前的相同密钥或生成一个新的随机字符串) - -**连接账户** - -如果您使用已连接的帐户来同步您的谷歌电子邮件和日历,您需要在谷歌管理员控制台中激活 [People API](https://developers.google.com/people)。 - -### v0.30.0 to v0.31.0 - -升级您的 Twenty 实例以使用 v0.31.0 映像 - -**模式和数据迁移**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.31 -``` - -`yarn database:migrate:prod` 命令将迁移应用于数据库结构(核心和元数据模式) -`yarn command:prod upgrade-0.31` 负责所有工作区的数据迁移。 - -### v0.24.0 to v0.30.0 - -升级您的 Twenty 实例以使用 v0.30.0 映像 - -**重大更改**: -为了提高性能,Twenty 现在要求配置 Redis 缓存。 我们更新了我们的 [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) 来反映这一点。 -确保更新您的配置并相应地更新您的环境变量: - -``` -REDIS_HOST={your-redis-host} -REDIS_PORT={your-redis-port} -CACHE_STORAGE_TYPE=redis -``` - -**模式和数据迁移**: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.30 -``` - -`yarn database:migrate:prod` 命令将迁移应用于数据库结构(核心和元数据模式) -`yarn command:prod upgrade-0.30` 负责所有工作区的数据迁移。 - -### v0.23.0 to v0.24.0 - -升级您的 Twenty 实例以使用 v0.24.0 映像 - -运行以下命令: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.24 -``` - -`yarn database:migrate:prod` 命令将迁移应用于数据库结构(核心和元数据模式) -`yarn command:prod upgrade-0.24` 负责所有工作区的数据迁移。 - -### v0.22.0 to v0.23.0 - -升级您的 Twenty 实例以使用 v0.23.0 映像 - -运行以下命令: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.23 -``` - -`yarn database:migrate:prod` 命令将迁移应用于数据库。 -`yarn command:prod upgrade-0.23` 负责数据迁移,包括将活动转移到任务/笔记。 - -### v0.21.0 to v0.22.0 - -升级您的 Twenty 实例以使用 v0.22.0 映像 - -运行以下命令: - -``` -yarn database:migrate:prod -yarn command:prod upgrade-0.22 -``` - -`yarn database:migrate:prod` 命令将迁移应用于数据库。 -该 `yarn command:prod upgrade-0.22` 命令将应用特定的数据转换,以适配新的对象 defaultRequestInstrumentationOptions。 +Make sure to go through every major tagged version when upgrading (upgrade v1.6.x to v.7.y, then v.7.y to v.8.z, etc.). diff --git a/packages/twenty-docs/l/zh/navigation.json b/packages/twenty-docs/l/zh/navigation.json index 6e85392d5de..efebcb29758 100644 --- a/packages/twenty-docs/l/zh/navigation.json +++ b/packages/twenty-docs/l/zh/navigation.json @@ -1,24 +1,27 @@ { "tabs": { + "gettingStarted": { + "label": "开始使用", + "groups": { + "welcome": { + "label": "Welcome" + }, + "coreConcepts": { + "label": "Core Concepts" + } + } + }, "userGuide": { "label": "用户指南", "groups": { - "discoverTwenty": { - "label": "发现 Twenty", - "groups": { - "gettingStartedCapabilities": { - "label": "功能" - }, - "gettingStartedHowTos": { - "label": "操作指南" - } - } + "userGuideOverview": { + "label": "概览" }, "dataModel": { "label": "数据模型", "groups": { - "dataModelCapabilities": { - "label": "功能" + "dataModelReference": { + "label": "Reference" }, "dataModelHowTos": { "label": "操作指南" @@ -28,8 +31,8 @@ "dataMigration": { "label": "数据迁移", "groups": { - "dataMigrationCapabilities": { - "label": "功能" + "dataMigrationReference": { + "label": "Reference" }, "dataMigrationHowTos": { "label": "操作指南" @@ -39,8 +42,8 @@ "calendarEmails": { "label": "日历与电子邮件", "groups": { - "calendarEmailsCapabilities": { - "label": "功能" + "calendarEmailsReference": { + "label": "Reference" }, "calendarEmailsHowTos": { "label": "操作指南" @@ -50,8 +53,8 @@ "workflows": { "label": "工作流", "groups": { - "workflowsCapabilities": { - "label": "功能" + "workflowsReference": { + "label": "Reference" }, "workflowsHowTos": { "label": "操作指南", @@ -75,21 +78,26 @@ "ai": { "label": "AI", "groups": { - "aiCapabilities": { - "label": "功能" + "aiReference": { + "label": "Reference" }, "aiHowTos": { "label": "操作指南" } } }, - "viewsPipelines": { - "label": "视图与管道", + "layout": { + "label": "布局", "groups": { - "viewsPipelinesCapabilities": { - "label": "功能" + "layoutReference": { + "label": "Reference", + "groups": { + "layoutViews": { + "label": "视图" + } + } }, - "viewsPipelinesHowTos": { + "layoutHowTos": { "label": "操作指南" } } @@ -97,8 +105,8 @@ "dashboards": { "label": "仪表板", "groups": { - "dashboardsCapabilities": { - "label": "功能" + "dashboardsReference": { + "label": "Reference" }, "dashboardsHowTos": { "label": "操作指南" @@ -108,8 +116,8 @@ "permissionsAccess": { "label": "权限与访问", "groups": { - "permissionsAccessCapabilities": { - "label": "功能" + "permissionsAccessReference": { + "label": "Reference" }, "permissionsAccessHowTos": { "label": "操作指南" @@ -119,8 +127,8 @@ "billing": { "label": "账单", "groups": { - "billingCapabilities": { - "label": "功能" + "billingReference": { + "label": "Reference" }, "billingHowTos": { "label": "操作指南" @@ -130,8 +138,8 @@ "settings": { "label": "设置", "groups": { - "settingsCapabilities": { - "label": "功能" + "settingsReference": { + "label": "Reference" }, "settingsHowTos": { "label": "操作指南" @@ -143,59 +151,20 @@ "developers": { "label": "开发者", "groups": { - "developersGroup": { - "label": "开发者" + "developersOverview": { + "label": "概览" }, - "extend": { - "label": "扩展", - "groups": { - "apps": { - "label": "应用" - } - } + "apps": { + "label": "应用" + }, + "api": { + "label": "接口" }, "selfHost": { - "label": "自托管", - "groups": { - "selfHostCapabilities": { - "label": "功能" - } - } + "label": "自托管" }, "contribute": { - "label": "贡献", - "groups": { - "contributeCapabilities": { - "label": "功能", - "groups": { - "frontendDevelopment": { - "label": "前端开发", - "groups": { - "twentyUi": { - "label": "Twenty UI", - "groups": { - "display": { - "label": "展示" - }, - "feedback": { - "label": "反馈" - }, - "input": { - "label": "输入" - }, - "navigation": { - "label": "导航" - } - } - } - } - }, - "backendDevelopment": { - "label": "后端开发" - } - } - } - } + "label": "贡献" } } } diff --git a/packages/twenty-docs/l/zh/twenty-ui/display/app-tooltip.mdx b/packages/twenty-docs/l/zh/twenty-ui/display/app-tooltip.mdx index 256a6ba05e7..9d2fad87f99 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/display/app-tooltip.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/display/app-tooltip.mdx @@ -1,5 +1,6 @@ --- title: 应用程序提示 +icon: 消息 --- diff --git a/packages/twenty-docs/l/zh/twenty-ui/display/checkmark.mdx b/packages/twenty-docs/l/zh/twenty-ui/display/checkmark.mdx index 9e7231d1b6e..4820345bc16 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/display/checkmark.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/display/checkmark.mdx @@ -1,5 +1,6 @@ --- title: 对勾标记 +icon: circle-check --- diff --git a/packages/twenty-docs/l/zh/twenty-ui/display/icons.mdx b/packages/twenty-docs/l/zh/twenty-ui/display/icons.mdx index b8ae34241ba..13adfa8277d 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/display/icons.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/display/icons.mdx @@ -1,5 +1,6 @@ --- title: 图标 +icon: 图标 --- diff --git a/packages/twenty-docs/l/zh/twenty-ui/display/soon-pill.mdx b/packages/twenty-docs/l/zh/twenty-ui/display/soon-pill.mdx index a3e94827f43..3aa2d74c87b 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/display/soon-pill.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/display/soon-pill.mdx @@ -2,7 +2,6 @@ title: 即将推出的小徽章或 "药丸"。 --- - 指示某物即将出现的小徽章或 "药丸" 。 ```jsx diff --git a/packages/twenty-docs/l/zh/twenty-ui/display/tag.mdx b/packages/twenty-docs/l/zh/twenty-ui/display/tag.mdx index 21296272d86..2c85c575bd8 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/display/tag.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/display/tag.mdx @@ -1,8 +1,8 @@ --- title: 标签 +icon: 标签 --- - 用于可视化分类或标记内容的组件。 diff --git a/packages/twenty-docs/l/zh/twenty-ui/input/buttons.mdx b/packages/twenty-docs/l/zh/twenty-ui/input/buttons.mdx index 70fe448d864..617c86d90fd 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/input/buttons.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/input/buttons.mdx @@ -1,5 +1,6 @@ --- title: 按钮 +icon: hand-pointer --- diff --git a/packages/twenty-docs/l/zh/twenty-ui/input/checkbox.mdx b/packages/twenty-docs/l/zh/twenty-ui/input/checkbox.mdx index 4982d9a022d..474ebeec4a8 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/input/checkbox.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/input/checkbox.mdx @@ -1,5 +1,6 @@ --- title: 复选框 +icon: square-check --- diff --git a/packages/twenty-docs/l/zh/twenty-ui/input/color-scheme.mdx b/packages/twenty-docs/l/zh/twenty-ui/input/color-scheme.mdx index 5b417d584aa..0dbd456d7c6 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/input/color-scheme.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/input/color-scheme.mdx @@ -1,5 +1,6 @@ --- title: 配色方案 +icon: 调色板 --- diff --git a/packages/twenty-docs/l/zh/twenty-ui/input/radio.mdx b/packages/twenty-docs/l/zh/twenty-ui/input/radio.mdx index d45bdb5d55e..04ea3fafb75 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/input/radio.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/input/radio.mdx @@ -1,5 +1,6 @@ --- title: 单选按钮 +icon: circle-dot --- diff --git a/packages/twenty-docs/l/zh/twenty-ui/input/toggle.mdx b/packages/twenty-docs/l/zh/twenty-ui/input/toggle.mdx index 23ea7f2b5f9..87f1468a067 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/input/toggle.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/input/toggle.mdx @@ -1,8 +1,8 @@ --- title: 切换 +icon: toggle-on --- - diff --git a/packages/twenty-docs/l/zh/twenty-ui/introduction.mdx b/packages/twenty-docs/l/zh/twenty-ui/introduction.mdx index da0918892fa..7d195f1cbbd 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/introduction.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/introduction.mdx @@ -1,5 +1,6 @@ --- title: 概览 +icon: 调色板 description: Twenty CRM 的组件库 --- diff --git a/packages/twenty-docs/l/zh/twenty-ui/navigation.mdx b/packages/twenty-docs/l/zh/twenty-ui/navigation.mdx index 6a3d83901cb..3f33cbe363a 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/navigation.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/navigation.mdx @@ -1,5 +1,6 @@ --- title: 导航 +icon: compass --- diff --git a/packages/twenty-docs/l/zh/twenty-ui/navigation/links.mdx b/packages/twenty-docs/l/zh/twenty-ui/navigation/links.mdx index eeed3a2840e..4e8c4dbee2f 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/navigation/links.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/navigation/links.mdx @@ -1,5 +1,6 @@ --- title: 链接 +icon: 链接 --- diff --git a/packages/twenty-docs/l/zh/twenty-ui/navigation/menu-item.mdx b/packages/twenty-docs/l/zh/twenty-ui/navigation/menu-item.mdx index 5c4ff71d264..49642512714 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/navigation/menu-item.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/navigation/menu-item.mdx @@ -1,8 +1,8 @@ --- title: 菜单项 +icon: bars --- - 一个多用途的菜单项,设计用于菜单或导航列表中。 diff --git a/packages/twenty-docs/l/zh/twenty-ui/navigation/navigation-bar.mdx b/packages/twenty-docs/l/zh/twenty-ui/navigation/navigation-bar.mdx index ec832c3fae8..0cf18fef1af 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/navigation/navigation-bar.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/navigation/navigation-bar.mdx @@ -1,8 +1,8 @@ --- title: 导航栏 +icon: bars --- - 渲染一个包含多个`NavigationBarItem`组件的导航栏。 diff --git a/packages/twenty-docs/l/zh/twenty-ui/progress-bar.mdx b/packages/twenty-docs/l/zh/twenty-ui/progress-bar.mdx index 1e1fdc47d3a..0b6db210be8 100644 --- a/packages/twenty-docs/l/zh/twenty-ui/progress-bar.mdx +++ b/packages/twenty-docs/l/zh/twenty-ui/progress-bar.mdx @@ -2,7 +2,6 @@ title: 反馈 --- - 表示进度或倒计时,并从右向左移动。 diff --git a/packages/twenty-docs/l/zh/user-guide/billing/capabilities/credits.mdx b/packages/twenty-docs/l/zh/user-guide/billing/capabilities/credits.mdx index 3093eec18f7..7d5dd28cf66 100644 --- a/packages/twenty-docs/l/zh/user-guide/billing/capabilities/credits.mdx +++ b/packages/twenty-docs/l/zh/user-guide/billing/capabilities/credits.mdx @@ -1,34 +1,41 @@ --- -title: 工作流程积分 -description: 了解工作流程积分、消耗情况,以及如何购买更多。 +title: 积分 +description: 了解积分如何为工作流程、AI 代理和 AI 聊天机器人提供支持—以及如何管理您的积分余额。 --- ## 概览 -在 Twenty 中,积分为您的工作流程自动化提供支持。 每个工作流程操作会根据其复杂性消耗积分。 +积分为工作流程自动化和 AI 功能提供支持。 当工作流程操作执行时、当 AI 代理在工作流程内处理任务时,以及在 AI 聊天中,都会消耗积分。 ## 积分分配 积分以您的计费周期为准,而非您的计划: -| 计费周期 | 积分 | -| ---- | ------- | -| 每月 | 500万/月 | -| 年度计划 | 5000万/年 | +| 计费周期 | 积分 | +| ---- | ---- | +| 每月 | 5/月 | +| 每年 | 50/年 | -每月500万积分旨在让您无需担心成本即可运行自动化。 对于使用标准操作的大多数工作流程,这已经绰绰有余。 仅在运行高级代码节点或由 AI 驱动的功能时,您才需要额外积分。 +每月的 5 个积分旨在让您无需担心成本即可运行自动化。 对于使用标准操作的大多数工作流,这已经绰绰有余。 仅在运行高级代码节点或由 AI 驱动的功能时,您才需要额外积分。 +## 积分结转 + +计费周期结束时未使用的积分会自动结转到下一个周期。 + +* **上限**:结转额度以一个计费周期的完整配额为上限,因此您结转的积分不会超过您的套餐每个周期提供的积分数量。 +* **可见性**:当结转积分可用时,它们会在**设置 → 账单**中显示为单独的**结转积分**一行,并与**可用总额**余额并列。 + ## 积分消耗 不同的操作会消耗不同数量的积分: -| 操作类型 | 信用使用情况 | +| 操作类型 | 积分使用情况 | | ------------------------- | -------- | | **基础操作** (搜索、更新、创建记录) | 极少 | | **复杂操作** (代码节点、外部 API 调用) | 更多积分 | -| **人工智能提示** (即将推出) | 根据使用情况而定 | +| **AI 提示词 & AI 聊天** | 根据使用情况而定 | 当工作流执行时,积分会实时扣除。 diff --git a/packages/twenty-docs/l/zh/user-guide/billing/how-tos/billing-faq.mdx b/packages/twenty-docs/l/zh/user-guide/billing/how-tos/billing-faq.mdx index d999ed54b3a..1862fefed98 100644 --- a/packages/twenty-docs/l/zh/user-guide/billing/how-tos/billing-faq.mdx +++ b/packages/twenty-docs/l/zh/user-guide/billing/how-tos/billing-faq.mdx @@ -44,8 +44,8 @@ description: 关于 Twenty 定价和账单的常见问题。 积分以您的计费周期为准,而非您的计划: -* **按月订阅**:每月500万积分 -* **按年订阅**:每年5000万积分 +* **按月订阅**:每月5积分 +* **按年订阅**:每年50积分 diff --git a/packages/twenty-docs/l/zh/user-guide/billing/overview.mdx b/packages/twenty-docs/l/zh/user-guide/billing/overview.mdx index 49145788c0c..fc8de87386a 100644 --- a/packages/twenty-docs/l/zh/user-guide/billing/overview.mdx +++ b/packages/twenty-docs/l/zh/user-guide/billing/overview.mdx @@ -3,7 +3,6 @@ title: 账单 description: 了解 Twenty 的定价并管理你的订阅。 --- - Twenty 提供灵活的定价方案,满足你的团队需求。 你可以在 **设置 → 账单** 中管理订阅、跟踪工作流额度并访问发票。 ## 本节内容 diff --git a/packages/twenty-docs/l/zh/user-guide/calendar-emails/overview.mdx b/packages/twenty-docs/l/zh/user-guide/calendar-emails/overview.mdx index 869a12cd4b6..c63048a2c7c 100644 --- a/packages/twenty-docs/l/zh/user-guide/calendar-emails/overview.mdx +++ b/packages/twenty-docs/l/zh/user-guide/calendar-emails/overview.mdx @@ -3,7 +3,6 @@ title: 日历与电子邮件 description: 将您的电子邮件和日历账户连接到 Twenty。 --- - ## 连接选项 ### Google账号(Gmail和Google日历) diff --git a/packages/twenty-docs/l/zh/user-guide/dashboards/overview.mdx b/packages/twenty-docs/l/zh/user-guide/dashboards/overview.mdx index f91f08aa3f6..327fcfba9f6 100644 --- a/packages/twenty-docs/l/zh/user-guide/dashboards/overview.mdx +++ b/packages/twenty-docs/l/zh/user-guide/dashboards/overview.mdx @@ -3,7 +3,6 @@ title: 仪表板 description: 了解 Twenty 中报表和仪表板的基础知识。 --- - 仪表板目前处于测试版阶段。 在 **设置 → 更新 → 早期访问** 中启用它们。 diff --git a/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/fix-import-errors.mdx b/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/fix-import-errors.mdx index cf45b8094dd..84b4a9a49fb 100644 --- a/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/fix-import-errors.mdx +++ b/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/fix-import-errors.mdx @@ -140,12 +140,12 @@ description: 解决 CSV 导入错误的完整故障排除指南。 #### 日期 **问题:** 无法识别的日期格式 -**解决方案:** 在整个文件中使用一致的格式 +**解决方案:** 在整个文件中一致使用 `YYYY-MM-DD` 格式 ``` -✓ 2024-03-15 (YYYY-MM-DD - recommended) -✓ 03/15/2024 (MM/DD/YYYY) -✓ 15/03/2024 (DD/MM/YYYY) +✓ 2024-03-15 (YYYY-MM-DD) +❌ 03/15/2024 (MM/DD/YYYY) +❌ 15/03/2024 (DD/MM/YYYY) ``` #### 电话 diff --git a/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/prepare-your-csv-files.mdx b/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/prepare-your-csv-files.mdx index 1b69ac0366d..b76574d709c 100644 --- a/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/prepare-your-csv-files.mdx +++ b/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/prepare-your-csv-files.mdx @@ -103,8 +103,6 @@ Twenty 会对某些字段强制唯一性。 重复项会导致导入错误。 在整个文件中使用一致的格式: * `YYYY-MM-DD`(推荐):`2024-03-15` -* `MM/DD/YYYY`:`03/15/2024` -* `DD/MM/YYYY`:`15/03/2024` * ISO 8601:`2024-03-15T10:30:00Z` ### 数字字段 diff --git a/packages/twenty-docs/l/zh/user-guide/data-migration/overview.mdx b/packages/twenty-docs/l/zh/user-guide/data-migration/overview.mdx index 4227faf1dcd..d7fbbdc195d 100644 --- a/packages/twenty-docs/l/zh/user-guide/data-migration/overview.mdx +++ b/packages/twenty-docs/l/zh/user-guide/data-migration/overview.mdx @@ -5,7 +5,6 @@ description: 通过 CSV 文件或 API 导入和导出您的 CRM 数据。 import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## 导入方法 Twenty 支持两种主要的数据导入方法: diff --git a/packages/twenty-docs/l/zh/user-guide/data-model/overview.mdx b/packages/twenty-docs/l/zh/user-guide/data-model/overview.mdx index 455fde335a2..d8947f9a84c 100644 --- a/packages/twenty-docs/l/zh/user-guide/data-model/overview.mdx +++ b/packages/twenty-docs/l/zh/user-guide/data-model/overview.mdx @@ -3,7 +3,6 @@ title: 数据模型 description: 了解什么是数据模型,以及如何设计一个适合您业务的模型。 --- - ## 什么是数据模型? 数据模型是定义信息在您的CRM中如何组织的结构。 可以把它看作您的客户数据的**蓝图** — 您只需设计一次,然后用实际数据将其填充。 diff --git a/packages/twenty-docs/l/zh/user-guide/introduction.mdx b/packages/twenty-docs/l/zh/user-guide/introduction.mdx index 24e80c3e3bd..d1af61cbf06 100644 --- a/packages/twenty-docs/l/zh/user-guide/introduction.mdx +++ b/packages/twenty-docs/l/zh/user-guide/introduction.mdx @@ -1,16 +1,11 @@ --- -title: 发现 Twenty +title: 用户指南 description: 欢迎使用 Twenty 用户指南,这是您获取高级配置和最佳实践的资源。 --- import { CardTitle } from "/snippets/card-title.mdx" - - 发现 Twenty - 了解 Twenty 是什么,以及它如何助力您的业务。 - - 数据模型 定制您的数据模型,使其适配您的业务流程。 @@ -36,9 +31,9 @@ import { CardTitle } from "/snippets/card-title.mdx" 使用 AI 代理提升您团队的能力。 - - 视图与管道 - 使用可操作的视图和管道来组织您的数据。 + + Layout + Navigation, views, and record page customization. diff --git a/packages/twenty-docs/l/zh/user-guide/layout/capabilities/navigation.mdx b/packages/twenty-docs/l/zh/user-guide/layout/capabilities/navigation.mdx new file mode 100644 index 00000000000..8d6e59f0507 --- /dev/null +++ b/packages/twenty-docs/l/zh/user-guide/layout/capabilities/navigation.mdx @@ -0,0 +1,32 @@ +--- +title: 导航 +description: Customize the left sidebar to match how your team works. +--- + +The left sidebar is your primary way to move around Twenty. It's fully customizable — you can reorganize it to match your workflow without touching any settings page. + +## Reordering items + +Drag and drop any item in the sidebar to change its position. The order is saved per user, so each team member can arrange their own sidebar. + +## 文件夹 + +Group related items into folders. For example, you might create a "Sales" folder containing your pipeline views, a "Support" folder for tickets, or an "Operations" folder for internal objects. + +To create a folder, right-click in the sidebar or use the `+` button. + +## Hiding objects + +Objects you don't use can be hidden from the sidebar. They're not deleted — they're just out of the way. You can show them again anytime from Settings > Data Model. + +## 收藏夹 + +Pin views, records, or searches to the Favorites section at the top of the sidebar for one-click access. Favorites are personal — each user manages their own. + +## Custom links + +Add links to external tools directly in the sidebar. Useful for linking to your wiki, dashboards in other tools, or any URL your team uses regularly. + +## Command menu + +Press `Cmd+K` (or `Ctrl+K`) to open the command menu — a quick-access search bar for jumping to any record, view, or action without navigating the sidebar. diff --git a/packages/twenty-docs/l/zh/user-guide/layout/capabilities/record-pages.mdx b/packages/twenty-docs/l/zh/user-guide/layout/capabilities/record-pages.mdx new file mode 100644 index 00000000000..32927217cf9 --- /dev/null +++ b/packages/twenty-docs/l/zh/user-guide/layout/capabilities/record-pages.mdx @@ -0,0 +1,51 @@ +--- +title: 记录页面 +description: 使用选项卡和小部件自定义单个记录详情页的布局。 +--- + +当你在 Twenty 中打开一条记录时,详情页由**选项卡**和**小部件**组成。 两者都可按对象类型完全自定义。 + +## 选项卡 + +每个记录页面可以包含多个选项卡—类似于浏览器中的选项卡。 使用它们来组织记录的不同方面。 例如,一个公司记录可能包含“概览”、“沟通”、“任务”和“文件”等选项卡。 + +您可以: + +* 添加和删除选项卡 +* 重命名选项卡 +* 通过拖动重新排序选项卡 +* 设置默认显示的选项卡 + +## 小部件 + +小部件是每个选项卡内的构建模块。 可用的小部件类型包括: + +| 小部件 | 显示内容 | +| ---------- | -------------- | +| **字段** | 记录字段,可分组或单独显示 | +| **相关记录** | 通过关系关联的记录表 | +| **电子邮件** | 来自已连接账户的电子邮件历史 | +| **日历** | 与该记录关联的日历事件 | +| **时间轴** | 活动与事件历史 | +| **任务** | 关联的任务 | +| **备注** | 富文本备注 | +| **文件** | 文件附件 | +| **图表** | 来自相关记录的可视化数据 | +| **iFrame** | 嵌入的外部内容 | +| **富文本** | 静态内容或说明 | + +## 自定义记录页面 + +1. 打开任意记录 +2. 按下 `Cmd+K`,搜索“编辑记录页面布局” +3. 你现在处于自定义模式: + * **添加小部件**,使用小部件选择器 + * **拖动小部件**,在网格上重新定位它们 + * **调整小部件大小**,通过拖动其边缘 + * **配置字段**,设置每个小部件内显示的字段 + * **管理选项卡** — 添加、删除、重命名、重新排序 +4. 保存你的更改—它们将应用于该对象类型的所有记录 + +## 字段可见性 + +在“字段”小部件中,你可以控制哪些字段可见以及它们的顺序。 这使你可以创建更聚焦的布局—例如,在“概览”选项卡中仅显示最重要的字段,并将详细字段放在单独的选项卡中。 diff --git a/packages/twenty-docs/l/zh/user-guide/layout/overview.mdx b/packages/twenty-docs/l/zh/user-guide/layout/overview.mdx new file mode 100644 index 00000000000..913e344f425 --- /dev/null +++ b/packages/twenty-docs/l/zh/user-guide/layout/overview.mdx @@ -0,0 +1,45 @@ +--- +title: 布局 +description: Customize how you navigate, browse, and view records in Twenty. +--- + +Twenty's layout is customizable at three levels: how you navigate the app, how you browse lists of records, and what you see when you open an individual record. + +## 导航 + +The left sidebar is fully customizable. 您可以: + +* **Reorder items** by dragging and dropping +* **Create folders** to group related objects and views +* **Hide objects** you don't use +* **Add custom links** to external tools +* **Pin favorites** for quick access to views, records, or searches + +[Navigation reference →](/l/zh/user-guide/layout/capabilities/navigation) + +## 视图 + +Views control how lists of records are displayed. Twenty supports three view types: + +| 视图 | Best for | +| ------------ | ---------------------------------------------------------------------- | +| **Table** | Working with many records at once — spreadsheet-style rows and columns | +| **Kanban** | Pipeline tracking — drag-and-drop cards organized by stage | +| **Calendar** | Time-based planning — records plotted by a date field | + +Each view saves its own filters, sorting, field visibility, and grouping configuration. Views can be shared with the workspace or kept private. + +[Table views →](/l/zh/user-guide/views-pipelines/capabilities/table-views) · [Kanban views →](/l/zh/user-guide/views-pipelines/capabilities/kanban-views) · [Calendar view →](/l/zh/user-guide/views-pipelines/capabilities/calendar-view) + +## Record pages + +When you open a record, the detail page is built from configurable tabs and widgets. 您可以: + +* **Add, remove, and reorder tabs** on any record type +* **Configure widgets** — fields, related records, emails, timeline, calendar, tasks, notes, files, charts, iframes, and more +* **Drag and resize widgets** on a grid layout +* **Control field visibility** per widget + +Enter layout customization mode from the command menu (`Cmd+K` → "Edit record page layout"). + +[Record pages reference →](/l/zh/user-guide/layout/capabilities/record-pages) diff --git a/packages/twenty-docs/l/zh/user-guide/permissions-access/overview.mdx b/packages/twenty-docs/l/zh/user-guide/permissions-access/overview.mdx index 9eb45af612f..d2f4e49c24c 100644 --- a/packages/twenty-docs/l/zh/user-guide/permissions-access/overview.mdx +++ b/packages/twenty-docs/l/zh/user-guide/permissions-access/overview.mdx @@ -3,7 +3,6 @@ title: 权限与访问 description: 在您的工作区中管理角色、权限和访问控制。 --- - Twenty 的权限系统使您能够控制谁可以在您的工作区中访问和修改数据。 创建角色、分配权限,并配置 SSO 以实现安全访问。 ## 本节内容 diff --git a/packages/twenty-docs/l/zh/user-guide/settings/overview.mdx b/packages/twenty-docs/l/zh/user-guide/settings/overview.mdx index 40c9821c8ec..6bafa0d887c 100644 --- a/packages/twenty-docs/l/zh/user-guide/settings/overview.mdx +++ b/packages/twenty-docs/l/zh/user-guide/settings/overview.mdx @@ -3,7 +3,6 @@ title: 设置 description: 通过关键配置来设置您的 Twenty 工作区。 --- - ## 初始设置 首次创建工作区时,需要配置一些关键设置。 diff --git a/packages/twenty-docs/l/zh/user-guide/views-pipelines/overview.mdx b/packages/twenty-docs/l/zh/user-guide/views-pipelines/overview.mdx index 2fdf22df3ff..9864347c448 100644 --- a/packages/twenty-docs/l/zh/user-guide/views-pipelines/overview.mdx +++ b/packages/twenty-docs/l/zh/user-guide/views-pipelines/overview.mdx @@ -5,7 +5,6 @@ description: 了解如何在 Twenty 中创建和管理视图。 import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; - ## 了解视图 视图是已保存的配置,用于决定数据的显示方式。 每个视图都可以有: diff --git a/packages/twenty-docs/l/zh/user-guide/workflows/capabilities/workflow-credits.mdx b/packages/twenty-docs/l/zh/user-guide/workflows/capabilities/workflow-credits.mdx index 81f0a9e1f45..4703994a01b 100644 --- a/packages/twenty-docs/l/zh/user-guide/workflows/capabilities/workflow-credits.mdx +++ b/packages/twenty-docs/l/zh/user-guide/workflows/capabilities/workflow-credits.mdx @@ -9,13 +9,13 @@ description: 了解工作流程积分的消耗与管理。 工作流程积分基于您的计费周期分配,而非您的计划: -| 计费周期 | 积分 | -| -------- | ----------- | -| **月度订阅** | 每月 500 万积分 | -| **年度订阅** | 每年 5000 万积分 | +| 计费周期 | 积分 | +| -------- | -------- | +| **月度订阅** | 每月 5 积分 | +| **年度订阅** | 每年 50 积分 | -每月 500 万积分对于标准自动化而言已相当充足。 大多数团队在典型的工作流程使用中不会超出此限制。 额外积分主要用于高级 Code 操作和由 AI 驱动的工作流程。 +每月 5 积分对于标准自动化而言已相当充足。 大多数团队在典型的工作流程使用中不会超出此限制。 额外积分主要用于高级 Code 操作和由 AI 驱动的工作流程。 ## 积分消耗工作原理 diff --git a/packages/twenty-docs/l/zh/user-guide/workflows/overview.mdx b/packages/twenty-docs/l/zh/user-guide/workflows/overview.mdx index b11cbe89b06..ad30e62989a 100644 --- a/packages/twenty-docs/l/zh/user-guide/workflows/overview.mdx +++ b/packages/twenty-docs/l/zh/user-guide/workflows/overview.mdx @@ -3,7 +3,6 @@ title: 工作流 description: 了解如何在 Twenty 中构建自动化流程。 --- - ## 为何工作流很重要 Twenty的设计宗旨是为用户提供最大的灵活性。 工作流可以让您自行构建自动化功能,打造最能支持您独特业务用例的CRM,而不是强迫您将业务流程迁就于僵化的预设功能。