snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
---
title: API'ler
icon: plug
description: Çalışma alanı şemanızdan oluşturulan REST ve GraphQL API'leri.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
## Kiracı başına şema API'leri
Twenty için statik bir API referansı yoktur. Her çalışma alanının kendine ait bir şeması vardır — özel bir nesne (ör. `Invoice`) eklediğinizde, `Company` veya `Person` gibi yerleşik nesnelerle aynı olan REST ve GraphQL uç noktalarını anında edinir. API şemadan oluşturulur, bu nedenle uç noktalar nesne ve alan adlarınızı doğrudan kullanır — anlaşılmaz kimlikler yoktur.
Bir API anahtarı oluşturduktan sonra çalışma alanınıza özel API dokümantasyonu **Ayarlar → API ve Webhook'lar** altında mevcuttur. Verilerinize karşı gerçek çağrılar çalıştırabileceğiniz etkileşimli bir deneme alanı içerir.
## İki API
**Çekirdek API** — `/rest/` ve `/graphql/`
Kayıtlar üzerinde CRUD: Kişiler, Şirketler, Fırsatlar ve özel nesneleriniz. Sorgulama, filtreleme, ilişkilerde gezinme.
**Meta veri API'si** — `/rest/metadata/` ve `/metadata/`
Şema yönetimi: nesneleri, alanları ve ilişkileri oluşturma/değiştirme/silme. Programatik olarak veri modelinizi bu şekilde değiştirirsiniz.
Her ikisi de REST ve GraphQL olarak mevcuttur. GraphQL, toplu upsert işlemleri ve ilişkilerde tek bir sorguda gezinme olanağı sağlar. Her iki yöntemde de aynı temel veri kullanılır.
## Temel URL'ler
| Ortam | Temel URL |
| ------------------ | ------------------------- |
| Bulut | `https://api.twenty.com/` |
| Kendi barındırmalı | `https://{your-domain}/` |
## Kimlik Doğrulama
```
Authorization: Bearer YOUR_API_KEY
```
**Settings → API & Webhooks → + Create key** bölümünde bir API anahtarı oluşturun. Hemen kopyalayın — yalnızca bir kez gösterilir. Anahtarlar, erişebilecekleri alanları sınırlamak için **Settings → Members → Roles → Assignment** sekmesi altında belirli bir role bağlanabilir.
<VimeoEmbed videoId="928786722" title="API anahtarı oluşturma" />
OAuth tabanlı erişim (kullanıcılar adına hareket eden harici uygulamalar) için bkz. [OAuth](/l/tr/developers/extend/oauth).
## Toplu işlemler
Hem REST hem de GraphQL, istek başına 60 kayda kadar toplu işlemeyi destekler — oluşturma, güncelleme veya silme. GraphQL ayrıca `CreateCompanies` gibi çoğul adlar kullanarak toplu upsert'i (tek çağrıda oluşturma veya güncelleme) destekler.
## Hız sınırları
| Sınır | Değer |
| ------------ | --------------------- |
| İstekler | Dakikada 100 |
| Toplu boyutu | Çağrı başına 60 kayıt |

View File

@@ -0,0 +1,124 @@
---
title: Uygulama Yapılandırması
description: Uygulamanızın kimliğini, varsayılan rolünü, değişkenlerini ve pazaryeri meta verilerini `defineApplication` ile bildirin.
icon: rocket
---
Her uygulamanın, tam olarak bir adet `defineApplication` çağrısı olması gerekir. Bu çağrı şunları bildirir:
* **Kimlik** — evrensel tanımlayıcı, görünen ad, açıklama.
* **İzinler** — mantık fonksiyonlarının ve ön uç bileşenlerinin hangi rol altında çalıştığı.
* **Değişkenler** *(isteğe bağlı)* — kodunuza ortam değişkenleri olarak sunulan anahtar–değer çiftleri.
* **Kurulum öncesi / kurulum sonrası / kaldırma kancaları** *(isteğe bağlı)* — bkz. [Mantık Fonksiyonları](/l/tr/developers/extend/apps/logic/logic-functions).
```ts src/application-config.ts
import { defineApplication } from 'twenty-sdk/define';
export default defineApplication({
universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d',
displayName: 'My Twenty App',
description: 'My first Twenty app',
applicationVariables: {
DEFAULT_RECIPIENT_NAME: {
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
description: 'Default recipient name for postcards',
value: 'Jane Doe',
isSecret: false,
},
},
});
```
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. Mantık fonksiyonlarında (sunucu tarafında), `process.env.VARIABLE_NAME` olarak kullanılabilirler. Ön bileşenlerde, `twenty-sdk/front-component` içindeki `getApplicationVariable('VARIABLE_NAME')` fonksiyonunu kullanın. `isSecret: true` ile işaretlenen değişkenler yalnızca mantık fonksiyonlarına enjekte edilir. Ön bileşenler yalnızca gizli olmayan değişkenleri alır.
* Varsayılan rol, [`defineApplicationRole()`](/l/tr/developers/extend/apps/config/roles) ile işaretlenmiş rol dosyasından otomatik olarak algılanır — onu `defineApplication()` içinden belirtmenize gerek yoktur.
* Kurulum öncesi, kurulum sonrası ve kaldırma fonksiyonları manifest derlemesi sırasında otomatik olarak algılanır — bunlara `defineApplication()` içinde referans vermeniz gerekmez.
* Geriye dönük uyumluluk için `defaultRoleUniversalIdentifier` değerini açıkça geçmek hâlâ desteklenmektedir, ancak `defineApplicationRole()` lehine kullanımdan kaldırılmıştır.
* `serverVariables`, API anahtarları gibi örnek düzeyindeki yapılandırmalar ve gizli bilgilerdir. `applicationVariables`'ın aksine, manifest içinde herhangi bir değer belirtmezler — çalışma alanı operatörü bunları uygulamanın ayarlarından doldurur ve yalnızca ayarlandıklarında mantık fonksiyonlarına enjekte edilirler.
* Her iki tür değişken de `isDeprecated: true` kabul eder. Bir değişkeni silmek yerine devre dışı bırakmak için kullanın: anahtarın tanımlı kalması, saklanan değeri korur (silmek, operatörün girdiği değeri yok eder) ve değişken hâlâ enjekte edilir, böylece kodunuz buna geri dönebilir — `process.env.NEW_API_KEY ?? process.env.API_KEY`. Artık kullanılmayan bir değişkenin değeri kalmadığında uygulamanın ayarlarından kaybolur ve hiçbir zaman uygulamanın yapılandırma kontrolüne dahil edilmez; bu yüzden `isDeprecated`, `isRequired` seçeneğine üstün gelir.
* Uygulamanın **Ayarlar** sekmesinde (varsayılan değişken yapılandırma bölümünün yerine) özel bir yapılandırma arayüzü oluşturmak için, kendi dosyasında [`defineSettingsFrontComponent()`](/l/tr/developers/extend/apps/layout/front-components#custom-settings-component) ile bir ön bileşen tanımlayın. Her uygulama için yalnızca bir tane olabilir. Sistem tarafından yönetilen bölümler (otomatik yükseltme, App URL, bağlantılar) her zaman görünür durumda kalır.
## Değişken türleri
Hem `applicationVariables` hem de `serverVariables`, isteğe bağlı bir `type` (ve `SELECT` / `MULTI_SELECT` için bir `options` listesi) kabul eder. Desteklenen türler: `TEXT` (varsayılan), `BOOLEAN`, `NUMBER`, `NUMERIC`, `DATE`, `DATE_TIME`, `SELECT`, `MULTI_SELECT`, `ARRAY`, `RAW_JSON`, `RICH_TEXT`.
```ts src/application-config.ts
import { defineApplication, FieldType } from 'twenty-sdk/define';
export default defineApplication({
// ...identity, role...
applicationVariables: {
MAX_POSTCARDS: {
universalIdentifier: '5f4497e4-9030-4085-85eb-2c48b8d53713',
description: 'Maximum postcards per batch',
type: FieldType.NUMBER,
value: 10,
},
DEFAULT_REGION: {
universalIdentifier: '76c5c321-b6b6-46eb-b4fc-f9f04bb04227',
description: 'Default shipping region',
type: FieldType.SELECT,
options: [
{ label: 'Europe', value: 'eu' },
{ label: 'United States', value: 'us' },
],
value: 'eu',
},
},
});
```
`type` yalnızca **görünümü ve doğrulamayı** etkiler — çalışma alanı ayarları arayüzünde eşleşen girdiyi seçer (açma/kapama düğmesi, sayı alanı, açılır liste, tarih seçici, JSON düzenleyici, …) ve derlemenin yapılandırmanızı doğrulamasını sağlar (örneğin, `SELECT` / `MULTI_SELECT` boş olmayan `options` bildirmelidir). Değerin kodunuza nasıl ulaştığını **değiştirmez**.
Değerler **her zaman string olarak eklenir** — bu, ortam değişkenlerinin doğasında vardır (`process.env.*` yalnızca string kabul eder). Mantık fonksiyonunuz çalıştığında, yürütücü her bir değeri `process.env` oluşturulurken bildirilen `type`'a göre serileştirir; böylece değerin nasıl ayarlandığından (manifest varsayılanı, ayarlar arayüzü veya önceki bir sürüm) bağımsız olarak string biçimi tutarlı olur:
| Tür | `process.env` string |
| ------------------------------------- | -------------------------------------- |
| `TEXT`, `SELECT`, `DATE`, `DATE_TIME` | ham değer (`"eu"`, `"2026-01-01"`) |
| `BOOLEAN` | `"true"` / `"false"` |
| `NUMBER`, `NUMERIC` | ondalık string (`"10"`, `"2.5"`) |
| `MULTI_SELECT`, `ARRAY` | JSON dizisi (`'["email","postcard"]'`) |
| `RAW_JSON`, `RICH_TEXT` | JSON nesnesi (`'{"retries":3}'`) |
String'i beklediğiniz türe geri ayrıştırın:
```ts
const maxCards = Number(process.env.MAX_POSTCARDS); // "10" -> 10
const enabled = process.env.ENABLE_TRACKING === 'true'; // "true" -> true
const channels = JSON.parse(process.env.ENABLED_CHANNELS ?? '[]'); // '["email"]' -> ["email"]
const config = JSON.parse(process.env.PROVIDER_CONFIG ?? '{}'); // '{"retries":3}' -> { retries: 3 }
```
Aynısı, `getApplicationVariable('VARIABLE_NAME')` ile değerleri okuyan ön bileşenler için de geçerlidir — döndürülen değer bir stringtir; gerektiği gibi ayrıştırın.
## Varsayılan fonksiyon rolü
[`defineApplicationRole()`](/l/tr/developers/extend/apps/config/roles) ile bildirilen rol, uygulamanın iş mantığı işlevlerinin ve ön uç bileşenlerinin neye erişebileceğini kontrol eder:
* Mantık işlevlerinize enjekte edilen çalışma zamanı belirteçleri bu rolden türetilir. Bir kişi olarak davranan çağrı, o kişinin yapabilecekleriyle daha da sınırlandırılır; dolayısıyla hiçbir zaman ikisinden birini aşamaz. Bkz. [Bir çağrının kullandığı erişim](/l/tr/developers/extend/apps/logic/logic-functions#whose-access-a-call-uses).
* Türlendirilmiş API istemcisi, 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 izinleri bildirin.
Yeni bir uygulama iskeleti oluşturduğunuzda, CLI `src/roles/default-role.ts` konumunda bir başlangıç rol dosyası oluşturur. Tam başvuru için bkz. [Roller ve İzinler](/l/tr/developers/extend/apps/config/roles).
## Pazaryeri meta verileri
Eğer [uygulamanızı yayımlamayı](/l/tr/developers/extend/apps/operations/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 |
| `logo` | Uygulamanızın `public/` klasöründe paketlenmiş logo dosyasının yolu (örn. `public/logo.png`) |
| `galleryImages` | `public/` içinde paketlenmiş galeri görseli 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ı |
<Note>
`logoUrl` ve `screenshots`, `logo` ve `galleryImages` için kullanımdan kaldırılmış takma adlardır. Bu alanlar için harici mutlak URL'ler (`http://` veya `https://`) desteklenmez: derleme sırasında uyarı ile birlikte kaldırılırlar. Bunun yerine görselleri uygulamanızın `public/` klasöründe paketleyin.
</Note>

View File

@@ -0,0 +1,188 @@
---
title: Kurulum Kancaları
description: Kurulum, yükseltme veya kaldırma yaşam döngüsü sırasında mantık çalıştırın — başlangıç verilerini yükleyin, kayıtları yedekleyin, yükseltmeyi doğrulayın, harici kaynakları temizleyin.
icon: wrench
---
Kurulum kancaları, kurulum, yükseltme veya kaldırma yaşam döngüsü sırasında çalışan özel mantık işlevleridir. Bunlar, normal [mantık işlevleri](/l/tr/developers/extend/apps/logic/logic-functions) ile aynı işleyici çalışma zamanını paylaşır, ancak kendi tanımlama işlevleriyle bildirilirler ve normal tetikleyici modelinin (HTTP, cron, veritabanı olayları) dışında yaşarlar. Kurulum kancaları bir `InstallPayload` alır (`{ previousVersion?: string; newVersion: string }` — yeni bir kurulumda `previousVersion`, `undefined` olur); kaldırma kancası ise bir `UninstallPayload` alır (`{ version?: string }` — kaldırılan sürüm).
Her uygulama, her bir kanca türünden (kurulum öncesi, kurulum sonrası, kaldırma) **en fazla bir tane** tanımlayabilir. Her türden birden fazla tespit edilirse manifest derlemesi hata verir.
```
┌─────────────────────────────────────────────────────────────┐
│ install flow │
│ │
│ upload package → [pre-install] → metadata migration → │
│ generate SDK → [post-install] │
│ │
│ old schema visible new schema visible │
└─────────────────────────────────────────────────────────────┘
```
## Bir bakışta
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
| ---------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| Çalışma zamanı | Üstveri geçişinden önce — **önceki** şema ve veriler hâlâ sağlamdır | Geçişten ve SDK oluşturmasından sonra — **yeni** şema yürürlüktedir |
| Yürütme | Her zaman senkron; kurulumu bloke eder | Varsayılan olarak asenkron (kuyruğa alınır, 3 yeniden deneme); `shouldRunSynchronously: true` ile isteğe bağlı senkron |
| Başarısızlık durumunda | Kurulum, herhangi bir şema değişikliğinden önce **iptal edilir** | Asenkron: en fazla 3 kez yeniden denenir. Senkron: çağıran `POST_INSTALL_ERROR` alır (şema değişiklikleri **geri alınmaz**) |
| Tipik kullanım | Bir geçişin kaybedeceği verileri yedeklemek veya düzeltmek; hata fırlatarak riskli bir yükseltmeyi reddetmek | Varsayılan verileri tohumlamak, çalışma alanını yapılandırmak, harici kaynakları kaydetmek |
**Kural:** varsayılan olarak post-install kullanın. Yalnızca geçişin kendisi yıkıcıysa ve önceki durum yok olmadan önce onu yakalamanız gerekiyorsa kurulum öncesine başvurun.
| Şunu yapmak istiyorsunuz... | Kullan |
| --------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Verileri tohumlamak, çalışma alanını yapılandırmak, harici kaynakları kaydetmek | `post-install` |
| Kurulum yanıtını engellememesi gereken uzun süreli işleri yürütmek | `post-install` (varsayılan asenkron mod, worker yeniden denemeleriyle) |
| Kurulum döndükten hemen sonra çağıranın güveneceği hızlı kurulumu gerçekleştirmek | `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 | `shouldRunOnVersionUpgrade: true` ile her iki kancadan biri |
## Her iki kanca tarafından paylaşılan davranış
* Yapılandırma, tetikleyici ayarları çıkarılmış bir `defineLogicFunction` yapılandırmasıdır ve buna ek olarak `shouldRunOnVersionUpgrade` içerir.
* **Ne zaman çalışır**: varsayılan olarak yalnızca ilk kurulumlarda. Yükseltmelerde de çalışması için `shouldRunOnVersionUpgrade: true` olarak ayarlayın. Yükseltme yoluna göre dallanmak için `previousVersion` / `newVersion` kullanın.
* **İdempotans önemlidir**: asenkron post-install yeniden denenebilir ve `shouldRunOnVersionUpgrade` açıkken her iki kanca da yükseltmelerde yeniden çalıştırılır.
* Alışıldık mantık işlevi ortamı (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) enjekte edilir, böylece Twenty API'sini uygulamanızın jetonuyla çağırabilirsiniz.
* Kanca, derleme zamanında otomatik olarak uygulama manifestine (`preInstallLogicFunction` / `postInstallLogicFunction`) eklenir — [`defineApplication()`](/l/tr/developers/extend/apps/config/application) içinde referans verilecek bir şey yoktur.
* Varsayılan `timeoutSeconds`, veri tohumlama gibi daha uzun kurulum görevlerine izin vermek için 300 olarak ayarlanmıştır.
* **Geliştirme modunda yürütülmez**: `yarn twenty dev` kurulum akışını atlar ve dosyaları doğrudan senkronize eder, bu nedenle kancalar burada asla çalışmaz. Bunları bunun yerine manuel olarak tetikleyin:
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Çalışma alanı üstveri (metadata) geçişi uygulanıp tamamlandıktan sonra çalışır">
Uygulamanızın kurulumu tamamlandıktan sonra bir kez çalışır: üstveri senkronize edilmiştir, SDK istemcisi oluşturulmuştur, yeni şema sorgulanabilir durumdadır. Örnek — ilk kurulumlarda varsayılan bir kaydı tohumlamak:
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
if (previousVersion) return; // fresh installs only
const client = new CoreApiClient();
await client.mutation({
createPostCard: {
__args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
id: true,
},
});
};
export default definePostInstallLogicFunction({
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
name: 'post-install',
description: 'Seeds a welcome post card after install.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
`shouldRunSynchronously` bayrağı yürütme modelini kontrol eder:
* `false` *(varsayılan)* — mesaj kuyruğuna alınır (`retryLimit: 3`) ve bir worker tarafından çalıştırılır. Kurulum yanıtı, iş kuyruğa alınır alınmaz döner. **Uzun süreli işler için kullanın** — büyük veri kümelerinin tohumlanması, yavaş üçüncü taraf API'leri.
* `true` — kurulum akışı sırasında satır içi olarak yürütülür. Kurulum isteği, işleyici bitene kadar bloke olur; fırlatılan bir hata, çağırana `POST_INSTALL_ERROR` olarak yansır (yeniden deneme yoktur). **Hızlı ve yanıt dönmeden önce mutlaka tamamlanması gereken işler için kullanın.** Bu noktada geçiş zaten uygulanmıştır, bu nedenle bir hata şema değişikliklerini geri almaz — yalnızca hatayı görünür kılar.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Çalışma alanı üstveri (metadata) geçişi uygulanmadan önce çalışır">
Üstveri geçişinden önce, **önceki** şemaya karşı çalışır — bir geçişin kaybedeceği verileri yedeklemek veya riskli bir yükseltmeyi reddetmek için doğru yerdir. Çalıştırmadan önce, sunucu yalnızca ekleyici bir "indirgenmiş eşitleme" yürütür; bu, yalnızca yeni sürümün kurulum öncesi işlevini kaydeder, diğer her şey — önceki sürümün nesneleri, alanları ve verileri — işleyiciniz çalıştığında dokunulmadan kalır.
Kurulum öncesi her zaman **senkron**dur ve kurulumu bloke eder. İşleyici bir hata fırlatırsa, herhangi bir şema değişikliğinden ö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.
Örnek — geçiş onu düşürmeden önce eski bir alanın değerlerini kopyalamak:
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) {
return;
}
const client = new CoreApiClient();
const { postCards } = await client.query({
postCards: {
__args: { filter: { notes: { isNot: null } } },
edges: { node: { id: true, notes: true } },
},
});
// Copy legacy `notes` into `description` before the migration drops the
// column. If this fails, the upgrade aborts and the workspace stays on v1.
for (const { node } of postCards.edges) {
await client.mutation({
updatePostCard: {
__args: { id: node.id, data: { description: node.notes } },
id: true,
},
});
}
};
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,
});
```
</Accordion>
</AccordionGroup>
## Kaldırma kancası
`defineUninstallLogicFunction`, bir kullanıcı uygulamanızı kaldırdığında çalışan bir kancayı bildirir. Bu kanca, uygulamanın üst verileri, verileri ve kodu kaldırılmadan **önce** çalışır — silme geçişi (migration) çalıştıktan sonra çalıştırılacak hiçbir şey kalmaz — bu nedenle işleyiciniz uygulamanın nesnelerini ve kayıtlarını hâlâ sorgulayabilir. Bunu harici kaynakların temizliği için kullanın: API kaynaklarının tahsisini geri alın, kalan botları silin, web kancalarını (webhook) iptal edin.
Notlar:
* Kanca, en iyi gayret esasına göre çalışır: senkron olarak çalışır, ancak bir hata günlüğe kaydedilir ve **kaldırmayı asla engellemez** — temizleme, bir uygulamanın kaldırılamaz hale gelmesine neden olmamalıdır.
* Kaldırma kancası, `UninstallPayload` alır (`{ version?: string }` — kaldırılan sürüm).
* Başarısız olan yeni bir kurulum geri alındığında çalışmaz — uygulama kurulumunu hiçbir zaman tamamlamamıştır.
* Kanca, uygulama kaldırıldıktan sonra çalışamaz; bu nedenle, uygulama verilerine bağlı olan harici temizlik (örneğin kayıtlarda saklanan bot kimlikleri) harici zamanlanmış bir işte değil, burada yapılmalıdır.
* Kurulum kancalarında olduğu gibi, bu kanca da **geliştirme modunda çalıştırılmaz** — bunun yerine manuel olarak tetikleyin:
```bash filename="Terminal"
yarn twenty dev:function:exec --uninstall
```
```ts src/logic-functions/uninstall.ts
import { defineUninstallLogicFunction, type UninstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async (_payload: UninstallPayload): Promise<void> => {
const client = new CoreApiClient();
const { meetingBots } = await client.query({
meetingBots: { edges: { node: { id: true, externalBotId: true } } },
});
// Delete the provider-side bots so nothing keeps recording after uninstall.
for (const { node } of meetingBots.edges) {
await fetch(`https://api.recorder.example/bots/${node.externalBotId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.RECORDER_API_KEY}` },
});
}
};
export default defineUninstallLogicFunction({
universalIdentifier: 'b2c3d4e5-6789-01bc-def0-234567890abc',
name: 'uninstall',
description: 'Deletes remaining recorder bots when the app is uninstalled.',
timeoutSeconds: 300,
handler,
});
```

View File

@@ -0,0 +1,56 @@
---
title: Genel Bakış
description: Uygulamanın kendisini yapılandırın — kimliğini, varsayılan izinlerini ve kurulum sırasında nelerin çalıştığını.
icon: screwdriver-wrench
---
Bir Twenty uygulamasının **config katmanı**, uygulamayı *platforma tanımlar* — kimliğini, sahip olduğu izinleri ve kurulum veya yükseltme sırasında çalışan kodu açıklar. Bu deklarasyonlar yeni veri biçimleri veya çalışma zamanı davranışı eklemez; Twenty’ye *uygulamanın kim olduğunu* ve *nasıl kurulacağını* söyler.
```text
┌────────────────────────────────────────────────────────┐
│ Application — identity, default role, variables, │
│ marketplace metadata │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Role — what the app's logic functions can read │ │
│ │ and write (referenced by Application) │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
│
▼ (at install / upgrade time)
┌──────────────────────────────────┐
│ Pre-install hook │ before metadata migration
└──────────────────────────────────┘
┌──────────────────────────────────┐
│ Post-install hook │ after metadata migration
└──────────────────────────────────┘
│
▼ (at uninstall time)
┌──────────────────────────────────┐
│ Uninstall hook │ before app removal
└──────────────────────────────────┘
```
## Bu bölümde
<CardGroup cols={2}>
<Card title="Uygulama Yapılandırması" icon="rocket" href="/l/tr/developers/extend/apps/config/application">
`defineApplication` — kimlik, varsayılan rol, değişkenler, marketplace metaverisi.
</Card>
<Card title="Roller ve İzinler" icon="shield-halved" href="/l/tr/developers/extend/apps/config/roles">
`defineRole` — uygulamanızın mantık fonksiyonlarının neleri okuyup yazabildiğini tanımlayın.
</Card>
<Card title="Kurulum Kancaları" icon="wrench" href="/l/tr/developers/extend/apps/config/install-hooks">
`definePreInstallLogicFunction`, `definePostInstallLogicFunction` ve `defineUninstallLogicFunction` — verileri yedekleyin, varsayılanları tohumlayın, yükseltmeleri doğrulayın, kaldırma sırasında temizleyin.
</Card>
</CardGroup>
## Parçaların birbiriyle ilişkisi
* **Application** giriş noktasıdır. Her uygulamada tam olarak bir `defineApplication()` çağrısı vardır ve bu çağrı varsayılan olarak bir **Rol**e işaret eder.
* **Rol**, uygulamanın mantık fonksiyonlarının ve ön bileşenlerinin neleri okuyup yazabildiğini kontrol eder. En az ayrıcalık ilkesini izleyin: kodunuzun gerçekten ihtiyaç duyduğu izinleri verin.
* **Install Hooks**, kurulum veya yükseltme sırasında çalışır — ön kurulum, metadata migrasyonundan önce (böylece riskli bir yükseltmeyi reddedebilir), son kurulum migrasyondan sonra (böylece yeni şemaya karşı varsayılan verileri tohumlayabilir). Kaldırma kancası, uygulama kaldırılmadan hemen önce çalışır, böylece uygulamanın verileri hâlâ okunabilirken harici kaynakları temizleyebilir.
<Note>
Kurulum kancaları, [mantık fonksiyonu](/l/tr/developers/extend/apps/logic/logic-functions) çalışma zamanını paylaşır — aynı handler imzası, aynı ortam değişkenleri, aynı türlendirilmiş API istemcisi — ancak kendi define fonksiyonlarıyla deklare edilir ve normal tetikleyici modelinin (HTTP, cron, veritabanı olayları) dışında yaşarlar.
</Note>

View File

@@ -0,0 +1,67 @@
---
title: Genel varlıklar
description: Uygulamanızla birlikte public/ klasörü aracılığıyla statik dosyalar — görseller, simgeler, yazı tipleri — dağıtın.
icon: folder-open
---
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 `logo` ve `galleryImages` alanları bu klasördeki dosyalara referans verir (örn. `public/logo.png`). Bunlar, uygulamanız yayımlandığında pazar yerinde görüntülenir. Bu alanlar için harici mutlak URL'ler yok sayılır — bunun yerine görselleri `public/` içine paketleyin. Her görüntü 10 MB'ı geçmemelidir.
* **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 dev: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 } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const handler = async (): Promise<any> => {
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 } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const CompanyCard = () => {
const logoUrl = getPublicAssetUrl('logo.png');
return <img src={logoUrl} alt="App logo" />;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'company-card',
component: CompanyCard,
});
```
`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.

View File

@@ -0,0 +1,207 @@
---
title: Roller ve İzinler
description: Uygulamanızın mantık fonksiyonlarının ve ön bileşenlerinin hangi nesne ve alanları okuyup yazabileceğini belirtin.
icon: shield-halved
---
Bir **rol**, bir izin kümesidir: bir uygulamanın hangi nesneleri okuyup yazabileceğini, hangi alanları görebileceğini ve hangi platform düzeyindeki yetenekleri kullanabileceğini tanımlar. Her uygulamanın mantık fonksiyonları ve ön bileşenleri, `defineApplicationRole()` ile işaretlenmiş rolün izinlerini devralır (aşağıda [Varsayılan fonksiyon rolü](#the-default-function-role) bölümüne bakın).
```ts src/roles/restricted-company-role.ts
import {
defineRole,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
SystemPermissionFlag,
} 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,
},
],
permissionFlagUniversalIdentifiers: [SystemPermissionFlag.APPLICATIONS],
});
```
## Satır düzeyi güvenlik
Nesne ve alan izinleri, bir rolün *hangi nesnelere ve alanlara* erişebileceğine karar verir. **Satır düzeyi
izin koşulları** daha da ileri gider ve bir rolün *hangi kayıtları* görebileceğini ve bunlar üzerinde işlem yapabileceğini belirler — örneğin, her harici kullanıcının yalnızca kendi kayıtlarını gördüğü self servis bir rol gibi.
Rolde `rowLevelPermissionPredicates` ile koşulları tanımlayın. Manifestin geri kalanında olduğu gibi,
her koşul kendi `universalIdentifier` değerini taşır ve bir nesneyi ve bir alanı onların
`universalIdentifier` değerleriyle belirtir; ayrıca bir `operand` ve (isteğe bağlı olarak) sorgu zamanında değeri enjekte edilen bir workspaceMember alanı içerir — böylece "kaydın sahibi ilişkisinin **geçerli çalışma alanı üyesi olduğunu**" ifade edebilirsiniz:
```ts src/roles/partner-role.ts
import {
defineRole,
RowLevelPermissionPredicateOperand,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { ACCOUNT_OWNER_FIELD_UNIVERSAL_IDENTIFIER } from '../fields/account-owner.field';
export default defineRole({
universalIdentifier: 'c3c1dc2e-1a08-4de5-abb7-2139b3d99343',
label: 'Partner',
description: 'External partner — sees only its own records',
canBeAssignedToUsers: true,
objectPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
canReadObjectRecords: true,
canUpdateObjectRecords: true,
},
],
rowLevelPermissionPredicates: [
{
universalIdentifier: 'd0f0c1a2-3b4c-4d5e-8f60-111111111111',
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier: ACCOUNT_OWNER_FIELD_UNIVERSAL_IDENTIFIER,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.fields.id
.universalIdentifier,
},
],
});
```
Koşullar manifest ile birlikte geldiği için, her kurulum ve yükseltmede rolle birlikte oluşturulur, güncellenir ve kaldırılır — senkronu korumak için ayrı bir kurulum sonrası adım yoktur.
### Koşulları gruplarla birleştirme
Varsayılan olarak bir rolün koşulları `AND` ile birleştirilir. Bunların bazılarını `OR` ile birleştirmek (veya
mantığı iç içe yerleştirmek) için bir `rowLevelPermissionPredicateGroups` girdisi bildirin ve her koşulu buna
`predicateGroupUniversalIdentifier` aracılığıyla yönlendirin. Bu rol, bir iş ortağının ya **sahibi olduğu** ya da **irtibat kişisi** olduğu bir Opportunity'yi görmesine izin verir:
```ts src/roles/partner-opportunities-role.ts
import {
defineRole,
RowLevelPermissionPredicateGroupLogicalOperator,
RowLevelPermissionPredicateOperand,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
const OPPORTUNITY = STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity;
const CURRENT_MEMBER =
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.fields.id
.universalIdentifier;
export default defineRole({
universalIdentifier: 'b2a1c0d9-8e7f-4a6b-9c5d-222222222222',
label: 'Partner (opportunities)',
canBeAssignedToUsers: true,
objectPermissions: [
{
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
canReadObjectRecords: true,
},
],
rowLevelPermissionPredicateGroups: [
{
universalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator.OR,
},
],
rowLevelPermissionPredicates: [
{
universalIdentifier: 'd4c3b2a1-0e9f-4c8b-9e7d-444444444444',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
fieldUniversalIdentifier: OPPORTUNITY.fields.owner.universalIdentifier,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier: CURRENT_MEMBER,
predicateGroupUniversalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
},
{
universalIdentifier: 'e5d4c3b2-1f0e-4d9c-8f8e-555555555555',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
fieldUniversalIdentifier:
OPPORTUNITY.fields.pointOfContact.universalIdentifier,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier: CURRENT_MEMBER,
predicateGroupUniversalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
},
],
});
```
Notlar:
* Her koşula ve gruba kararlı bir `universalIdentifier` (herhangi bir uuid) verin — bu, yükseltmeler boyunca varlığı
anahtarlamaya yarar ve koşullar gruplara bu anahtar üzerinden başvurur.
* Koşullar, uygulamanıza ait nesnelere ve alanlara veya Twenty'nin standart nesnelerine başvurabilir.
* Satır düzeyi güvenlik, bunu içeren planlardaki çalışma alanları için uygulanır; koşullar diğer planlarda da yine senkronize edilir, yalnızca uygulanmaz.
## Varsayılan fonksiyon rolü
Yeni bir uygulama iskeleti oluşturduğunuzda, CLI `defineApplicationRole()` ile bildirilen varsayılan bir rol dosyası oluşturur:
```ts src/roles/default-role.ts
import { defineApplicationRole } from 'twenty-sdk/define';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineApplicationRole({
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: [],
permissionFlagUniversalIdentifiers: [],
});
```
`defineApplicationRole()`, yükleme sırasında uygulamanızın varsayılanı olarak kullanılan **rolü** işaretleyen, `defineRole()` etrafında ince bir sarmalayıcıdır. Doğrulama, `defineRole` ile aynıdır, ancak derleme hattı `universalIdentifier` değerini uygulama manifestindeki `defaultRoleUniversalIdentifier` alanına otomatik olarak bağlar — bu nedenle onu [`defineApplication`](/l/tr/developers/extend/apps/config/application) içinden kendiniz referans göstermeniz gerekmez.
Notlar:
* Uygulama başına tam olarak **bir** `defineApplicationRole(...)`a izin verilir — birden fazla bulursa manifest derlemesi başarısız olur.
* Uygulamanızın birlikte sunduğu **ek** roller için `defineApplicationRole()` değil, `defineRole()` kullanın.
* `defineApplication()` üzerinde `defaultRoleUniversalIdentifier` değerini açıkça ayarlamak geriye dönük uyumluluk için hâlâ desteklenmektedir, ancak `defineApplicationRole()` lehine kullanımdan kaldırılmıştır.
## En İyi Uygulamalar
* Oluşturulmuş (scaffolded) rolden başlayın ve ardından giderek kısıtlayın — varsayılan rol geniş okuma erişimi verir; bu ise üretim ortamında nadiren isteyeceğiniz bir şeydir.
* `objectPermissions` ve `fieldPermissions` değerlerini, fonksiyonlarınızın gerçekten ihtiyaç duyduğu nesne ve alanlarla değiştirin.
* `permissionFlagUniversalIdentifiers`, platform düzeyindeki yeteneklere erişimi kontrol eder. Bunları asgari düzeyde tutun.
* Çalışan bir örnek için bkz.: [`examples/hello-world/src/roles/default-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/hello-world/src/roles/default-role.ts).

View File

@@ -0,0 +1,52 @@
---
title: Nesneleri Genişletme
description: Standart Twenty nesnelerine (Person, Company, …) alanlar ekleyin veya `defineField` kullanarak diğer uygulamalardaki nesnelere alan ekleyin.
icon: wand-magic-sparkles
---
Sahibi olmadığınız bir nesneye alan eklemek için `defineField()` kullanın — Person veya Company gibi standart bir Twenty nesnesi ya da başka bir yüklü uygulama tarafından sağlanan bir nesne. [`defineObject`](/l/tr/developers/extend/apps/data/objects) içinde tanımlanan 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 Twenty nesneleri için sabiti `twenty-sdk` içinden içe aktarın:
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier
// …
```
* Alanları **`defineObject()` içinde satır içi tanımlarken**, `objectUniversalIdentifier`'a **ihtiyaç duymazsınız** — üst nesneden devralınır.
* `defineField()`, `defineObject()` ile oluşturmadığınız nesnelere alan eklemenin tek yoludur.
* Dosya konumu size bağlıdır. Genel kabul gören yapı `src/fields/\<name>.field.ts` şeklindedir, ancak SDK `src/` içinde herhangi bir yerdeki alanları algılar.
* `writability` ayrıca alan bazında da ayarlanabilir: `MetadataWritability.APPLICATION`, yazma işlemlerini yalnızca uygulamanızın kendi mantık işlevleri tarafından gerçekleştirilecek şekilde sınırlar; `MetadataWritability.SYSTEM` ise platform tarafından yönetilen veriler için ayrılmıştır. Bir alanın yazılabilirliği yalnızca nesnesinin seviyesinden daha katı olabilir ve okumalar etkilenmez. Tam semantik için [Objects](/l/tr/developers/extend/apps/data/objects) bölümüne bakın.
* Standart bir sayfa yerleşimine (örneğin, Görev veya Şirket detay sayfası) bir sekme eklemek için, `twenty-sdk/define` içindeki `STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS` ile birlikte [`definePageLayoutTab`](/l/tr/developers/extend/apps/layout/page-layouts#definepagelayouttab) kullanın.
## Mevcut bir nesneye ilişki ekleme
Bir ilişki alanı eklemek için (örneğin özel nesnenizi standart bir `Person` nesnesine bağlamak), `FieldType.RELATION` ile `defineField()` kullanın. Desen, satır içi ilişkilerle aynıdır ancak `objectUniversalIdentifier` açıkça ayarlanır. Çift yönlü desen için [Relations](/l/tr/developers/extend/apps/data/relations) bölümüne bakın.

View File

@@ -0,0 +1,143 @@
---
title: Nesneler
description: defineObject kullanarak, kendi alanlarına sahip özel tablolar olan yeni kayıt türlerini bildirin.
icon: table
---
Özel **nesneler**, uygulamanızın bir çalışma alanına eklediği yeni kayıt türleridir — Kartpostal, Fatura, Abonelik, alanınıza özgü herhangi bir şey. Her nesne kendi şemasını (alanlar, ilişkiler, varsayılan değerler) ve eşitlemeler ve dağıtımlar boyunca korunan, kararlı bir evrensel tanımlayıcıyı bildirir.
```ts src/objects/post-card.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
* `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.
* `openRecordIn`, bu nesnenin kayıtlarının tıklandığında nerede açılacağını ayarlar: `ObjectOpenRecordIn.USER_CHOICE` (varsayılan; her çalışma alanı üyesinin Ayarlar → Deneyim bölümündeki kendi tercihine uyar), `ObjectOpenRecordIn.SIDE_PANEL` veya `ObjectOpenRecordIn.RECORD_PAGE`. Kayıtların kullanılabilir olması için tam sayfa gerektirdiği durumlarda, iş akışları ve panolarda olduğu gibi `RECORD_PAGE` üzerinde sabitleyin; yalnızca hızlı bir panel olarak anlam ifade eden kayıtlar için ise, takvim etkinliklerinde olduğu gibi `SIDE_PANEL` üzerinde sabitleyin.
* `writability`, rol izinleri devreye girmeden önce, bir nesnenin kayıtlarını kimin yazabileceğini belirler: `MetadataWritability.OPEN` (varsayılan — kararı çalışma alanı rolleri verir), `MetadataWritability.APPLICATION` (yalnızca uygulamanızın kendi mantık işlevleri kayıt oluşturabilir, güncelleyebilir veya silebilir; bunu, kayıtları uygulamanın davranışını belirleyen yapılandırma benzeri nesneler için kullanın; böylece kayıtlara geniş erişimi olan çalışma alanı üyeleri bunları API üzerinden düzenleyemez), veya `MetadataWritability.SYSTEM` (platform tarafından yönetilen veriler için ayrılmıştır). Okumalar etkilenmez — bu, yalnızca arayüz öğelerini gizleyen `isUIEditable`'ın aksine sunucu tarafında uygulanır. Ayrıca alan başına da mevcuttur ve bu durumda yalnızca nesnenin düzeyinden daha kısıtlayıcı olabilir.
* Burada tanımlanan satır içi alanların bir `objectUniversalIdentifier`'a **ihtiyacı yoktur** — üst nesneden devralınır. Sahibi olmadığınız nesnelere alan eklemek için [`defineField()`](/l/tr/developers/extend/apps/data/extending-objects) kullanın.
* `yarn twenty dev:add object` kullanarak, adlandırma, alanlar ve ilişkiler konusunda sizi yönlendirerek yeni nesneler oluşturabilirsiniz. [Mimari → Varlık iskeleti oluşturma](/l/tr/developers/extend/apps/getting-started/scaffolding) bölümüne bakın.
<Note>
**Temel alanlar otomatik olarak eklenir.** Özel bir nesne tanımladığınızda Twenty, sizin için `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` ve `deletedAt` gibi standart alanlar oluşturur. Bunları `fields` dizinizde bildirmenize gerek yok — yalnızca özel alanlarınızı ekleyin. Aynı ada sahip bir alan bildirerek varsayılan bir alanı geçersiz kılabilirsiniz, ancak bu nadiren iyi bir fikirdir.
</Note>
## Alan tipleri
`twenty-sdk/define` içinden dışa aktarılan, `FieldType` değerlerinin tam kümesi:
| Kategori | Türler |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Metin | `TEXT`, `RICH_TEXT`, `ARRAY` (string dizisi), `RAW_JSON` |
| Sayısal | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`), `NUMERIC` (sınırsız hassasiyet), `RATING`, `POSITION` |
| Tarihler | `DATE`, `DATE_TIME` |
| Seçim | `BOOLEAN`, `SELECT`, `MULTI_SELECT` |
| Bileşik | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` |
| Tanımlayıcılar ve ilişkiler | `UUID`, `RELATION`, `MORPH_RELATION` (bkz. [İlişkiler](/l/tr/developers/extend/apps/data/relations)) |
| Sistem | `TS_VECTOR` (sunucu tarafından yönetilen tam metin arama vektörü) |
Bileşik tipler birden çok alt alan depolar (ör. `FULL_NAME` = ad + soyad; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` ve `MULTI_SELECT`, yukarıdaki örnekte olduğu gibi bir `options` dizisi gerektirir.
## Varsayılan değerler
Sabit (literal) dize varsayılanları, dize **içinde** tek tırnak içine alınmış olmalıdır — `defaultValue: "'Draft'"`, `defaultValue: "Draft"` değil. Bu nedenle yukarıdaki `status` alanı `` `'${PostCardStatus.DRAFT}'` `` kullanır.
Tırnak içine alınmamış dizeler, bir kayıt oluşturulduğunda değerlendirilen, hesaplanmış varsayılanlar için ayrılmıştır:
* `'uuid'` — bir UUID üretir (`UUID` alanları için)
* `'now'` — geçerli zaman damgası (`DATE_TIME` alanları için)
Aynı kural, bileşik varsayılanların string alt alanları için (örneğin bir `ACTOR` alanında `{ source: "'MANUAL'" }`) ve `SELECT`/`MULTI_SELECT` değerleri için de geçerlidir. Tırnak içine alınmamış bir sabit string varsayılan, uygulamanız oluşturulurken bir uyarıya neden olur.
## Boş değer atanabilirliği
`isNullable`, bir alanın `NULL` kabul edip etmediğini kontrol eder. Varsayılan değeri `true`'dur — isteğe bağlı alanlar için bunu belirtmeyin. Bir alanı veritabanı düzeyinde zorunlu yapmak için `isNullable: false` olarak ayarlayın.
`isNullable` değişiklikleri, mevcut bir alanı güncelleyenler de dahil olmak üzere her senkronizasyonda uygulanır — bu nedenle manifesti düzenleyip yeniden senkronize ederek bir alanın boş değer atanabilirliğini değiştirebilirsiniz.
<Note>
**Var olan bir alanı boş değer kabul etmeyecek (non-nullable) hale getirmek için bir varsayılan değer gereklidir.** Bir alanı `isNullable: false` olarak değiştirdiğinizde, ayrıca boş olmayan (non-null) bir `defaultValue` sağlamanız gerekir. Varsayılan değer, `NOT NULL` kısıtlaması uygulanmadan önce mevcut `NULL` satırlarını doldurur; bu olmadan senkronizasyon, `Default value cannot be null for non-nullable fields` hatasıyla başarısız olur. İlişki alanları (relation fields) ve `TS_VECTOR` alanları her zaman boş değere izin verir (nullable), bu yüzden `isNullable` bunlar üzerinde hiçbir etkiye sahip değildir.
</Note>
```ts
{
universalIdentifier: 'b1a7c0de-1234-4f00-9abc-000000000000',
name: 'reference',
type: FieldType.TEXT,
label: 'Reference',
isNullable: false,
defaultValue: "'N/A'",
}
```
## Sırada ne var
* **Bu nesneyi diğerlerine bağlayın** — çift yönlü ilişki modeli için [İlişkiler](/l/tr/developers/extend/apps/data/relations) bölümüne bakın.
* **Diğer uygulamalardaki nesnelere alanlar ekleyin** — `defineField()` için [Nesneleri Genişletme](/l/tr/developers/extend/apps/data/extending-objects) bölümüne bakın.
* **Bu nesneyi arayüzde görüntüle** — kenar çubuğuna bir menü öğesi eklemek için [Gezinme Menüsü Öğeleri](/l/tr/developers/extend/apps/layout/navigation-menu-items) bölümüne bakın; özel liste yapılandırmaları eklemek için [Görünümler](/l/tr/developers/extend/apps/layout/views) bölümüne bakın.

View File

@@ -0,0 +1,101 @@
---
title: Genel Bakış
description: Uygulamanızın bir çalışma alanına eklediği verileri şekillendirin — nesneler, alanlar ve ilişkiler.
icon: database
---
Bir Twenty uygulamasının **veri katmanı**, uygulamanızın bir çalışma alanına *eklediği* veridir — bildirdiği yeni kayıt türleri, mevcut nesnelere eklediği sütunlar ve bu kayıtların birbirine nasıl bağlandığı.
```text
┌──────────────────────────────────────────────────┐
│ Object — a record type, e.g. PostCard │
│ ├─ Field (name, type, label) │
│ ├─ Field │
│ └─ Relation (link to another object) │
└──────────────────────────────────────────────────┘
│
├── lives in your app, OR
│
▼
┌──────────────────────────────────────────────────┐
│ Standard / other apps' objects │
│ └─ Field added by your app via defineField │
└──────────────────────────────────────────────────┘
```
## Bu bölümde
<CardGroup cols={2}>
<Card title="Nesneler" icon="table" href="/l/tr/developers/extend/apps/data/objects">
`defineObject` — kendi alanlarına sahip yeni kayıt türleri bildirin.
</Card>
<Card title="Nesneleri Genişletme" icon="wand-magic-sparkles" href="/l/tr/developers/extend/apps/data/extending-objects">
`defineField` — standart nesnelere veya diğer uygulamaların nesnelerine alanlar ekleyin.
</Card>
<Card title="İlişkiler" icon="diagram-project" href="/l/tr/developers/extend/apps/data/relations">
Nesneler arasında çift yönlü `MANY_TO_ONE` / `ONE_TO_MANY` bağlantıları.
</Card>
<Card title="Zaman Çizelgesi Aktivite Türleri" icon="clock-rotate-left" href="/l/tr/developers/extend/apps/data/timeline-activity-types">
Kayıt zaman çizelgeleri için otomatik denetim olaylarını ve açık uygulama olaylarını tanımlayın.
</Card>
</CardGroup>
## Varlıklara genel bakış
| Varlık | Amaç | Şununla tanımlanır |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| **Nesne** | Kendi alanlarına sahip yeni bir özel kayıt türü (ör. PostCard, Invoice) | `defineObject()` |
| **Alan** | Bir nesne üzerindeki sütun. Bağımsız alanlar, oluşturmadığınız nesneleri genişletebilir (ör. Company nesnesine `loyaltyTier` ekleyin) | `defineField()` |
| **İlişki** | İki nesne arasında, her iki tarafı da alan olarak bildirilmiş çift yönlü bir bağlantı | `defineField()` ile `FieldType.RELATION` |
| **Dizin** | Nesnelerinizden biri üzerinde yinelenen bir sorguyu hızlandırmak için bir veritabanı dizini | `defineIndex()` |
| **Zaman Çizelgesi Aktivite Türü** | Kayıt zaman çizelgelerinde gösterilen otomatik denetim olayı veya açık etki alanı olayı. | `defineTimelineActivityType()` |
SDK bunları derleme zamanında AST analizi aracılığıyla algılar; dolayısıyla dosya organizasyonu size kalmıştır — gelenek `src/objects/`, `src/fields/`, `src/indexes/` ve `src/timeline-activity-types/` dizinlerini kullanmaktır. Kararlı `universalIdentifier` UUID’leri, dağıtımlar arasında her şeyi birbirine bağlar.
## Dizinler (İsteğe bağlı)
Uygulamalar, yinelenen sorguları hızlı tutmak için nesneleriyle birlikte dizinler sunabilir. En yaygın durum, sık okuduğunuz bir durum ya da yabancı anahtar sütunudur.
```ts src/indexes/post-card-status.index.ts
import { defineIndex } from 'twenty-sdk/define';
import {
POST_CARD_UNIVERSAL_IDENTIFIER,
STATUS_FIELD_UNIVERSAL_IDENTIFIER,
} from '../objects/post-card.object';
export default defineIndex({
universalIdentifier: 'b6e9d2a1-5a4c-46ca-9d52-42c8f02d1ff0',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
fields: [
{
universalIdentifier: 'b6e9d2a1-5a4c-46ca-9d52-42c8f02d1ff1',
fieldUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
},
],
});
```
### Benzersiz dizinler
`defineIndex`, hem tek sütunlu hem çok sütunlu benzersizlik için `isUnique: true` kabul eder. Önerilen yöntem budur — `defineField({ isUnique: true })` kullanımdan kaldırılmıştır ve gelecekteki bir sürümde kaldırılacaktır.
```ts
defineIndex({
universalIdentifier: '…',
objectUniversalIdentifier: PERSON_UNIVERSAL_IDENTIFIER,
isUnique: true,
fields: [{ universalIdentifier: '…', fieldUniversalIdentifier: EMAIL_FIELD_UNIVERSAL_IDENTIFIER }],
});
```
### Diğer kısıtlamalar
* Kısmi `WHERE` koşulları yönetici kontrolü altında kalır — uygulamalar bunları tanımlayamaz.
* Her nesne, 10 özel dizin ile sınırlandırılmıştır (framework'ün kendi dizinleri buna dahil değildir).
`fields` dizisini, Postgres'in kullanması gereken şekilde sıralayın — en soldaki sütun ilk, bir telefon rehberinde olduğu gibi. Dizinler bedava değildir: tabloya yapılan her yazma işlemi bunları günceller. Bir dizini yalnızca ona ihtiyaç duyan bir sorgunuz olduğunda ekleyin.
<Note>
**Application Config** veya **Roles & Permissions** mı arıyorsunuz? Bunlar, ekledikleri verilerden çok uygulamanın kendisini tanımlar — [Config](/l/tr/developers/extend/apps/config/overview) altında bulunurlar. **Connections** (Linear, GitHub, Slack OAuth) mı arıyorsunuz? Bunlar, mantık fonksiyonları *içinden* çağrılmak için vardır ve [Logic](/l/tr/developers/extend/apps/logic/connections) altında bulunurlar.
</Note>

View File

@@ -0,0 +1,184 @@
---
title: İlişkiler
description: Nesneleri çift yönlü MANY_TO_ONE / ONE_TO_MANY ilişkilerle birbirine bağlayın.
icon: diagram-project
---
İlişkiler iki nesneyi birbirine bağlar. Twenty'de ilişkiler her zaman **çift yönlüdür** — her ilişkinin iki tarafı vardır ve her taraf diğeriyle ilişkili alan olarak tanımlanı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` birçok `PostCardRecipient` kaydına gönderilebilir. 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',
},
});
```
<Note>
**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ğerinde içe aktarın. Derleme sistemi bunları derleme zamanında çözer.
</Note>
## Bağlantı ilişkileri
Bir bağlantı nesnesi, bir kaynak kaydını bir veya daha fazla hedef kayda bağlar. Örneğin, `PostCardRecipient` hem bir `postCard` ilişkisini hem de bir `recipient` ilişkisini tutabilir. Kaynak nesnenin `ONE_TO_MANY` alanında, `junctionTargetFieldUniversalIdentifier` değerini bağlantı nesnesinde hedefi işaret eden ilişki alanına ayarlayın:
```ts
export default defineField({
universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCardRecipients',
label: 'Post Card Recipients',
relationTargetObjectMetadataUniversalIdentifier:
POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
junctionTargetFieldUniversalIdentifier: RECIPIENT_FIELD_ID,
},
});
```
`RECIPIENT_FIELD_ID`, bağlantı nesnesindeki bir `MANY_TO_ONE` veya morph ilişkisini tanımlamalıdır. [zaman çizelgesi etkinliği yayılımı](/l/tr/developers/extend/apps/data/timeline-activity-types#automatic-and-explicit-events) gibi bir özellik bağlantı nesnesinden geçtiğinde, Twenty'ye hangi ilişkili kaydın hedef olduğunu bildirir.
## 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`) |
| `universalSettings.junctionTargetFieldUniversalIdentifier` | Yalnızca bağlantı nesnesi ONE_TO_MANY | Yayılım hedefini işaret eden bağlantı nesnesindeki ilişki alanı |
## Satır içi ilişki alanları
Bir ilişkiyi doğrudan [`defineObject`](/l/tr/developers/extend/apps/data/objects) içinde de tanımlayabilirsiniz. Satır içi olduğunda, `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
],
});
```

View File

@@ -0,0 +1,204 @@
---
title: Sistem Meta Verilerini Hedefleme
description: Twenty meta verilerinin deterministik evrensel tanımlayıcılarını her nesnede otomatik olarak çözümleyin; böylece uygulamanız bunlara, değerleri kodun içine gömmek zorunda kalmadan başvurabilir.
icon: gears
---
Twenty içindeki her nesne, kendi başınıza asla bildirmediğiniz **sistem meta verileriyle** birlikte gelir; bir alan kümesi ve sütunlarıyla birlikte ana liste görünümü gibi. Sunucu, nesne sağlandığında bunların hepsini oluşturur ve Twenty büyüdükçe bu küme de büyür.
Siz bunu bildirmediğiniz için, içe aktarabileceğiniz bir `universalIdentifier` sabiti yoktur. Bunun yerine, sunucu her tanımlayıcıyı deterministik olarak **türetir** ve `twenty-sdk`, bildirim dosyanızın sunucunun kullandığı kesin değeri çözümleyebilmesi için aynı türetmeyi dışa açar.
## Sistem Alanları
Her nesnede bulunan, hiçbirini [`defineField()`](/l/tr/developers/extend/apps/data/extending-objects) ile bildirmediğiniz skaler alanlar:
`id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`
Peki bir [görünümde](/l/tr/developers/extend/apps/layout/views) `createdAt` alanına sütun olarak nasıl referans verirsiniz?
### Sorun
Twenty 2.19'dan beri, bir sistem alanının evrensel tanımlayıcısı, sunucu tarafından üç girdiden **deterministik olarak türetilir**: uygulama evrensel tanımlayıcısı, nesne evrensel tanımlayıcısı ve alan adı. Rastgele bir id uydurup onu hardcode etmek işe yaramaz: sunucudaki hiçbir şeyle eşleşmez ve senkronizasyon, askıda kalan referansı reddeder:
```
Dev sync failed: viewField: INVALID_VIEW_DATA: Field metadata not found
```
### Çözüm
<Note>
`getFieldUniversalIdentifier`, `twenty-sdk` 2.21 sürümünden itibaren kullanılabilir.
</Note>
Sunucunun kullandığıyla tamamen aynı değeri elde etmek için `getFieldUniversalIdentifier` kullanın. Üç girdiyi alır ve alanın evrensel tanımlayıcısını döndürür:
```ts
import { getFieldUniversalIdentifier } from 'twenty-sdk/define';
const createdAtFieldId = getFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
name: 'createdAt',
});
```
* `applicationUniversalIdentifier`, uygulamanızın tanımlayıcısıdır; [`defineApplication()`](/l/tr/developers/extend/apps/config/application) çağrısına aktardığınız tanımlayıcı.
* `objectUniversalIdentifier`, alanın ait olduğu nesnenin tanımlayıcısıdır.
* `name`, sistem alan adıdır; yukarıda listelenen değerlerden biridir.
### Örnek: bir görünümde createdAt sütunu
Tipik durum, özel nesnelerinizden birinin görünümüne bir `createdAt` sütunu eklemektir. Alan id'sini çözümleyin ve ona, herhangi bir `fieldMetadataUniversalIdentifier` gibi referans verin:
```ts src/views/example-view.ts
import {
defineView,
getFieldUniversalIdentifier,
} from 'twenty-sdk/define';
const APPLICATION_UNIVERSAL_IDENTIFIER =
'0b04e15c-27b2-4741-9046-b32e07469072';
const MY_OBJECT_UNIVERSAL_IDENTIFIER =
'c782b61c-70fd-4c88-9cd6-4e61ab8d7591';
export default defineView({
universalIdentifier: '70f10d44-144a-4da8-8c6f-3ec2422138c0',
name: 'All records',
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconList',
position: 0,
fields: [
{
universalIdentifier: '75a90bc4-d901-4df4-85e0-af29db5e0104',
fieldMetadataUniversalIdentifier: getFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
name: 'createdAt',
}),
position: 0,
isVisible: true,
size: 200,
},
],
});
```
Aynı çözümlenen id, bir `fieldMetadataUniversalIdentifier` beklendiği her yerde çalışır: görünüm alanları, filtreler, sıralamalar, gruplar ve sayfa düzeni bileşenleri.
<Note>
Id'yi çözümleyin, hardcode etmeyin. Sunucu değeri uygulama id'si, nesne id'si ve alan adından
türettiği için, `getFieldUniversalIdentifier` çağrısı, bu girdiler değişse bile referansınızı doğru tutar ve türetme zaman içinde gelişirse sapmayı önler.
</Note>
### Sistem ilişki alanları
<Note>
`getSystemRelationFieldUniversalIdentifier` 2.23 sürümünden itibaren `twenty-sdk` içinde kullanılabilir ve Twenty sunucusunun 2.23 veya daha yeni bir sürümünü gerektirir.
</Note>
Yukarıdaki skaler sistem alanlarının yanı sıra, sunucu her nesne üzerinde dört **sistem ilişki alanı** da sağlar: `timelineActivities`, `attachments`, `noteTargets` ve `taskTargets`. Bunların her biri, karşılık gelen standart ilişki nesnesini işaret eder.
Bu alanlar `getFieldUniversalIdentifier` ile çözümlenmez: tanımlayıcıları, alanı barındıran nesne ve alanın işaret ettiği nesneden **isimden bağımsız** olarak türetilir. Bu sayede bir nesnenin adını değiştirmek, ilişki alanlarının tanımlayıcılarını asla değiştirmez.
Bunları çözümlemek için `getSystemRelationFieldUniversalIdentifier` kullanın:
```ts
import {
getSystemRelationFieldUniversalIdentifier,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
// rocket.attachments — the relation field hosted on your custom object
const rocketAttachmentsFieldId = getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
relationTargetObjectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment.universalIdentifier,
});
```
* `objectUniversalIdentifier`, alanın **barındırıldığı** nesnedir.
* `relationTargetObjectUniversalIdentifier`, alanın **işaret ettiği** nesnedir.
Yön, argüman sırasına göre kodlanır. Ters tarafı çözümlemek için (örneğin, `attachment.targetRocket`, sunucunun standart ilişki nesnesi üzerinde oluşturduğu morph alanı), ikisini değiştirin:
```ts
// attachment.targetRocket — the reverse morph field on Attachment
const attachmentTargetRocketFieldId =
getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment.universalIdentifier,
relationTargetObjectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
});
```
Skaler sistem alanlarında olduğu gibi, çözümlenen kimlik, `fieldMetadataUniversalIdentifier` beklendiği her yerde çalışır.
## Sistem görünümleri
<Note>
`getSystemViewUniversalIdentifier` ve `getSystemViewFieldUniversalIdentifier`, `twenty-sdk` 2.26 sürümünden itibaren kullanılabilir ve Twenty sunucusunun 2.26 veya daha yeni bir sürümünü gerektirir.
</Note>
Sunucu ayrıca her nesnede bir **sistem görünümü** de sağlar: ana liste görünümü (`All {objectLabelPlural}`, `ViewKey.INDEX` ( `SYSTEM_VIEW_KEYS.INDEX` ile türetilmiş) anahtarıyla), gösterilebilir her alan için bir sütunla. Sistem ilişki alanlarında olduğu gibi, tanımlayıcıları **isimden bağımsız** olarak türetilir; bu nedenle bir nesnenin veya alanın adını değiştirmeniz onları asla değiştirmez.
Görünümü çözümlemek için `getSystemViewUniversalIdentifier` kullanın:
```ts
import {
getSystemViewUniversalIdentifier,
SYSTEM_VIEW_KEYS,
} from 'twenty-sdk/define';
const rocketIndexViewId = getSystemViewUniversalIdentifier({
objectMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
viewKey: SYSTEM_VIEW_KEYS.INDEX,
});
```
* `objectMetadataApplicationUniversalIdentifier`, görünümün ad alanını belirleyen, **nesneye** sahip olan uygulamadır.
* `objectUniversalIdentifier`, görünümün listelediği nesnedir.
* `viewKey`, sistem görünümü ayırt edicisidir: ana liste görünümü için `SYSTEM_VIEW_KEYS.INDEX`, kayıt sayfası alanları bileşen görünümü için `SYSTEM_VIEW_KEYS.FIELDS_WIDGET`. Türetmenin anahtarını belirler; yalnızca `INDEX` görünüm satırında da saklanır.
Çözümlenen kimlik, bir `viewUniversalIdentifier` beklendiği her yerde, örneğin bir [`NavigationMenuItemType.VIEW`](/l/tr/developers/extend/apps/layout/navigation-menu-items) kenar çubuğu girdisinde çalışır. Sadece bir nesnenin ana listesini açmak için, `targetObjectUniversalIdentifier` ile `NavigationMenuItemType.OBJECT` kullanmayı tercih edin: hiçbir türetmeye ihtiyaç duymaz.
`getSystemViewFieldUniversalIdentifier`, bir sistem görünümündeki tek bir **sütunu**, görünümden ve görüntülediği alandan yola çıkarak çözümler:
```ts
import { getSystemViewFieldUniversalIdentifier } from 'twenty-sdk/define';
const rocketNameColumnId = getSystemViewFieldUniversalIdentifier({
fieldMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
viewUniversalIdentifier: rocketIndexViewId,
fieldMetadataUniversalIdentifier: ROCKET_NAME_FIELD_UNIVERSAL_IDENTIFIER,
});
```
İlk argümana dikkat edin: bir sütun, **görüntülediği alana** sahip olan uygulamanın ad alanındadır, görünüme sahip olanın değil. Uygulamanızın standart bir nesneye eklediği bir alan, Twenty'ye ait bir görünüm üzerinde, kendi uygulamanız altında türetilen bir sütun alır.
<Warning>
Sistem görünümleri ve sütunları **sunucuya aittir**: bunları bildirmek için değil, yalnızca onlara başvurmak için tanımlayıcılarını çözümleyin. [`defineView()`](/l/tr/developers/extend/apps/layout/views) üzerindeki
`key` kullanımdan kaldırılmıştır ve yok sayılır; bu nedenle bir bildirim görünümü asla `INDEX` anahtarını talep edemez ve sunucu, eklediğiniz her alan için zaten bir sütun sağlar; bu yüzden bir sistem görünümünde aynı alan için kendi `defineViewField()` bildiriminizi yapmak bununla çelişir.
</Warning>
## Standart Twenty nesneleri
**Standart** bir Twenty nesnesi (Person, Company, Opportunity, …) için hiçbir şey türetmeniz gerekmez: hem alanlar hem de görünümler için, doğrudan içe aktarabileceğiniz önceden hesaplanmış sabit tanımlayıcılar vardır.
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.createdAt.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.fields.updatedAt.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.views.allPeople.universalIdentifier
```
Yukarıdaki yardımcı işlevlere, [`defineObject()`](/l/tr/developers/extend/apps/data/objects) ile **uygulamanızın** tanımladığı ve böyle bir sabitin bulunmadığı nesneler için başvurun.
<Note>
`name`, bir sistem alanı değil, bir **varsayılan** alandır. Kendi hardcode edilmiş
evrensel tanımlayıcısını korur ve
`getFieldUniversalIdentifier` ile çözülmez. Tanımladığınız nesnelerde, `name` alanına `defineObject()` içinde ona verdiğiniz tanımlayıcı ile referans verin.
</Note>

View File

@@ -0,0 +1,184 @@
---
title: Zaman Çizelgesi Aktivite Türleri
description: Kayıt zaman çizelgelerinde görüntülenen otomatik denetim olaylarını ve açık uygulama olaylarını tanımlayın.
icon: clock-rotate-left
---
Bir zaman çizelgesi aktivite türü, bir kaydın zaman çizelgesinde gösterilen olay için kararlı sözlüğü tanımlar. Standart nesneler ve uygulama nesneleri aynı sözleşmeyi kullanır: bir türün etiketi ve simgesi vardır, isteğe bağlı olarak ne zaman yayımlandığını bildirebilir ve uygulamanızın [ön bileşenlerinden](/l/tr/developers/extend/apps/layout/front-components) biri aracılığıyla isteğe bağlı olarak işlenebilir.
<Note>
Zaman çizelgesi aktivite türleri beta aşamasındadır ve yakında Twenty 2.34'te kullanıma sunulacaktır. Uygulama geliştiricilerinin kullanım durumlarından öğrendikçe
API gelişebilir.
</Note>
Scaffolder ile bir tane oluşturun:
```bash filename="Terminal"
yarn twenty dev:add timelineActivityType
```
Veya doğrudan tanımlayın:
```ts filename="src/timeline-activity-types/post-card-created.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7500',
name: 'postCardCreated',
label: 'created a post card',
icon: 'IconMail',
emit: {
on: 'created',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
},
});
```
## Otomatik ve açık olaylar
Twenty'nin bu türü otomatik olarak oluşturması gerektiğinde `emit` ekleyin. `emit.on`, `created`, `updated`, `deleted`, `restored`, `linked` ve `unlinked` değerlerini desteklerken `emit.objectUniversalIdentifier` kaynak nesneyi tanımlar. Bir çalışma alanında aynı yayımlama anahtarını —`on`, nesne ve isteğe bağlı through ilişkisi— yalnızca bir etkin tür işleyebilir.
`emit.through` olmadan olay, kaynak kaydın kendi zaman çizelgesine yazılır. Bunu ilişkili kayıtlara yaymak için `emit.through.relationFieldUniversalIdentifier` değerini, kaynak nesnedeki doğrudan çoktan-bire ilişkiye veya [bire-çok birleşim ilişkisine](/l/tr/developers/extend/apps/data/relations#junction-relations) ayarlayın. Morph ilişkileri, morph gruplarının her üyesine yayılır; bu nedenle tek bir bildirim birkaç nesne türünü hedefleyebilir.
Doğrudan bir ilişkide kaynak kaydın oluşturulması veya geri yüklenmesi `linked` üretir, silinmesi `unlinked` üretir ve ilişkinin yeniden hedeflenmesi önceki hedefte `unlinked` ile yeni hedefte `linked` üretir. Diğer kaynak güncellemeleri bağlantı olayı üretmez. Bu, hedefi doğrudan bir morph ilişkisi olan ekler tarafından kullanılan sözleşmedir.
Bir birleşim ilişkisi için `universalSettings.junctionTargetFieldUniversalIdentifier`, birleşim nesnesinden hedefe olan ilişkiyi tanımlar. Bir birleşim satırının oluşturulması veya silinmesi `linked` ya da `unlinked` üretir. Bir birleşim satırının her iki tarafının yeniden hedeflenmesi de bir bağlantı olayı üretir; ilişkisiz birleşim alanlarındaki güncellemeler üretmez.
`linked` ve `unlinked` olayları `emit.through` gerektirir; çünkü tetikleyicileri yapılandırılmış doğrudan veya birleşim ilişkisindeki bir değişikliktir.
Örneğin bu, notlar, görevler, mesajlar ve takvim olayları tarafından kullanılan genel sözleşmenin aynısıdır:
```ts filename="src/timeline-activity-types/post-card-linked.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
import { POST_CARD_RECIPIENTS_FIELD_UNIVERSAL_IDENTIFIER } from '../fields/post-card-recipients-on-post-card.field';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7502',
name: 'postCardLinked',
label: 'received a post card',
icon: 'IconMail',
emit: {
on: 'linked',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
through: {
relationFieldUniversalIdentifier:
POST_CARD_RECIPIENTS_FIELD_UNIVERSAL_IDENTIFIER,
},
},
});
```
Varsayılan olarak her kaynak güncellemesi için bir `updated` through olayı yayımlanır. Yalnızca seçili kaynak alanlarındaki değişikliklerin hedef zaman çizelgelerinde görünmesi gerektiğinde `emit.through.triggerFieldUniversalIdentifiers` ayarlayın.
Mantık işlevinizin kendisinin oluşturduğu açık bir etki alanı olayı için `emit` kullanmayın. Bu, aynı işlem için hem otomatik bir denetim satırı hem de açık bir satır oluşturulmasını önler.
```ts filename="src/timeline-activity-types/post-card-sent.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
name: 'postCardSent',
label: 'sent a post card',
icon: 'IconSend',
});
```
[`createTimelineActivity()`](/l/tr/developers/extend/apps/logic/logic-functions#create-a-timeline-activity) ile açık olaylar oluşturun. Uygulama kodu kararlı evrensel tanımlayıcılar kullanır; Twenty, yüklemeye özgü meta veri kimliklerini çözümler.
## Özel işleme
Bir ön bileşen olmadan Twenty, tür etiketi, simgesi ve bağlantılı nesne meta verilerinden yerel bir genel satır işler. Bu, nesneye özgü bir işleyici olmadan standart ve özel nesneler için çalışır.
Özel ayrıntılar için `frontComponentUniversalIdentifier` değerini aynı uygulamanın sahip olduğu bir ön bileşene ayarlayın:
```ts
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
name: 'postCardSent',
label: 'sent a post card',
icon: 'IconSend',
frontComponentUniversalIdentifier: '88c15ae2-5f87-4a6b-b48f-1974bbe62eb7',
});
```
Yerel satır daraltılmış görünüm olarak kalır. Twenty, ön bileşeni yalnızca kullanıcı o satırı genişlettikten sonra bağlar; böylece görünür her olay için bir korumalı alan ve çalışan kullanılması önlenir. Bileşen içinde, satır kimliğini okumak ve görünümünüzün ihtiyaç duyduğu verileri getirmek için `twenty-sdk/front-component` içinden `useTimelineActivityId()` çağrısı yapın. Bileşen bir zaman çizelgesi satırının dışında işlendiğinde `null` döndürür.
## Başka bir uygulamanın görünümünü geçersiz kılma
Bir uygulama, kendi nesneleri için otomatik zaman çizelgesi sözleşmelerine sahiptir. Başka bir uygulamanın sahip olduğu bir nesnedeki olayı özelleştirmek için mevcut türü `replacesTimelineActivityTypeUniversalIdentifier` ile açıkça bildirin:
```ts
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7503',
name: 'companyCreatedWithDeploymentContext',
label: 'was created from a deployment',
emit: {
on: 'created',
objectUniversalIdentifier: COMPANY_UNIVERSAL_IDENTIFIER,
},
replacesTimelineActivityTypeUniversalIdentifier:
RECORD_CREATED_TIMELINE_ACTIVITY_TYPE_UNIVERSAL_IDENTIFIER,
frontComponentUniversalIdentifier: '88c15ae2-5f87-4a6b-b48f-1974bbe62eb7',
});
```
Bir geçersiz kılma, yalnızca açık tür yerine mevcut bir otomatik yayımlama yuvasını değiştirdiği için `emit` içermelidir. Başvurulan tür, hedef nesnenin uygulamasına ait olmalı ve aynı eylemi ve rotayı açıklamalıdır. Geçersiz kılan uygulamanın kaldırılması temel türü geri yükler; temel türün kaldırılması geçersiz kılmayı devre dışı bırakır.
## Çalışma alanı geçersiz kılmaları ve sessize alma
Çalışma alanı yöneticileri, uygulamayı çatallamadan bir türün görünümünü değiştirebilir veya otomatik ve açık olaylarını sessize alabilir. Yüklemeye özgü tür kimliğiyle meta veri API'sinin `updateTimelineActivityType` mutasyonunu kullanın:
```graphql
mutation CustomizeTimelineActivityType(
$id: UUID!
$label: String
$icon: String
$isActive: Boolean
) {
updateTimelineActivityType(
input: { id: $id, label: $label, icon: $icon, isActive: $isActive }
) {
id
label
icon
isActive
}
}
```
`label` ve `icon`, çalışma alanı geçersiz kılmaları olarak depolanır; bu nedenle daha sonraki uygulama güncellemeleri yöneticinin seçimlerinin üzerine yazmaz. Otomatik yayımlamayı durdurmak ve bu türdeki yeni açık olayları reddetmek için `isActive` değerini `false` olarak ayarlayın. Mevcut satırlar görünür kalır.
Etkin durum da dahil olmak üzere uygulama varsayılanlarını şununla geri yükleyin:
```graphql
mutation ResetTimelineActivityType($id: UUID!) {
resetTimelineActivityType(id: $id) {
id
label
icon
isActive
}
}
```
## Yapılandırma alanları
| Alan | Zorunlu | Açıklama |
| ------------------------------------------------- | -------- | ----------------------------------------------------------------------------------- |
| `universalIdentifier` | Evet | Yüklemeler ve yükseltmeler boyunca tür için kararlı UUID |
| `name` | Evet | Uygulama yerelinde programatik ad; uygulama içinde benzersiz olmalıdır |
| `label` | Evet | Yerel işleyici tarafından kullanılan, kullanıcıya yönelik eylem metni |
| `icon` | Hayır | Olayın yanında görüntülenen Twenty simge adı |
| `emit` | Hayır | Otomatik yayımlama bildirimi; yalnızca açık türler için kullanmayın |
| `emit.on` | Emit ile | Bu türün yazılmasına neden olan denetim eylemi |
| `emit.objectUniversalIdentifier` | Emit ile | Kayıtları bu türü yayımlayan nesne |
| `emit.through.relationFieldUniversalIdentifier` | Hayır | İlişkili zaman çizelgelerine yaymak için kullanılan doğrudan veya birleşim ilişkisi |
| `emit.through.triggerFieldUniversalIdentifiers` | Hayır | Bir `updated` through olayını tetikleyebilen kaynak alanlar |
| `frontComponentUniversalIdentifier` | Hayır | Satır genişletildiğinde bağlanan uygulamaya ait ön uç bileşeni. |
| `replacesTimelineActivityTypeUniversalIdentifier` | Hayır | Mevcut emit türü değiştirildi; `emit` gerektirir |
Twenty, bir olay oluşturulduğunda anlamsal kimliği ve kalıcı sunum geri dönüşlerini anlık görüntüler. Geçmiş satırlar özgün eylem ve nesne anlamlarını korur. Tür yüklüyken, mevcut çevrilmiş etiketi, simgesi ve ön uç bileşeni canlı olarak işlenir; kaldırıldıktan sonra anlık görüntü geri dönüşü satırı okunabilir tutar. Çözümleme evrensel tanımlayıcıyı kullanır, bu nedenle sunum uygulamanın yeniden yüklenmesinden de etkilenmez.

View File

@@ -0,0 +1,102 @@
---
title: Kavramlar
description: Twenty uygulamalarının nasıl çalıştığı — varlık modeli, korumalı alan (sandboxing) ve kurulum yaşam döngüsü.
icon: sitemap
---
Twenty uygulamaları, çalışma alanınızı özel nesneler, mantık, UI bileşenleri ve yapay zekâ yetenekleriyle genişleten TypeScript paketleridir. Tam korumalı alan ve izin kontrolleriyle Twenty platformunda çalışırlar.
## Uygulamalar nasıl çalışır
Bir uygulama, `twenty-sdk` paketindeki `defineEntity()` işlevleri kullanılarak bildirilen **varlıklar** koleksiyonudur. SDK, bu bildirimleri derleme sırasında AST analiziyle algılar ve bir **manifest** üretir — uygulamanızın bir çalışma alanına neler eklediğinin eksiksiz bir açıklaması. Bu işlevler, derleme zamanında yapılandırmanızı doğrular ve IDE otomatik tamamlama ile tür güvenliği sağlar.
```
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
```
<Note>
**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. Yukarıdaki klasör yapısı bir gelenektir, zorunluluk değildir.
</Note>
## Varlık türleri
| Varlık | Amaç | Belgeler |
| ---------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **Uygulama** | Uygulama kimliği, varsayılan rol, değişkenler | [Uygulama Yapılandırması](/l/tr/developers/extend/apps/config/application) |
| **Rol** | Nesneler ve alanlar üzerindeki izin kümeleri | [Roller ve İzinler](/l/tr/developers/extend/apps/config/roles) |
| **Nesne** | Alanlara sahip özel kayıt türleri | [Nesneler](/l/tr/developers/extend/apps/data/objects) |
| **Alan** | Diğer uygulamalardaki nesnelere alan ekleyin | [Nesneleri Genişletme](/l/tr/developers/extend/apps/data/extending-objects) |
| **İlişki** | Nesneler arasındaki çift yönlü bağlantılar | [İlişkiler](/l/tr/developers/extend/apps/data/relations) |
| **Mantık İşlevi** | Tetikleyicilerle sunucu tarafı TypeScript | [Mantıksal İşlevler](/l/tr/developers/extend/apps/logic/logic-functions) |
| **Beceri** | Yeniden kullanılabilir yapay zekâ temsilcisi yönergeleri | [Beceriler ve Temsilciler](/l/tr/developers/extend/apps/logic/skills-and-agents) |
| **Temsilci** | Özel istemlere sahip yapay zekâ asistanları | [Beceriler ve Temsilciler](/l/tr/developers/extend/apps/logic/skills-and-agents) |
| **Bağlantı Sağlayıcısı** | Üçüncü taraf API'ler için OAuth kimlik bilgileri | [Bağlantılar](/l/tr/developers/extend/apps/logic/connections) |
| **Görünüm** | Önceden yapılandırılmış kayıt listesi görünümleri | [Görünümler](/l/tr/developers/extend/apps/layout/views) |
| **Gezinme Menüsü Öğesi** | Özel kenar çubuğu öğeleri | [Gezinme Menüsü Öğeleri](/l/tr/developers/extend/apps/layout/navigation-menu-items) |
| **Sayfa Düzeni** | Bir kaydın ayrıntı sayfasındaki sekmeler ve widget'lar | [Sayfa Düzenleri](/l/tr/developers/extend/apps/layout/page-layouts) |
| **Ön Uç Bileşeni** | Twenty içinde korumalı alandaki React kullanıcı arayüzü | [Ön Uç Bileşenleri](/l/tr/developers/extend/apps/layout/front-components) |
| **Paylaşılan Bağımlılıklar** | Uygulamanın ön uç bileşenleri tarafından paylaşılan ve package.json dosyasında bildirilen bağımlılıklar | [Paylaşılan Bağımlılıklar](/l/tr/developers/extend/apps/layout/front-components#sharing-dependencies-across-front-components) |
| **Komut Menüsü Öğesi** | Hızlı işlemler ve Cmd+K girdileri | [Komut Menüsü Öğeleri](/l/tr/developers/extend/apps/layout/command-menu-items) |
## Korumalı alan
* **Mantık işlevleri** sunucuda yalıtılmış Node.js işlemlerinde çalışır. Verilere yalnızca, çalıştırmayı tetikleyen kişi veya uygulamanın kendisi olarak hareket eden tür güvenlikli API istemcisi aracılığıyla erişirler. [Bir çağrının hangi erişimi kullandığına](/l/tr/developers/extend/apps/logic/logic-functions#whose-access-a-call-uses) bakın.
* **Ön uç bileşenleri**, Remote DOM kullanan Web Worker'larda çalışır — ana sayfadan yalıtılmıştır ancak yerel DOM öğelerini (iframe'ler değil) oluşturur. Twenty ile mesaj iletimi yapan bir ana makine API'si aracılığıyla iletişim kurarlar.
* **İzinler**, API düzeyinde uygulanır. Her ikisi de `defineApplicationRole()` ile tanımlanan rolden türetilen iki çalışma zamanı belirteci eklenir; varsayılan belirteç, çalıştırmanın arkasında biri olduğunda tetikleyen kişinin yapabilecekleriyle daha da sınırlandırılır.
## Uygulama yaşam döngüsü
```
┌─────────────────────────────────────────────────────────┐
│ Development │
│ npx create-twenty-app → yarn twenty dev (live sync) │
├─────────────────────────────────────────────────────────┤
│ Build & Deploy │
│ yarn twenty dev:build → yarn twenty app:publish │
├─────────────────────────────────────────────────────────┤
│ Install flow │
│ upload → [pre-install] → metadata migration → │
│ generate SDK → [post-install] → … → [uninstall] │
├─────────────────────────────────────────────────────────┤
│ Publish │
│ npm publish → appears in Twenty marketplace │
└─────────────────────────────────────────────────────────┘
```
* **`yarn twenty dev`** — kaynak dosyalarınızı izler ve bağlı bir Twenty sunucusuna değişiklikleri canlı olarak senkronize eder. Şema değiştiğinde tipli API istemcisi otomatik olarak yeniden oluşturulur.
* **`yarn twenty dev:build`** — TypeScript'i derler, mantık işlevlerini ve ön uç bileşenlerini esbuild ile paketler ve bir manifest üretir.
* **Kurulum öncesi/sonrası ve kaldırma kancaları** — kurulum sırasında veya kaldırılmadan hemen önce çalışan isteğe bağlı işlevler. Ayrıntılar için [Kurulum Kancaları](/l/tr/developers/extend/apps/config/install-hooks) bölümüne bakın.
## Sonraki adımlar
<CardGroup cols={2}>
<Card title="Yapılandırma" icon="screwdriver-wrench" href="/l/tr/developers/extend/apps/config/overview">
Uygulama kimliği, varsayılan rol ve kurulum ile kaldırma kancaları.
</Card>
<Card title="Veri" icon="database" href="/l/tr/developers/extend/apps/data/overview">
Nesneler, alanlar ve çift yönlü ilişkiler.
</Card>
<Card title="Mantık" icon="bolt" href="/l/tr/developers/extend/apps/logic/overview">
Mantık işlevleri, beceriler, temsilciler ve OAuth bağlantıları.
</Card>
<Card title="Düzen" icon="table-columns" href="/l/tr/developers/extend/apps/layout/overview">
Görünümler, gezinme, sayfa düzenleri, ön uç bileşenleri.
</Card>
<Card title="İşlemler" icon="rocket" href="/l/tr/developers/extend/apps/operations/overview">
CLI, test, uzak depolar, CI ve uygulamanızı yayımlama.
</Card>
</CardGroup>

View File

@@ -0,0 +1,87 @@
---
title: Yerel Sunucu
description: Yerel Twenty Docker sunucusunu yönetin — başlatın, durdurun, yükseltin, paralel test örneği çalıştırın ve manuel SDK kurulumu yapın.
icon: server
---
## Yerel sunucuyu yönetme
Yerel Twenty konteynerini kontrol etmek için `yarn twenty docker:*` kullanın:
| Komut | Ne yapar |
| -------------------------------------- | --------------------------------------------------------- |
| `yarn twenty docker:start` | Sunucuyu başlatır (gerekirse imajı çeker) |
| `yarn twenty docker:start 2.2.0` | Belirli bir sunucu sürümünü başlatın |
| `yarn twenty docker:start --port 3030` | Özel bir portta başlatır |
| `yarn twenty docker:stop` | Sunucuyu durdurur (verileri korur) |
| `yarn twenty docker:status` | URL'yi, sürümü ve oturum açma kimlik bilgilerini gösterir |
| `yarn twenty docker:logs` | Sunucu günlüklerini akış olarak gösterir |
| `yarn twenty docker:reset` | Verileri siler ve sıfırdan başlatır |
| `yarn twenty docker:upgrade` | En güncel `twenty-app-dev` imajını çeker |
| `yarn twenty docker:upgrade 2.2.0` | Belirli bir sürüme yükseltir |
Veriler, yeniden başlatmalar arasında iki Docker biriminde kalıcıdır (PostgreSQL için `twenty-app-dev-data`, dosyalar için `twenty-app-dev-storage`). Her şeyi silmek için `reset` kullanın.
## Sunucu sürümünü sabitleme
Herhangi bir sürüm iletilmediğinde, `docker:start` sürümü uygulamanızın `package.json` içindeki `engines.twenty` aralığından çözümler — bu, uygulamanız yüklendiğinde sunucunun doğruladığı aralıkla aynıdır. Bu komut, aralığı karşılayan en yeni yayımlanmış `twenty-app-dev` imajını başlatır; alan yoksa veya hiçbir yayımlanmış sürüm eşleşmiyorsa `latest` değerine geri döner:
```json filename="package.json"
{
"engines": {
"twenty": ">=2.2.0"
}
}
```
Aralığı tek bir çalıştırma için geçersiz kılmak amacıyla sürümü açıkça iletin: `yarn twenty docker:start 2.3.0`. Bir kapsayıcı zaten farklı bir sürümde mevcutsa, `docker:start` onu yerinde yükseltir (veri birimlerinizi korurken kapsayıcıyı yeniden oluşturarak).
## Sunucu imajını yükseltme
`yarn twenty docker:upgrade`, en güncel imajı çeker, özetleri karşılaştırır ve yalnızca gerçekten bir şey değiştiyse konteyneri yeniden oluşturur. Birimler korunur — yalnızca konteyner değiştirilir. Yeni bir imaj çekildiyse ve konteyner çalışıyorsa, yükseltme otomatik olarak yeni bir konteyner başlatır; sağlıklı hale gelmesini beklemek için ardından `yarn twenty docker:start` çalıştırın.
```bash filename="Terminal"
yarn twenty docker:upgrade # Latest
yarn twenty docker:upgrade 2.2.0 # Specific version
```
Çalışan sürümü `yarn twenty docker:status` ile doğrulayabilirsiniz (konteynere gömülü `APP_VERSION` değerini gösterir).
## Paralel bir test örneği çalıştırma
Ana geliştirme verilerinize dokunmadan entegrasyon testlerini veya denemeleri yürütmek için, herhangi bir `docker:*` komutuna `--test` parametresini vererek ikinci, tamamen yalıtılmış bir örneği yönetebilirsiniz:
| Komut | Ne yapar |
| ----------------------------------- | ------------------------------------------------------------- |
| `yarn twenty docker:start --test` | Test örneğini başlatır (varsayılan bağlantı noktası 2021'dir) |
| `yarn twenty docker:stop --test` | Durdurur |
| `yarn twenty docker:status --test` | Durumunu gösterir |
| `yarn twenty docker:logs --test` | Günlüklerini akış olarak gösterir |
| `yarn twenty docker:reset --test` | Verilerini siler |
| `yarn twenty docker:upgrade --test` | İmajını yükseltir |
Test örneği, kendine ait bir Docker konteynerinde (`twenty-app-dev-test`), ayrılmış birimlerle (`twenty-app-dev-test-data`, `twenty-app-dev-test-storage`) ve yapılandırmayla çalışır; böylece ana örneğinizle çakışma olmadan paralel olarak çalışabilir. Varsayılan 2021'i geçersiz kılmak için `--test` ile `--port`'u birlikte kullanın.
## Manuel kurulum (iskelet oluşturucu olmadan)
SDK'yı mevcut bir projeye ekliyorsanız iskelet oluşturma adımını atlayın:
```bash filename="Terminal"
yarn add twenty-sdk twenty-client-sdk
```
`package.json` dosyasına betiği ekleyin:
```json filename="package.json"
{
"scripts": {
"twenty": "twenty"
}
}
```
Artık `yarn twenty dev`, `yarn twenty docker:start` ve diğerlerini çalıştırabilirsiniz.
<Note>
`twenty-sdk`'yi global olarak kurmayın — her uygulama kendi sürümünü kullansın diye proje bazında sabitleyin.
</Note>

View File

@@ -0,0 +1,79 @@
---
title: Proje yapısı
description: Önceden hazırlanmış bir Twenty uygulamasının içinde neler var — dosyalar, klasörler ve her birinin ne yaptığı.
icon: folder-tree
---
`npx create-twenty-app` ile oluşturulan yeni bir uygulama şöyle görünür:
```text filename="my-twenty-app/"
my-twenty-app/
package.json
src/
application-config.ts # Required — your app's entry point
default-role.ts # Permissions for logic functions
constants/
universal-identifiers.ts # Auto-generated UUIDs and metadata
front-components/
main-page.tsx # Welcome page component
navigation-menu-items/
main-page.navigation-menu-item.ts # Sidebar entry for the welcome page
page-layouts/
main-page.page-layout.ts # Standalone page hosting the component
__tests__/
application-config.test.ts # Unit test
global-setup.ts # Integration test setup (sync + uninstall)
schema.integration-test.ts # Integration test against a live server
.github/workflows/
ci.yml # Lint, typecheck, unit + integration tests
cd.yml # Deploy + install on push to main
publish.yml # Publish to npm on version tags (with provenance)
public/
logo.svg # Static assets
vitest.config.ts # Integration test runner config
vitest.unit.config.ts # Unit test runner config
tsconfig.json, tsconfig.spec.json
.nvmrc, .yarnrc.yml, .oxlintrc.json
README.md, AGENTS.md, CLAUDE.md, CHANGELOG.md, SETUP.md
```
## Temel dosyalar
| Dosya / Klasör | Amaç |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `src/application-config.ts` | **Gerekli.** Uygulamanızın ana yapılandırma dosyası. |
| `src/default-role.ts` | Mantık işlevlerinizin neye erişebileceğini denetleyen varsayılan rol. |
| `src/constants/universal-identifiers.ts` | Otomatik oluşturulan UUID'ler ve meta veriler (görünen ad, açıklama). |
| `src/front-components/`, `src/navigation-menu-items/`, `src/page-layouts/` | Bir başlangıç karşılama sayfası: kenar çubuğundan erişilebilen, bağımsız bir sayfa düzeni tarafından oluşturulan bir ön bileşen. |
| `src/__tests__/` | Uygulamayı gerçek bir sunucuya karşı senkronize eden (genel kurulumu ile birlikte) bir birim testi artı bir entegrasyon testi. |
| `public/` | Uygulamanızla birlikte sunulan statik varlıklar (görüntüler, yazı tipleri). |
| `AGENTS.md` / `CLAUDE.md` | Uygulama üzerinde çalışan yapay zekâ kodlama ajanları için yönergeler. |
| `CHANGELOG.md` / `SETUP.md` | Önemli değişikliklerin değişiklik günlüğü ve yerel geliştirme için kurulum talimatları. |
<Note>
**Dosya organizasyonu size kalmış.** Yukarıdaki klasörler birer konvansiyondur — SDK, dosyanın nerede olduğundan bağımsız olarak `export default defineEntity(...)` çağrılarını AST analizi yoluyla algılar.
</Note>
## Bağımlılıklar
Her iki Twenty SDK paketi de `dependencies` değil, `devDependencies` altında olmalıdır:
```json filename="package.json"
{
"dependencies": {},
"devDependencies": {
"twenty-client-sdk": "2.20.0",
"twenty-sdk": "2.20.0",
"twenty-ui": "1.0.0-alpha.1"
}
}
```
İskele oluşturucu, `twenty-sdk` ve `twenty-client-sdk` paketlerini kendi sürümüne sabitler — yükseltme yaparken bu ikisini senkron tutun.
* **`twenty-sdk`**, `twenty` CLI'yi ve derleme/iskelet oluşturma araçlarını sağlar. Yalnızca geliştirme ve derleme zamanında çalışır ve yayımlanmış uygulamanızın çalışma zamanında asla içe aktarılmaz.
* **`twenty-client-sdk`** uygulama kodunuz tarafından (`CoreApiClient`, `MetadataApiClient`, `RestApiClient`) içe aktarılır, ancak Twenty bunu çalışma zamanında sağlar — mantık fonksiyonları bunu oluşturulan bir SDK katmanından alır ve ön yüz bileşenleri bunu sunucu tarafından sunulan modüllerden çözümler. Yüklediğiniz kopya yalnızca tür denetimi ve dağıtım zamanındaki derleme için kullanılır, bu yüzden dağıtılan paket ile birlikte gönderilmesine gerek yoktur.
`dependencies` altında bu paketlerden herhangi birinin tutulması, onu kurulu uygulamanın çalışma zamanı paketine dahil eder ve orada gereksiz yük haline getirir. `twenty dev:build`, bunlardan herhangi biri hâlâ `dependencies` altında listelendiğinde bir uyarı verir.
Uygulamanızın kendi çalışma zamanı bağımlılıklarını (mantık fonksiyonlarınızın çalışma zamanında gerçekten içe aktardığı kütüphaneler) her zamanki gibi `dependencies` altına ekleyin. Ağacı sade tutun: `dependencies`, açılmış hali 250 MB ile sınırlandırılmış bir çalışma zamanı katmanına kurulur ve bu sınırın ötesinde bağımlılık boyutu hatasıyla senkronizasyon veya kurulum başarısız olur — UI kütüphaneleri ve geliştirme araçları `devDependencies` içinde yer almalıdır.

View File

@@ -0,0 +1,166 @@
---
title: Hızlı Başlangıç
icon: rocket
description: İlk Twenty uygulamanızı dakikalar içinde oluşturun.
---
## Ön Gereksinimler
* **Node.js 24.5+** — [Buradan indirin](https://nodejs.org/)
* **Yarn 4** — Corepack aracılığıyla Node ile birlikte gelir. Etkinleştirin: `corepack enable`
* **Docker** — [Buradan indirin](https://www.docker.com/products/docker-desktop/). Yerel bir Twenty sunucusunu çalıştırmak için gereklidir. Zaten başka bir yerde Twenty çalıştırıyorsanız atlayın.
Bir Twenty uygulaması oluşturmanın üç aşaması vardır. İskelet oluşturucu bunları tek bir sorunsuz akış komutuna indirger, ancak her aşama ayrı bir kavramdır — bir şeyler başarısız olduğunda hangi aşamada olduğunuzu bilmek neyi düzeltmeniz gerektiğini söyler.
| Aşama | Ne yaparsınız | Araç | Sonuç |
| ---------------------------- | ------------------------------------------------- | ----------------------------------- | ---------------------------------- |
| **1. İskelet Oluşturma** | Uygulamanın kaynak kodunu oluşturun | `npx create-twenty-app` | Diskte bir TypeScript projesi |
| **2. Bir sunucu çalıştırma** | Eşitleme yapacağınız bir Twenty sunucusu başlatın | Docker + `yarn twenty docker:start` | Çalışan bir Twenty örneği |
| **3. Eşitleme** | Kodunuzu sunucuya canlı olarak eşitleyin | `yarn twenty dev` | Değişiklikleriniz arayüzde görünür |
---
## Aşama 1 — Projenizin iskeletini oluşturun
Şablondan yeni bir uygulama oluşturun:
```bash filename="Terminal"
npx create-twenty-app@latest my-twenty-app
```
İskele oluşturucu etkileşimsizdir: dizin adı uygulama adı olur. Oluşturulan üstveriyi özelleştirmek için `--display-name` ve `--description` parametrelerini iletin (bunu daha sonra `src/constants/universal-identifiers.ts` içinde de düzenleyebilirsiniz). Bu, `my-twenty-app/` içinde bir başlangıç `application-config.ts`, varsayılan bir rol, CI/CD iş akışları ve bir entegrasyon testi ile bir TypeScript projesi oluşturur.
**Bu aşamadan sonra:** makinenizde uygulamanın kaynak kodu bulunur. Henüz çalışmıyor — bu, 2. Aşama.
---
## Aşama 2 — Yerel bir Twenty sunucusu çalıştırın
Uygulamanızın eşitleme yapacağı bir Twenty sunucusuna ihtiyacı vardır. Sunucu, Docker içinde yerel olarak çalışan tam bir Twenty örneğidir — UI, GraphQL API, PostgreSQL. Yerel kodunuz tanımlarını bu sunucuya yükler; bu da onların arayüzde görünmesini sağlar.
İskele oluşturucu bunu sizin için başlatır: Docker çalışırken `twentycrm/twenty-app-dev` imajını çeker, onu `2020` portunda başlatır ve CLI'yı önceden doldurulmuş demo çalışma alanına (`tim@apple.dev`) karşı kimlik doğrular — oturum açmanız gerekmez.
Bunun yerine mevcut bir Twenty sunucusuna bağlanmak için `--url \<your-server-url>` parametresini iletin. Uzak sunucular OAuth ile kimlik doğrular: oturum açabilmeniz ve **Authorize**'a tıklayabilmeniz için bir tarayıcı açılır; bu, CLI'ye çalışma alanınıza erişim sağlar. (Yerelde de `--authentication-method oauth` ile OAuth kullanmayı tercih edebilirsiniz — `tim@apple.dev` / `tim@apple.dev` ile oturum açın.)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty oturum açma ekranı" />
</div>
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty CLI yetkilendirme ekranı" />
</div>
Terminaliniz her şeyin kurulduğunu onaylayacaktır.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Uygulama iskeleti başarıyla oluşturuldu" />
</div>
**Bu aşamadan sonra:** [http://localhost:2020](http://localhost:2020) adresinde çalışan bir Twenty sunucunuz ve ona eşitleme yapmak için yetkilendirilmiş bir CLI'ınız olur.
<Note>
Docker yüklü değilse veya çalışmıyorsa, iskelet oluşturucu işletim sisteminiz için doğru başlatma komutunu söyleyecektir. Docker çalışır duruma geldiğinde, `yarn twenty docker:start` ile devam edebilirsiniz — yeniden iskelet oluşturmaya gerek yok.
</Note>
---
## Aşama 3 — Değişikliklerinizi eşitleyin
Zamanınızın çoğunu geçireceğiniz iç döngü budur.
```bash filename="Terminal"
cd my-twenty-app
yarn twenty dev
```
Bu, `src/` dizinini izler, her değişiklikte yeniden oluşturur ve sonucu sunucuya eşitler. Bir dosyayı düzenleyin, kaydedin ve birkaç saniye içinde sunucu değişikliği yansıtır. Terminalinizde canlı bir durum paneli göreceksiniz.
Daha ayrıntılı çıktı (derleme günlükleri, eşitleme istekleri, hata izleri) için `--verbose` ekleyin.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.png" alt="Geliştirme modu terminal çıktısı" />
</div>
[http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) adresini açın. Uygulamanızı **Your Apps** altında görmelisiniz.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Your Apps listesinde My twenty app gösteriliyor" />
</div>
**My twenty app**'e tıklayarak **uygulama kaydını** görüntüleyin — uygulamanızı tanımlayan (ad, tanımlayıcı, OAuth kimlik bilgileri, kaynak) sunucu düzeyinde bir kayıttır. Tek bir kayıt, aynı sunucudaki birden çok çalışma alanına kurulabilir.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Uygulama kaydı ayrıntıları" />
</div>
Çalışma alanındaki kurulumu görmek için **View installed app**'e tıklayın. **About** sekmesi sürümü ve yönetim seçeneklerini gösterir.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Yüklü uygulama" />
</div>
**Bu aşamadan sonra:** canlı bir geliştirme döngünüz olur. `src/` içindeki herhangi bir dosyayı düzenleyin; arayüzde görünür.
### CI ve betikler için tek seferlik eşitleme
Bir izleyici olmadan aynı boru hattını bir kez çalıştırmak için `plan` ve `apply` komutlarını kullanın:
```bash filename="Terminal"
yarn twenty plan # preview the metadata changes without applying them
yarn twenty apply # show the plan, then apply it
```
| Komut | Davranış | Ne zaman kullanılmalı |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `yarn twenty dev` | Her değişiklikte izler ve yeniden eşitler. Siz durdurana kadar çalışır. | Etkileşimli yerel geliştirme. |
| `yarn twenty apply` | Tek derleme + eşitleme; başarıda `0`, başarısızlıkta `1` ile çıkar. Yıkıcı değişiklikler için onay ister (atlamak için `--force` parametresini iletin). | CI, pre-commit kancaları, AI ajanları, betiklenmiş iş akışları. |
| `yarn twenty plan` | Meta veri değişikliklerini **uygulamadan oluşturur ve yazdırır**. | Bir senkronizasyonun, ona başlamadan önce neleri değiştireceğini incelemek. |
Tüm kiplerin kimliği doğrulanmış bir uzak sunucuya ihtiyacı vardır. `plan` hakkında daha fazla bilgi için [Senkronizasyon ve kurtarma](/l/tr/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) bölümüne bakın.
<Note>
`yarn twenty dev --once` ve `yarn twenty dev --once --dry-run`, `yarn twenty apply` ve `yarn twenty plan` için kullanımdan kaldırılmış takma adlardır.
</Note>
### Geliştirme kipi seçenekleri
| Bayrak | Açıklama |
| ------------------------------------- | ------------------------------------------------------------------------------------------ |
| `--force` | Onay olmadan yıkıcı değişiklikleri (silme işlemlerini) uygular. |
| `--debounceMs \<ms>` | Dosya değişikliği geciktirme süresini milisaniye cinsinden ayarlayın (varsayılan: `1000`). |
| `--verbose` / `--debug` | Ayrıntılı derleme günlüklerini, eşitleme isteklerini ve hata izlerini gösterin. |
## Oluşturabilecekleriniz
Uygulamalar **varlıklardan** oluşur — her biri tek bir `export default` içeren bir TypeScript dosyası olarak tanımlanır:
| Varlık | Ne yapar |
| ------------------------- | --------------------------------------------------------------------------------------------------------- |
| **Nesneler ve Alanlar** | Özel veri modelleri (Post Card, Invoice vb.) tiplendirilmiş alanlarla |
| **Mantıksal işlevler** | HTTP rotaları, cron zamanlamaları veya veritabanı olayları tarafından tetiklenen sunucu tarafı TypeScript |
| **Ön uç bileşenleri** | Twenty'nin arayüzünde görüntülenen React bileşenleri (yan panel, widget'lar, komut menüsü) |
| **Beceriler ve Aracılar** | Yapay zeka yetenekleri — yeniden kullanılabilir yönergeler ve otonom asistanlar |
| **Görünümler ve Gezinme** | Önceden yapılandırılmış liste görünümleri ve kenar çubuğu menü öğeleri |
| **Sayfa düzenleri** | Sekmeler ve widget'lar içeren özel kayıt ayrıntı sayfaları |
Tam başvuru: [Kavramlar](/l/tr/developers/extend/apps/getting-started/concepts).
## Sonraki adımlar
<CardGroup cols={2}>
<Card title="Yapılandırma" icon="screwdriver-wrench" href="/l/tr/developers/extend/apps/config/overview">
Uygulama kimliği, varsayılan rol, kurulum ve kaldırma kancaları, genel varlıklar.
</Card>
<Card title="Veri" icon="database" href="/l/tr/developers/extend/apps/data/overview">
Nesneler, alanlar ve çift yönlü ilişkiler.
</Card>
<Card title="Mantık" icon="bolt" href="/l/tr/developers/extend/apps/logic/overview">
Mantık işlevleri, beceriler, aracılar ve OAuth bağlantıları.
</Card>
<Card title="Düzen" icon="table-columns" href="/l/tr/developers/extend/apps/layout/overview">
Görünümler, gezinme, sayfa düzenleri, ön uç bileşenleri.
</Card>
<Card title="İşlemler" icon="rocket" href="/l/tr/developers/extend/apps/operations/overview">
CLI, test, uzak uçlar, CI ve uygulamanızı yayımlama.
</Card>
</CardGroup>

View File

@@ -0,0 +1,63 @@
---
title: İskelet Oluşturma
description: Nesne, alan, görünüm, mantık işlevi ve daha fazlasına ait varlık dosyalarını `yarn twenty dev:add` komutuyla etkileşimli olarak oluşturun.
icon: wand-magic-sparkles
---
Varlık dosyalarını elle oluşturmak yerine etkileşimli iskelet oluşturucuyu kullanın:
```bash filename="Terminal"
yarn twenty dev:add
```
Bir varlık türü seçmeniz için sizi yönlendirir ve gerekli alanlarda adım adım ilerlemenizi sağlar, ardından sabit bir `universalIdentifier` ve doğru `defineEntity()` çağrısıyla kullanıma hazır bir dosya yazar.
İlk istemi atlamak için varlık türünü doğrudan da geçebilirsiniz:
```bash filename="Terminal"
yarn twenty dev:add object
yarn twenty dev:add logicFunction
yarn twenty dev:add frontComponent
```
## Kullanılabilir varlık türleri
| Varlık türü | Komut | Oluşturulan dosya |
| ----------------------------- | ------------------------------------------ | --------------------------------------------------------- |
| Nesne | `yarn twenty dev:add object` | `src/objects/\<name>.ts` |
| Alan | `yarn twenty dev:add field` | `src/fields/\<name>.ts` |
| Mantık işlevi | `yarn twenty dev:add logicFunction` | `src/logic-functions/\<name>.ts` |
| Ön uç bileşeni | `yarn twenty dev:add frontComponent` | `src/front-components/\<name>.tsx` |
| Rol | `yarn twenty dev:add role` | `src/roles/\<name>.ts` |
| Beceri | `yarn twenty dev:add skill` | `src/skills/\<name>.ts` |
| Temsilci | `yarn twenty dev:add agent` | `src/agents/\<name>.ts` |
| Görünüm | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| Gezinme menüsü öğesi | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Sayfa düzeni | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| Sayfa düzeni sekmesi | `yarn twenty dev:add pageLayoutTab` | `src/page-layout-tabs/\<name>.ts` |
| Komut menüsü öğesi | `yarn twenty dev:add commandMenuItem` | `src/command-menu-items/\<name>.ts` |
| Görünüm alanı | `yarn twenty dev:add viewField` | `src/view-fields/\<name>.ts` |
| Bağlantı sağlayıcısı | `yarn twenty dev:add connectionProvider` | `src/connection-providers/\<name>.ts` |
| Zaman Çizelgesi Aktivite Türü | `yarn twenty dev:add timelineActivityType` | `src/timeline-activity-types/\<name>.ts` |
## İskelet oluşturucunun ürettikleri
Her varlık türünün kendi şablonu vardır. Örneğin, `yarn twenty dev: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 dev:add logicFunction --path src/custom-folder
```

View File

@@ -0,0 +1,14 @@
---
title: Sorun Giderme
description: Yaygın ilk çalıştırma sorunları — Docker, Node sürümü, Yarn, bağımlılıklar.
icon: wrench
---
* **Docker hataları** — `yarn twenty docker:start` öncesinde Docker Desktop'ın (veya daemon'un) çalıştığından emin olun. Hata iletisi, işletim sisteminiz için doğru başlatma komutunu gösterecektir.
* **Yanlış Node sürümü** — 24.5+ gerekiyor (`engines.node: ^24.5.0`). `node -v` ile kontrol edin.
* **Yarn 4 eksik** — `corepack enable` çalıştırın.
* **Bağımlılıklar bozuk** — `rm -rf node_modules && yarn install`.
* **`twenty-sdk` v2.8.0 sürümüne yükselttikten sonra oluşan hatalar** — v2.8.0 sürümünde `dependencies` içinden `devDependencies` içine taşındı. [Proje Yapısı → Bağımlılıklar](/l/tr/developers/extend/apps/getting-started/project-structure#dependencies) bölümüne bakın.
* **`twenty dev:build`, `dependencies` altındaki `twenty-client-sdk` konusunda uyarır** — Bu paket, çalışma zamanında Twenty tarafından sağlanır, bu nedenle `twenty-sdk` ile birlikte `devDependencies` altına taşınmalıdır. [Proje Yapısı → Bağımlılıklar](/l/tr/developers/extend/apps/getting-started/project-structure#dependencies) bölümüne bakın.
Takıldınız mı? [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322) üzerinde yardım isteyin.

View File

@@ -0,0 +1,118 @@
---
title: Komut menüsü öğeleri
description: Ön bileşenleri hızlı işlemler ve komut menüsü (Cmd+K) girdileri olarak `defineCommandMenuItem` ile sunun.
icon: terminal
---
Bir **komut menüsü öğesi**, kullanıcı ile bir [ön bileşen](/l/tr/developers/extend/apps/layout/front-components) arasında köprü görevi görür. Bu, bileşeni Twenty'nin komut menüsüne (Cmd+K) ve isteğe bağlı olarak sayfanın sağ üst köşesindeki sabitlenmiş bir hızlı işlem düğmesi olarak kaydeder.
```ts src/command-menu-items/open-dashboard.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
label: 'Open Dashboard',
shortLabel: 'Dashboard',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```
## Yapılandırma alanları
| 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 |
| `frontComponentUniversalIdentifier` | Evet | Bu komutun açtığı ön bileşenin `universalIdentifier` değeri |
| `shortLabel` | Hayır | Sabitlenmiş hızlı işlem düğmesinde görüntülenen daha kısa etiket |
| `icon` | Hayır | **Kullanımdan kaldırıldı** — uygulama simgesi tercih edildiği için yok sayılır; ayarlanırsa oluşturma sırasında bir uyarı verir |
| `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), `'GLOBAL_OBJECT_CONTEXT'` (yalnızca bir nesne bağlamı olan sayfalarda — indeks ve kayıt sayfaları), `'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 | Görünürlüğü dinamik olarak kontrol eden bir boolean ifade (aşağıya bakın) |
## Arayüzsüz komutlar
Bir [arayüzsüz ön bileşen](/l/tr/developers/extend/apps/layout/front-components#headless-vs-non-headless) ile eşleştirilmiş bir komut menüsü öğesi, tek tıklamayla bir eylem sunmanın — kod çalıştırma, gezinme veya onaylayıp yürütme — yaygın kullanılan biçimidir. Ön Bileşenler sayfası, eylem-ve-kaldırma modelini yöneten [SDK Command bileşenlerini](/l/tr/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`) kapsar.
Tipik bir akış: başsız bir bileşen `<Command execute={...} />` oluşturur (bkz. [tam örnek](/l/tr/developers/extend/apps/layout/front-components#sdk-command-components)) ve komut menüsü öğesi ona işaret eder:
```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
## 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:
```ts src/command-menu-items/bulk-update.command-menu-item.ts
import {
defineCommandMenuItem,
objectPermissions,
everyEquals,
} from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: '...',
label: 'Bulk Update',
availabilityType: 'RECORD_SELECTION',
frontComponentUniversalIdentifier: '...',
conditionalAvailabilityExpression: everyEquals(
objectPermissions,
'canUpdateObjectRecords',
true,
),
});
```
<Note>
`RECORD_SELECTION` zaten boş olmayan bir seçim anlamına gelir — `numberOfSelectedRecords` ifadesini yalnızca belirli sayılar için kullanın (örneğin `>= 2`).
</Note>
### 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` |

View File

@@ -0,0 +1,849 @@
---
title: Ön uç bileşenleri
description: Twenty'nin UI'si içinde, korumalı alan (sandbox) izolasyonu ile görüntülenen React bileşenleri oluşturun.
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, korumalı (sandbox) ve opak kökenli bir iframe içinde yürütülür; ancak UI, o iframe ile sınırlanmak yerine sayfada yerel olarak işlenir.
<Warning>
Front bileşenleri hâlâ aktif geliştirme aşamasındadır. Kodunuz gerçek bir tarayıcı sayfası yerine kısmi bir DOM üzerinde çalışır, bu nedenle ileri düzey kullanımlar çoğu zaman sessizce başarısız olabilir. [Mevcut sınırlamalara](#current-limitations) bakın.
</Warning>
## Ön uç bileşenlerinin kullanılabileceği yerler
Ön uç bileşenler, Twenty içinde üç 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üzenleri](/l/tr/developers/extend/apps/layout/page-layouts) içinde 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.
* **Uygulama ayarları** — [`defineSettingsFrontComponent()`](#custom-settings-component) ile tanımlandığında, ön uç bileşeni, varsayılan değişken yapılandırma kullanıcı arayüzü yerine uygulamanın **Settings** sekmesi içinde bir bölüm olarak görüntülenir.
Tek başına bir ön uç bileşenine kullanıcı arayüzünden erişilemez — onu görünür hâle getirmeniz gerekir. Bunu yapmanın üç yolu vardır:
* **Onu bir [komut menüsü öğesi](/l/tr/developers/extend/apps/layout/command-menu-items) ile eşleştirin** — komut menüsüne (Cmd+K) ve isteğe bağlı olarak sabitlenmiş hızlı işlem olarak kaydeder.
* **Onu bir [sayfa düzeni](/l/tr/developers/extend/apps/layout/page-layouts) içinde widget olarak gömün** — bir kaydın ayrıntı sayfasına veya panosuna yerleştirir.
* **Bunu [`defineSettingsFrontComponent()`](#custom-settings-component) ile tanımlayın** — varsayılan değişken yapılandırma kullanıcı arayüzü yerine uygulamanın **Settings** sekmesi içinde bir bölüm olarak görüntülenir.
## Basit örnek
Bir ön uç bileşenini çalışır halde görmenin en hızlı yolu, onu [`defineCommandMenuItem`](/l/tr/developers/extend/apps/layout/command-menu-items) ile eşleştirmektir; böylece sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak görünür:
```tsx src/front-components/hello-world.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
const HelloWorld = () => {
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>Hello from my app!</h1>
<p>This component renders inside Twenty.</p>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
name: 'hello-world',
description: 'A simple front component',
component: HelloWorld,
});
```
```ts src/command-menu-items/hello-world.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
shortLabel: 'Hello',
label: 'Hello World',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```
`yarn twenty dev` ile senkronize ettikten sonra (veya tek seferlik bir `yarn twenty apply` çalıştırdıktan sonra), hızlı işlem sayfanın sağ üst köşesinde görünür:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Sağ üst köşedeki hızlı işlem düğmesi" />
</div>
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) |
## 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 [Sayfa Düzenleri](/l/tr/developers/extend/apps/layout/page-layouts) bölümüne bakın.
## Özel ayarlar bileşeni
Uygulamanızın **Settings** sekmesindeki otomatik oluşturulan değişken yapılandırma kullanıcı arayüzünü kendi bileşeninizle değiştirmek için, `defineFrontComponent` yerine `defineSettingsFrontComponent` ile tanımlayın. Ayarlar bileşeni her zaman görünür bir kullanıcı arayüzü oluşturduğundan, kabul edilmeyen `isHeadless` dışında, aynı [yapılandırma alanlarını](#configuration-fields) alır ve ek olarak bileşeni uygulamanın ayarlar arayüzü olarak işaretler.
Bileşen, tüm sekmenin yerine geçmek yerine, Settings sekmesi içinde bir bölüm olarak görüntülenir. Twenty'nin sistem tarafından yönetilen bölümleri — otomatik yükseltme, App URL ve bağlantılar — her zaman bunun üstünde görüntülenir ve uygulama tarafından geçersiz kılınamaz.
```tsx src/front-components/app-settings.tsx
import { defineSettingsFrontComponent } from 'twenty-sdk/define';
const AppSettings = () => {
return (
<div style={{ padding: '20px' }}>
<h2>My app settings</h2>
{/* render your own configuration UI here */}
</div>
);
};
export default defineSettingsFrontComponent({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'app-settings',
description: "Custom UI for the app's Settings tab",
component: AppSettings,
});
```
Uygulama başına yalnızca bir ayarlar ön uç bileşenine izin verilir; birden fazla tanımlanması derlemenin başarısız olmasına neden olur. Mevcut olduğunda, uygulamanın **Settings** sekmesi, varsayılan değişken yapılandırma kullanıcı arayüzü yerine bu bileşeni görüntüler.
## 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 { useSelectedRecordIds, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useEffect } from 'react';
const SyncTracker = () => {
const [recordId] = useSelectedRecordIds();
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 uç 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/front-component` 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`** — Bir yan panel sayfasını açar. Props, `page`'e bağlıdır — örneğin `ViewRecord`, `recordId` + `objectNameSingular` alır (ayrıca kaydı belirli bir sekmede açmak için isteğe bağlı bir `tab` kimliği alır), diğer sayfalar `pageTitle` + `pageIcon` alır.
`Command` kullanarak komut menüsünden bir eylem çalıştıran headless bir ön uç bileşenin tam örneği:
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const RunAction = () => {
const execute = async () => {
const client = new CoreApiClient();
await client.mutation({
createTask: {
__args: { data: { title: 'Created by my app' } },
id: true,
},
});
};
return <Command execute={execute} />;
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
name: 'run-action',
description: 'Creates a task from the command menu',
component: RunAction,
isHeadless: true,
});
```
```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
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/front-component';
const DeleteDraft = () => {
const execute = async () => {
// perform the deletion
};
return (
<CommandModal
title="Delete draft?"
subtitle="This action cannot be undone."
execute={execute}
confirmButtonText="Delete"
confirmButtonAccent="danger"
/>
);
};
export default defineFrontComponent({
universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456',
name: 'delete-draft',
description: 'Deletes a draft with confirmation',
component: DeleteDraft,
isHeadless: true,
});
```
Ve geçerli kaydı belirli bir sekmede yan panelde açmak için `CommandOpenSidePanelPage` kullanan bir örnek. `tab`, bir sayfa düzeni sekme kimliğidir (varsayılan düzenler `company-tab-emails` veya `company-tab-timeline` gibi kimlikler kullanır; özel düzenler sekmenin kendi kimliğini kullanır). Kimlik kaydın düzeninde mevcut değilse, bunun yerine varsayılan sekme açılır:
```tsx src/front-components/open-company-emails.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import {
CommandOpenSidePanelPage,
SidePanelPages,
useSelectedRecordIds,
} from 'twenty-sdk/front-component';
const OpenCompanyEmails = () => {
const selectedRecordIds = useSelectedRecordIds();
const recordId = selectedRecordIds.length === 1 ? selectedRecordIds[0] : null;
if (!recordId) {
return null;
}
return (
<CommandOpenSidePanelPage
page={SidePanelPages.ViewRecord}
recordId={recordId}
objectNameSingular="company"
tab="company-tab-emails"
resetNavigationStack={false}
/>
);
};
export default defineFrontComponent({
universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567',
name: 'open-company-emails',
description: 'Opens the current company on its Emails tab',
component: OpenCompanyEmails,
isHeadless: true,
});
```
## Bir mantık işlevini çağırma
Ön bileşenler, tarayıcı tarafında, opak kaynaklı bir iframe içinde yer alan korumalı (sandbox) bir Web Worker içinde çalışırken, [mantık işlevleri](/l/tr/developers/extend/apps/logic/logic-functions) sunucu tarafında çalışır. İkisi arasında doğrudan, işlem içi bir çağrı yoktur — bunun yerine, bir ön bileşen bir mantık işlevine HTTP üzerinden erişir.
`httpRouteTriggerSettings` ile bildirilen bir mantık işlevi, rota yolunda HTTP üzerinden erişilebilir durumdadır. `RestApiClient`, `/s/` ile başlayan yolları uygulama rotaları olarak ele alır, bunları fonksiyonlarınızın sunulduğu URL'ye çözümler ve `TWENTY_APP_ACCESS_TOKEN` ile kimlik doğrulaması yapar.
> **Twenty Cloud üzerinde, HTTP ile tetiklenen mantık işlevleri, çalışma alanı başına ayrılmış özel bir etki alanında** `https://\<your-workspace-subdomain>.withtwenty.com\<path>` adresinde sunulur. Harici çağrıcılar için, tam URL’yi işlevin **HTTP trigger** ayarlarından veya uygulamanın **Settings** sekmesinden kopyalayın.
Başsız bir ön bileşen, çağrıyı `Command` bileşeni aracılığıyla mount sırasında çalıştırabilir ve ardından otomatik olarak unmount olabilir:
```tsx src/front-components/sync-prs.tsx
import { RestApiClient } from 'twenty-client-sdk/rest';
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/front-component';
const SyncPrs = () => {
const execute = async () => {
await new RestApiClient().post('/s/github/fetch-prs', {
owner: 'twentyhq',
repo: 'twenty',
});
};
return <Command execute={execute} />;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'sync-prs',
description: 'Triggers the fetch-prs logic function',
isHeadless: true,
component: SyncPrs,
});
```
`RestApiClient`'e iletilen yol, başına `/s` eklenmiş mantık işlevinin `httpRouteTriggerSettings.path` değeridir. `isAuthRequired: true` ayarını koruyun; bileşeniniz için Twenty'nin oluşturduğu `TWENTY_APP_ACCESS_TOKEN` isteği kimlik doğrular:
```ts src/logic-functions/fetch-prs.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/logic-function';
const handler = async (event: RoutePayload) => {
const { owner, repo } = (event.body ?? {}) as { owner: string; repo: string };
// ...fetch from GitHub and persist records...
return { ok: true };
};
export default defineLogicFunction({
universalIdentifier: '...',
name: 'fetch-prs',
handler,
httpRouteTriggerSettings: {
path: '/github/fetch-prs',
httpMethod: 'POST',
isAuthRequired: true,
},
});
```
<Note>
`TWENTY_APP_ACCESS_TOKEN` otomatik olarak enjekte edilir — bkz. [Uygulama değişkenleri](#application-variables). Gizli uygulama değişkenleri asla ön bileşenlere açığa çıkarılmadığından, API anahtarlarını ve diğer hassas mantığı ön bileşende değil, mantık işlevinin içinde tutun.
</Note>
### Twenty REST API'sini çağırma
Uygulama HTTP rotalarını çağırmak veya bir front bileşeninden Twenty kayıtlarını okumak ve yazmak için `twenty-client-sdk/rest` paketinden `RestApiClient` kullanın. `/s/...` yollarını çalışma alanınızdaki işlevlerin temel URL'sine, `/rest/...` dahil diğer tüm yolları ise `TWENTY_API_URL`'ye gönderir.
Her zaman sayfayı görüntüleyen kişi olarak hareket eder. `runAs: 'application'` yalnızca bir mantık işlevi seçeneğidir: bir bileşen uygulamanızın kendi token'ını asla almaz; dolayısıyla burada bunu istemek hata verir. Uygulamanın kendi erişimini gerektiren işleri bir mantık işlevinin arkasına yerleştirin ve bunun yerine onu çağırın.
| Yöntem | Açıklama |
| --------------------------------- | ------------------------------------------------------------------------ |
| `get(path, options?)` | Bir `GET` isteği gönderir |
| `post(path, body?, options?)` | Bir `POST` isteği gönderir |
| `put(path, body?, options?)` | Bir `PUT` isteği gönderir |
| `patch(path, body?, options?)` | Bir `PATCH` isteği gönderir |
| `delete(path, options?)` | Bir `DELETE` isteği gönderir |
| `request(method, path, options?)` | Herhangi bir HTTP yöntemiyle genel istek |
| `resolveUrl(path, options?)` | Bir istekte bulunmadan (bağlantılar için) bir yolu tam URL'sine çözümler |
`options`, `headers`, `query` (sorgu dizesi parametrelerinin kaydı; null benzeri değerler atlanır) ve `signal` aracılığıyla bir `AbortSignal` kabul eder. `FormData` olmayan bir `body` nesnesi otomatik olarak JSON’a serileştirilir. `401` durumunda, istemci erişim jetonunu bir kez ana makine (host) üzerinden yeniler ve isteği yeniden dener.
Temel URL ve jeton varsayılan olarak ortamdan çözümlenir. Gerektiğinde — örneğin testlerde — kurucuya (constructor) geçersiz kılmalar (override) iletin:
```ts
const client = new RestApiClient({
baseUrl: 'https://myworkspace.twenty.com',
token: 'my-token',
});
```
Başarısız istekler, `status`, `statusText`, `url` ve ayrıştırılmış `body` değerlerini açığa çıkaran bir `RestApiClientError` fırlatır:
```tsx
import { RestApiClient, RestApiClientError } from 'twenty-client-sdk/rest';
const client = new RestApiClient();
try {
const people = await client.get('/rest/people', {
query: { limit: 10 },
});
} catch (error) {
if (error instanceof RestApiClientError) {
console.error(error.status, error.body);
}
}
```
## Ç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,
useSelectedRecordIds,
useFrontComponentId,
} from 'twenty-sdk/front-component';
const RecordInfo = () => {
const userId = useUserId();
const [recordId] = useSelectedRecordIds();
const componentId = useFrontComponentId();
return (
<div>
<p>User: {userId}</p>
<p>Record: {recordId ?? 'No record context'}</p>
<p>Component: {componentId}</p>
</div>
);
};
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 |
| `useSelectedRecordIds()` | `string[]` | Tüm seçili kayıt kimlikleri (hiçbiri seçilmediyse boş dizi) |
| `useRecordId()` | `string` veya `null` | **Kullanımdan kaldırıldı.** Bunun yerine `useSelectedRecordIds()` kullanın |
| `useFrontComponentId()` | `string` | Bu bileşen örneğinin ID'si |
| `useTimelineActivityId()` | `string` veya `null` | Özel bir zaman çizelgesi satırı oluşturulurken geçerli zaman çizelgesi etkinlik kimliği |
| `useColorScheme()` | `'light'` veya `'dark'` | Ana makine kullanıcı arayüzünün etkin renk şeması (`System` zaten çözümlenmiştir) |
| `useFrontComponentExecutionContext(selector)` | değişir | Bir seçici işlevle tam yürütme bağlamına erişin |
## Uygulama değişkenleri
`isSecret: false` ile [`defineApplication()`](/l/tr/developers/extend/apps/config/application) içinde tanımlanan uygulama değişkenleri, `getApplicationVariable` yardımcı işlevi aracılığıyla ön uç bileşenleri içinde kullanılabilir:
```tsx src/front-components/greeting.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getApplicationVariable } from 'twenty-sdk/front-component';
const Greeting = () => {
const recipientName = getApplicationVariable('DEFAULT_RECIPIENT_NAME') ?? 'World';
return <p>Hello, {recipientName}!</p>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'greeting',
component: Greeting,
});
```
<Warning>
Gizli değişkenler (`isSecret: true`) ön uç bileşenlere açıklanmaz. Bunlar yalnızca sunucu tarafında çalışan [mantık işlevlerinde](/l/tr/developers/extend/apps/logic/logic-functions) kullanılabilir. Bu, API anahtarları gibi hassas değerlerin tarayıcıya gönderilmesini engeller.
</Warning>
`getApplicationVariable`, değişkenin bildirilen `type` değerinden bağımsız olarak her zaman bir **string** (veya `undefined`) döndürür. String, türe göre tutarlı bir şekilde serileştirilir (boolean değerler `"true"` / `"false"`, sayılar ondalık dizeler olarak, diziler/nesneler JSON olarak) ve bu, `process.env` mantık işlevi için kullanılan formatla aynıdır — veriyi kendiniz ayrıştırın (`Number(...)`, `JSON.parse(...)`, `=== 'true'`). [Değişken türleri](/l/tr/developers/extend/apps/config/application#variable-types) bölümüne bakın.
Aşağıdaki sistem değişkenleri her zaman `process.env` aracılığıyla kullanılabilir:
| Değişken | Açıklama |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `TWENTY_API_URL` | Twenty çekirdek API'sinin temel URL'si |
| `TWENTY_APP_ACCESS_TOKEN` | Oturum açmış kişinin roluyla uygulamanızın rolunun kesişimiyle kapsamı belirlenen kısa ömürlü token; böylece bir bileşen, onu görüntüleyen kişiden asla daha fazlasını yapamaz |
### `TWENTY_FUNCTIONS_URL`
Twenty ayrıca `TWENTY_FUNCTIONS_URL` değişkenini front bileşenlerine ve mantık işlevlerine enjekte eder: bu, uygulamanızın HTTP ile tetiklenen mantık işlevlerinin sunulduğu temel URL'dir.
Bu değişken, bu URL her zaman Twenty sunucusunun kendisi olmadığı için mevcuttur. Twenty Cloud üzerinde, uygulama rotaları, uygulama tarafından yazılmış yanıtların Twenty uygulamasının kaynağı yerine yalıtılmış bir kaynak üzerinde çalışması için, çalışma alanı başına ayrılmış özel bir etki alanında (`https://\<your-workspace-subdomain>.withtwenty.com` veya yapılandırıldığında uygulamanın birincil genel etki alanında) sunulur. Self-hosted ve yerel örnekler, uygulama rotalarını sunucunun kendisinde `/s` öneki altında sunar ve değişkeni hiç ayarlamayabilir. Temel URL, çalışma alanına ve örneğe göre değiştiğinden, kodunuz bunu sabit olarak yazamaz — sunucu, çalışma zamanında doğru değeri enjekte eder.
Bu değişkeni doğrudan okumanız nadiren gerekir. Rotalarınızı, yolu `/s/` önekiyle birlikte vererek `RestApiClient` üzerinden çağırın; istemci URL'yi sizin için çözümler: `/s` önekini kaldırır ve `TWENTY_FUNCTIONS_URL`'yi hedefler, değişken ayarlı değilse `\<TWENTY_API_URL>/s` adresine geri döner. Mutlak URL'yi bir istek göndermeden elde etmek için, örneğin bir bağlantı için, `resolveUrl('/s/\<path>')` kullanın. Değişkeni yalnızca bir URL'yi elle oluştururken doğrudan okuyun:
```ts
const routeUrl = `${process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL}/s`}/documents/generate`;
```
## 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 |
| `uploadFile(file, { fieldMetadataId, fileName? })` | Bir `Blob` dosyasını FILES alanına yükler; `{ status: 'uploaded', file: { fileId, path, url, size, mimeType } }` veya `{ status: 'failed', reason: 'invalid-params' \| 'upload-failed' }` sonucunu döndürür. |
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 { enqueueSnackbar, closeSidePanel, useSelectedRecordIds } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const ArchiveRecord = () => {
const [recordId] = useSelectedRecordIds();
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 (
<div style={{ padding: '20px' }}>
<p>Archive this record?</p>
<button onClick={handleArchive}>Archive</button>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678',
name: 'archive-record',
description: 'Archives the current record',
component: ArchiveRecord,
});
```
### Depolama
`localStorage` ve `sessionStorage` normal bir sayfada olduğu gibi, standart senkron API ile çalışır. Anahtarlarınız, uygulama kurulumunuza ve oturum açmış kullanıcıya göre kapsamlandırılır: başka hiçbir uygulama bunları okuyamaz ve aynı tarayıcıda oturum açan başka bir kullanıcı, boş bir depoyla başlar. `localStorage`'a yazılan değerler, yeniden yüklemeler arasında cihazda kalır; `sessionStorage` ise tarayıcı oturumu süresince geçerlidir.
```tsx src/front-components/note-draft.tsx
import { useState } from 'react';
import { defineFrontComponent } from 'twenty-sdk/define';
const NoteDraft = () => {
const [draft, setDraft] = useState(() => localStorage.getItem('draft') ?? '');
const handleChange = (nextDraft: string) => {
setDraft(nextDraft);
localStorage.setItem('draft', nextDraft);
};
return (
<textarea value={draft} onChange={(event) => handleChange(event.target.value)} />
);
};
export default defineFrontComponent({
universalIdentifier: 'd0e1f2a3-b4c5-6789-def0-890123456789',
name: 'note-draft',
description: 'Keeps an unsaved note on this device',
component: NoteDraft,
});
```
Twenty, değerleri uygulamanız adına depolar, böylece bir yazma işlemi yerelde anında uygulanır ve arka planda kaydedilir. Okumalar hiçbir zaman ana makine üzerinde beklemez. Hiçbir şey eşitlenmez: değerler kullanıcıyı başka bir tarayıcıya veya makineye takip etmez, bu yüzden cihaz değişikliğinden sonra da korunması gereken her şey için bir mantık fonksiyonunun [anahtar-değer deposunu](/l/tr/developers/extend/apps/logic/key-value-store) kullanın.
Yazma işlemleri sınırlandırılmıştır ve her sınır bayt yerine karakterleri sayar: anahtarlar en fazla 512 karakter, tek bir değer en fazla 262.144 karakter ve her depolama alanı uygulama ve kullanıcı başına en fazla 1.048.576 karakter olabilir. Bir sınırı aşan yazma, tarayıcı API’sinde olduğu gibi bir `QuotaExceededError` fırlatır.
### Birden çok kayıtla çalışma
Birden çok seçili kaydı yönetmek için `useSelectedRecordIds()` kullanın. Bu, toplu işlemler için kullanışlıdır:
```tsx src/front-components/bulk-export.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const BulkExport = () => {
const selectedRecordIds = useSelectedRecordIds();
const handleExport = async () => {
const client = new CoreApiClient();
for (const recordId of selectedRecordIds) {
await client.mutation({
updateTask: {
__args: { id: recordId, data: { exported: true } },
id: true,
},
});
}
await enqueueSnackbar({
message: `Exported ${selectedRecordIds.length} records`,
variant: 'success',
});
await closeSidePanel();
};
return (
<div style={{ padding: '20px' }}>
<p>Export {selectedRecordIds.length} selected record(s)?</p>
<button onClick={handleExport}>Export</button>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678901',
name: 'bulk-export',
description: 'Export selected records',
component: BulkExport,
});
```
Bunu, kayıt seçimleriyle sınırlandırılmış bir [komut menüsü öğesi](/l/tr/developers/extend/apps/layout/command-menu-items) ile sunun:
```ts src/command-menu-items/bulk-export.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
frontComponentUniversalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678901',
});
```
## Genel varlıklar
Ön uç bileşenleri, `getPublicAssetUrl` kullanarak uygulamanın `public/` dizinindeki dosyalara erişebilir:
```tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const Logo = () => <img src={getPublicAssetUrl('logo.png')} alt="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/config/public-assets) bakın.
## Ön uç bileşenleri arasında bağımlılıkları paylaşma
Varsayılan olarak, her ön uç bileşeni içe aktardığı her bir kitaplığın kendi kopyasını paketler; bu nedenle beş bileşenli bir uygulama React'i beş kez paketler. Bu kitaplıkları tek seferde derlemek ve uygulamanın her bileşeninin onları tek bir önbelleğe alınmış dosyadan yüklemesini sağlamak için, paylaşılan bağımlılıkları uygulamanızın `package.json` dosyasında belirtin:
```json package.json
{
"frontComponentSharedDependencies": ["react", "react-dom/client", "twenty-ui/input"]
}
```
Her bileşen daha sonra bağımlılıklarını önceden olduğu gibi içe aktarır — bileşen kodunuzda hiçbir şey değişmez:
```tsx src/front-components/counter.tsx
import { useState } from 'react';
import { defineFrontComponent } from 'twenty-sdk/define';
const Counter = () => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'counter',
component: Counter,
});
```
Bilmeniz gereken birkaç nokta:
* **Uygulama başına tek bir paylaşılan bağımlılık paketi.** Paket, uygulamanızın kendi bağımlılıklarından oluşturulur; böylece dağıttığınız sürümler üzerinde tam denetim sizde olur.
* **İçe aktardığınız tam belirteçleri listeleyin.** `twenty-ui/input` ve `twenty-ui/display` iki ayrı girdidir; yalnızca bir paket adı alt yollarını kapsamaz. `react`i listelemek, otomatik olarak `react/jsx-runtime`ı da kapsar.
* **`react` ile birlikte `react-dom/client`ı paylaşın.** Her bileşen `createRoot` üzerinden oluşturulur; bu yüzden bunu dışarıda bırakmak, her bileşenin yine de React DOM'u paketlemesi anlamına gelir.
* **Paket önbelleğe alınır.** İçerik karma değerine sahip bir URL altında, uzun ömürlü, değişmez bir önbellekle sunulur; bu nedenle bir kez indirilir ve bağımlılıklarından biri değişene kadar uygulamanın tüm bileşenleri arasında yeniden kullanılır.
* **Paylaşılan paketlerden hiçbirini içe aktarmayan bileşenler onu asla indirmez.**
## 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'nin kendi bileşen kitaplığı; aşağıda [Twenty UI bileşenlerini kullanma](#using-twenty-ui-components) bölümüne bakın
* **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ığı**
## Twenty UI bileşenlerini kullanma
Twenty, bileşen kitaplığını [`twenty-ui`](https://www.npmjs.com/package/twenty-ui/v/1.0.0-alpha.1) paketi olarak sunar. Ön uç bileşenleri bunu, çalışma alanının açık ve koyu temasıyla otomatik olarak eşleşen düğmeler, etiketler, durum rozetleri, çipler, avatarlar, simgeler, tipografi ve tema belirteçleri için kullanabilir.
### Kurulum
Paketi, Twenty örneğinizle birlikte gelen sürüme sabitleyerek uygulamanıza ekleyin:
```bash
yarn add twenty-ui@1.0.0-alpha.1
```
`twenty-ui`, derleme zamanında ön uç bileşeninize paketlenir, bu nedenle yalnızca uygulamanızın bir bağımlılığı olması gerekir — çalışma zamanında yapılandırmanız gereken hiçbir şey yoktur.
### Bileşenleri içe aktarma
Kullandığınız bileşenlerin yalnızca paketinizde yer alması için, paket kökü yerine eşleşen alt yoldan içe aktarın:
| Alt yol | Dışa aktardıkları |
| --------------------------- | -------------------------------------------------------- |
| `twenty-ui/input` | `Button` ve form girdileri |
| `twenty-ui/data-display` | `Tag`, `Status`, `Chip`, `Avatar` ve daha fazlası |
| `twenty-ui/feedback` | `Callout`, `Banner`, `Info` ve daha fazlası |
| `twenty-ui/typography` | `H1Title`, `H2Title`, `H3Title`, `Label` ve daha fazlası |
| `twenty-ui/icon` | `Icon*` bileşenleri (ör. `IconCheck`) |
| `twenty-ui/theme-constants` | `ThemeProvider`, `themeCssVariables` |
```tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Status, Tag } from 'twenty-ui/data-display';
import { Button } from 'twenty-ui/input';
const StyledWidget = () => {
return (
<div style={{ padding: '16px', display: 'flex', gap: '8px' }}>
<Button title="Click me" onClick={() => alert('Clicked!')} />
<Tag text="Active" color="green" />
<Status color="green" text="Online" />
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456',
name: 'styled-widget',
component: StyledWidget,
});
```
### İkonlar
Tek tek simgeleri `twenty-ui/icon` içinden içe aktarın:
```tsx
import { IconBox, IconCheck } from 'twenty-ui/icon';
```
Her adlandırılmış simge ağaç sallama optimizasyonuna tabidir; bu nedenle birkaç tanesini içe aktarmak paket boyutunuza çok az şey ekler. `IconsProvider`, `useIcons` ve `iconsState` kullanmaktan kaçının — bunlar, birkaç MB'lık Tabler simge setinin tamamını içeri çeker.
### Temalandırma ve tema belirteçleri
Twenty UI bileşenleri, çalışma alanının açık ve koyu temasıyla otomatik olarak eşleşir — oluşturucu etkin renk şemasını barındırıcıda uygular ve bileşenler renklerini buna göre belirler.
Kendi satır içi stillerinizde aynı tasarım belirteçlerini kullanmak için `useTheme()` kancasını çağırın. Bu kanca, etkin temaya bağlı Twenty tema belirteçlerini (boşluklandırma, renkler, köşe yarıçapları, yazı tipleri) döndürür; bileşeninizde `ThemeProvider` kurulumuna gerek yoktur:
```tsx
import { useTheme } from 'twenty-ui/theme-constants';
const Card = () => {
const theme = useTheme();
return (
<div
style={{
padding: theme.spacing[4],
background: theme.background.secondary,
color: theme.font.color.primary,
}}
>
Themed card
</div>
);
};
```
`useTheme()` bir kanca olduğundan, belirteçleri bileşen gövdesinin içinde okursunuz; böylece değerler her zaman etkin temayı yansıtır. Aynı belirteç haritası, `themeCssVariables` sabiti olarak da dışa aktarılır; ancak ön uç bileşenlerinde `useTheme()` kullanmayı tercih edin — `themeCssVariables` öğesini dolaylı olarak kullanan modül düzeyinde bir sabit, uygulama manifesti çıkarılırken tanımsız olabilir.
Etkin şemaya açıkça dallanmak için, `twenty-sdk/front-component` içindeki `useColorScheme()` ile okuyun; bu kanca `'light'` veya `'dark'` döndürür.
## Mevcut sınırlamalar
Front bileşenleri aktif geliştirme aşamasındadır. Oluşturma, stillendirme, olayları işleme, öğeleri ölçme ve tarayıcı depolaması iyi çalışır. Bu sınırların *ötesine* geçen her şey (bir ref üzerinde bir DOM yöntemi çağırmak, öğe yeniden boyutlandırmalarını gözlemlemek, ağacınızın dışına portal oluşturmak) bugün eksik ya da tamamlanmamış durumda ve çoğu sessizce başarısız oluyor: ne bir istisna fırlatılıyor ne de TypeScript hatası görülüyor; çünkü iskeletin türleri tam tarayıcı DOM’una göre tanımlanmıştır.
Bunlardan biri sizi engelliyorsa, önceliklendirilmesi için [bir issue açın](https://github.com/twentyhq/twenty/issues/new/choose).
### Yerleşim ve ölçüm
Öğeler kendilerini ölçebilir: ana bilgisayar geometriyi sandbox’a yansıtır; bu nedenle okumalar yerel olarak yanıtlanır ancak bir kareye kadar eski olabilir ve hiç ölçülmemiş bir öğenin ilk okunması sıfırlar döndürür. Yazdıktan sonra, `requestAnimationFrame` geri çağrısında veya bir effect içinde yeniden okuyun.
| API | Ne olur |
| ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `getBoundingClientRect()`, `offsetWidth`, `clientWidth`, `scrollWidth`, `offsetTop`, ... | Ayna üzerinden çalışır; `scrollTop` / `scrollLeft` atamak etkisizdir |
| `getClientRects()`, `window.matchMedia()` | Fırlatır |
| `window.innerWidth`, `innerHeight`, `devicePixelRatio`, `scrollX`, `scrollY` | Çalışır ve **tarayıcı görünüm alanını** raporlar; bileşeninizin kendi boyutları `document.body.clientWidth` / `clientHeight` değerleridir |
| `window.getComputedStyle()` | Yalnızca öğenin satır içi stillerini döndürür, hiçbir zaman ana bilgisayar tarafından hesaplanan kaskadı döndürmez |
| `ResizeObserver`, `IntersectionObserver` | `ReferenceError` (`typeof` kontrolleri çalışır) |
| `MutationObserver` | Çalışır, `subtree`, `attributeFilter`, eski değerler ve `takeRecords()` dahil |
`getBoundingClientRect` ile konumlandırma artık çalışıyor, ancak boyut değişikliklerini `ResizeObserver` aracılığıyla izleyen her şey (recharts `ResponsiveContainer`, Floating UI'nin `autoUpdate`) hâlâ çalışmıyor. Yerleşim için yine de CSS'i tercih edin: stil sayfanız gerçek sayfaya erişir, bu nedenle flexbox, grid, `aspect-ratio`, `clamp()` ve `@container` gecikme olmadan normal şekilde davranır.
<Note>
`requestAnimationFrame`, `fetch`, `setTimeout` ve `queueMicrotask` `window.` öneki olmadan çalışır. Yalnızca `window.requestAnimationFrame(...)` ve benzerleri fırlatır.
</Note>
### DOM erişimi
Bir `ref` size bir `HTMLElement` değil, bir sandbox öğesi verir.
| Ne yazarsınız | Ne olur | Bunun yerine şunu kullanın |
| ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ref.current.focus()`, `.click()`, `.select()`, `.setSelectionRange()`, `.scrollIntoView()`, `video.play()` | Fırlatır | Kontrollü bileşenler; değerleri `event.target` üzerinden okuyun |
| `element.classList.add(...)` | Fırlatır (`classList` `undefined` durumundadır) | `className` dizgisini kendiniz oluşturun |
| `document.createTreeWalker()` | Fırlatır | `querySelector()` / `querySelectorAll()` ve `getElementById()` çalışır; `getElementsByClassName()` de çalışır, ancak canlı bir `HTMLCollection` yerine statik bir koleksiyon döndürür |
| `document.activeElement` | Her zaman `undefined` | Odağı `onFocus` / `onBlur` ile takip edin |
| `canvas` | Hiçbir şey render etmez, hata yoktur | SVG veya ekranda olmayan bir alanda çizim yapıp sonucu bir `img` öğesinde gösterin |
| `createPortal(node, document.body)` | `isConnected` başarı bildirmesine rağmen hiçbir şey render etmez | `position: absolute` ile satır içi yerleştirilmiş bindirmeler veya kitaplığa kendi konteyner öğenizi iletin |
Portal boşluğu, Radix, Headless UI, MUI ve react-select popover’larının varsayılan olarak hiçbir şey render etmemesinin nedenidir. Çoğu bir container prop kabul eder; onu render ettiğiniz bir öğeye yönlendirin.
### Etkinlikler
Mouse, pointer, touch, sürükleme, klavye, odak, `input`/`change`/`submit`, `scroll`/`wheel`/`contextmenu` ve `animationend`/`transitionend` host’a iletilir, ayrıca öğe başına birkaç tane daha vardır: `img` üzerinde `load`/`error`, `input`/`textarea` üzerinde pano ve bileşim olayları, `video`/`audio` üzerinde medya, `details`/`dialog` üzerinde `toggle`. Bunun dışındaki her şey (`onAuxClick`, `onSelect`, `onInvalid`, `onReset`, `onAnimationStart`, pointer capture, `img` üzerinde `onLoad`) uyarı verilmeden kaldırılır.
`document.addEventListener()` ve `window.addEventListener()` hatasız olarak kaydolur ancak asla tetiklenmez; bu yüzden bir sürükleme, işaretçi başladığı öğeden ayrılır ayrılmaz durur. `event.preventDefault()` da geçmez; form gönderimi, `dragover`/`drop` ve bağlantı tıklamaları zaten sizin için korunmuştur.
### Öznitelikler ve stillendirme
Her öğe, kendi özelliklerini host DOM’a iletir (`a` üzerinde `href`, `img` üzerinde `src`/`alt`, `input` üzerinde `value`/`placeholder`/`disabled` vb.), ayrıca her öğede ortak bir küme vardır: `id`, `className`, `style`, `title`, `tabIndex`, `role`, `draggable` ve tireli olan tüm `aria-*` / `data-*` öznitelikleri (bu yüzden `ariaLabel` düşürülür). Bunun dışındaki her şey sessizce atılır, bu yüzden özel durumu `data-*` olarak ifade edin.
Bileşen CSS’i, ister `import './styles.css'` ile, ister CSS-in-JS ile ya da bir `style` öğesinden gelsin, host sayfanın `head` kısmına **scope’suz** olarak enjekte edilir. Bu nedenle sınıf adları Twenty’nin kendi sınıf adlarıyla çakışır (onlara önek ekleyin ve asla çıplak `div { ... }` seçicileri yazmayın) ve `@media`, sizin bileşeniniz yerine tarayıcı penceresini hedefler (kendi `container-type` değerinizle `@container` kullanın). Satır içi `style` prop’ları etkilenmez.
### Depolama ve ağ
`localStorage` ve `sessionStorage` tarayıcı yerine Twenty tarafından sağlanır: bileşen, opak bir origin’de bir worker içinde çalışır, bu nedenle ana makine değerleri uygulamanız adına depolar. Kapsamları ve sınırları için [storage](#storage) bölümüne bakın. IndexedDB, çerezler, Cache API ve `BroadcastChannel` hâlâ kullanılamaz. Durumu cihazlar arasında kalıcı hale getirmek için bir [mantık fonksiyonu](/l/tr/developers/extend/apps/logic/logic-functions) çağırın ve onun [anahtar-değer deposunu](/l/tr/developers/extend/apps/logic/key-value-store) kullanın.
`fetch` çalışır, bazı sınırlamalarla:
* Twenty API’sine ve uygulamanızın rotalarına yapılan çağrılar host tarafından proxy’lenir, bu yüzden [`RestApiClient`](#calling-the-twenty-rest-api) kullanmayı tercih edin. Proxy’lenen çağrılarda `AbortSignal` ve diğer `RequestInit` seçenekleri kaldırılır ve yalnızca `string` ve `URLSearchParams` gövdeleri desteklenir.
* Diğer origin’ler sandbox’tan `Origin: null` ile çıkar, bu yüzden üçüncü taraf bir API yalnızca `Access-Control-Allow-Origin: *` gönderirse yanıt verir. Bunun yerine onu bir mantık fonksiyonundan çağırın.
* `fetch('/rest/people')` hiçbir zaman Twenty API’siyle eşleştirilmez, çünkü sandbox’un göreli yolu çözecek bir sayfa URL’si yoktur.
### Ortam yakalama
`navigator.mediaDevices.getUserMedia()` ve `MediaRecorder`, sandbox polyfill’leri aracılığıyla front bileşenleri içinde çalışır; böylece standart kayıt kodu değişmeden çalışır ve `MediaRecorder.isTypeSupported`, yaygın kapsayıcı/kodek kombinasyonları için yanıt verir. Ayrıntılı `getUserMedia` kısıtlama nesneleri kabul edilir ancak iletilmez — ana makine, istenen türler için varsayılanlarıyla yakalama yapar — ve uygulamalar genelinde aynı anda yalnızca bir yakalama etkin olabilir. Kaydedilmiş bir `Blob`’u `uploadFile` host fonksiyonu ile saklayın.
### Diğer eksikler
* **Dosya içerikleri.** `file` türünde bir `input`, handler’ınıza yalnızca dosya meta verilerini verir, baytları vermez, bu yüzden `FileReader` kullanılamaz. Kodunuzun zaten elinde bulunan bir `Blob`’u — örneğin `MediaRecorder` tarafından üretilen bir tane — yüklemek için `uploadFile` host fonksiyonunu kullanın.
* **Sürükle-bırak yükleri.** Sürükleme olayları tetiklenir, ancak `event.dataTransfer` `undefined` durumundadır.
* **Node yerleşikleri.** `fs`, `path` ve `node:crypto` build aşamasında başarısız olur; bu nedenle bu işi bir [mantık fonksiyonuna](/l/tr/developers/extend/apps/logic/logic-functions) taşıyın. Web Crypto, `fetch`, `TextEncoder` ve `URL` kullanılabilir durumdadır.
* **`iframe`** her zaman `allow-same-origin` olmadan yeniden sandbox’a alınır, bu nedenle kendi oturumuna güvenen bir yerleştirme, oturum kapalı olarak işlenir. `onLoad`’u da yoktur.

View File

@@ -0,0 +1,46 @@
---
title: Gezinme Menüsü Öğeleri
description: Çalışma alanı kenar çubuğuna özel öğeler ekleyin — kaydedilmiş görünümlere veya harici URL’lere bağlantılar.
icon: bars
---
Bir **gezinme menüsü öğesi**, sol kenar çubuğundaki bir öğedir. Özel kenar çubuğu bağlantıları sunmak için `defineNavigationMenuItem()` kullanın — genellikle sunduğunuz her [görünüm](/l/tr/developers/extend/apps/layout/views) için bir tane — veya harici URL’lere işaret etmek için 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ğlantı verdiğini belirler. Her tür, belirli bir tanımlayıcı alanıyla eşleşir:
| Tür | Ne yapar | Zorunlu alan |
| ------------------------------------ | ------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `NavigationMenuItemType.VIEW` | Kaydedilmiş bir görünümü açar | `viewUniversalIdentifier` |
| `NavigationMenuItemType.LINK` | Harici bir URL açar | `link` |
| `NavigationMenuItemType.FOLDER` | İç içe geçmiş öğeleri bir etiket altında gruplar | `name` (ve alt öğeler klasöre `folderUniversalIdentifier` aracılığıyla referans verir) |
| `NavigationMenuItemType.OBJECT` | Bir nesnenin varsayılan indeks sayfasını açar | `targetObjectUniversalIdentifier` |
| `NavigationMenuItemType.PAGE_LAYOUT` | Bağımsız bir sayfa düzeni açar | `pageLayoutUniversalIdentifier` |
* `position`, kenar çubuğundaki sıralamayı kontrol eder.
* Enum, ayrıca kullanıcı tarafından oluşturulan kayıt favorileri için dahili olarak kullanılan `NavigationMenuItemType.RECORD` değerini de içerir — bir kayda referans verecek alan bulunmadığından uygulama manifestinde kullanılamaz.
* `icon` ve `color` isteğe bağlıdır ve öğenin görünümünü özelleştirir.
* `folderUniversalIdentifier`, herhangi bir öğede de mevcuttur ve onu bir `FOLDER` türü üst öğenin içine yerleştirmek için kullanılır.
<Note>
**Yaygın hata:** gezinme menüsü öğesiyle ilişkilendirilmemiş bir nesne oluşturmak, o nesneyi kenar çubuğundan erişilemez hale getirir. Teknik/içsel bir nesne olmadığı sürece, her özel nesnenin ona işaret eden bir kenar çubuğu öğesi olmalıdır.
</Note>

View File

@@ -0,0 +1,59 @@
---
title: Genel Bakış
description: Uygulamanızı Twenty'nin kullanıcı arayüzüne yerleştirin — kenar çubuğu öğeleri, kaydedilmiş görünümler, kayıt sayfası sekmeleri ve yalıtılmış React bileşenleri.
icon: table-columns
---
Bir Twenty uygulamasının **düzen katmanı**, kullanıcının gördüğü her şeydir: uygulamanın kenar çubuğunda nerede göründüğü, hangi liste görünümlerini sunduğu, kayıt detay sayfalarının nasıl düzenlendiği ve bu sayfaların içinde hangi özel React bileşenlerinin oluşturulduğu.
```text
Sidebar Record list Record detail page
─────── ─────────── ──────────────────
[📋 My View] ────▶ ┌──────────┐ ┌─────────────────────┐
[📋 Drafts ] │ Companies│ │ Tabs: [Overview ] │
[📋 Inbox ] │ ──────── │ │ [Notes ] │
▲ │ Apple │ │ [Hello ]◀──── definePageLayoutTab
│ │ Acme │ │ │ adds a tab...
└ defineNavi- │ … │ │ ┌────────────────┐ │
gationMenu- └────▲─────┘ │ │ │ │
Item points │ │ │ React UI │◀── …with a
to a defineView │ │ │ (sandboxed in │ │ defineFrontComponent
└ defineView │ │ a Worker) │ │ widget inside
picks columns │ └────────────────┘ │
and filters └─────────────────────┘
```
## Bu bölümde
<CardGroup cols={2}>
<Card title="Görünümler" icon="list" href="/l/tr/developers/extend/apps/layout/views">
`defineView` — kaydedilmiş liste yapılandırmaları: görünür sütunlar, filtreler, gruplar.
</Card>
<Card title="Gezinme Menüsü Öğeleri" icon="bars" href="/l/tr/developers/extend/apps/layout/navigation-menu-items">
`defineNavigationMenuItem` — görünümlere veya harici URL'lere işaret eden kenar çubuğu girişleri.
</Card>
<Card title="Sayfa Düzenleri" icon="table-columns" href="/l/tr/developers/extend/apps/layout/page-layouts">
`definePageLayout` ve `definePageLayoutTab` — bir kaydın detay sayfasındaki sekmeler ve bileşenler.
</Card>
<Card title="Ön uç bileşenleri" icon="window-maximize" href="/l/tr/developers/extend/apps/layout/front-components">
`defineFrontComponent` — Twenty içinde oluşturulan yalıtılmış React bileşenleri.
</Card>
<Card title="Komut Menüsü Öğeleri" icon="terminal" href="/l/tr/developers/extend/apps/layout/command-menu-items">
`defineCommandMenuItem` — ön uç bileşenlerini Cmd+K girişleri ve hızlı eylemler olarak kaydedin.
</Card>
</CardGroup>
## Uygulamanın göründüğü yerler
| Görünüm alanı | Neyi kontrol eder | Varlık |
| ------------------------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| **Kenar çubuğu** | Kaydedilmiş bir görünüme veya harici bir URL'ye bağlanan özel bir giriş | `defineNavigationMenuItem` |
| **Kayıt listesi** | Bir nesne için kaydedilmiş yapılandırma — görünür sütunlar, sıralama, filtreler, gruplar | `defineView` |
| **Kayıt detay sayfası** | Bir kayıt sayfasındaki sekmeler ve bileşenler (kendi nesnenize ait veya standart bir nesneye ait) | `definePageLayout`, `definePageLayoutTab` |
| **Yukarıdakilerin herhangi birinin içinde** | Özel bir React bileşeni — butonlar, formlar, panolar, entegrasyonlar | `defineFrontComponent` |
| **Uygulama ayarları** | Varsayılan değişkenler arayüzü yerine, uygulamanın Ayarlar sekmesinde özel bir yapılandırma bölümü | `defineSettingsFrontComponent` |
| **Komut menüsü (Cmd+K)** | Sabitlenmiş bir hızlı eylem veya gizli komut | `defineCommandMenuItem` |
Ön uç bileşenleri, Remote DOM kullanan yalıtılmış bir Web Worker içinde çalışır — sayfada *yerel* olarak oluşturulurlar (bir iframe içinde değil), ancak ana makine sayfasına veya DOM'a doğrudan erişemezler. Twenty ile iletişim, mesaj iletimi yapan bir ana makine API'si aracılığıyla gerçekleşir.
Hâlâ etkin geliştirme aşamasındalar: sandbox kısmi bir DOM uygular, bu nedenle gelişmiş kullanımlar başarısız olabilir. [Mevcut sınırlamalara](/l/tr/developers/extend/apps/layout/front-components#current-limitations) bakın.

View File

@@ -0,0 +1,158 @@
---
title: Sayfa Düzenleri
description: definePageLayout ve definePageLayoutTab kullanarak kayıt ayrıntı sayfalarını — sekmeleri, widget'ları ve ön uç bileşenlerinin nerede oluşturulacağını — özelleştirin.
icon: table-columns
---
Bir sayfa düzeni, bir kaydın ayrıntı sayfasının nasıl düzenleneceğini kontrol eder: hangi sekmelerin görüneceği ve bunların hangi widget'ları içereceği. Sahibi olduğunuz bir nesne için bir düzen tanımlamak amacıyla `definePageLayout()` kullanın veya halihazırda var olan bir düzene (size ait veya standart bir Twenty düzenine) tek bir sekme eklemek için `definePageLayoutTab()` kullanın.
| Kullanım alanı | Varlık |
| ------------------------------------------------------------------------------ | --------------------- |
| Sahibi olduğunuz bir nesnedeki bir kayıt sayfası için tüm düzeni tanımlayın | `definePageLayout` |
| Var olan bir düzene bir sekme ekleyin (kendi nesneniz veya standart bir nesne) | `definePageLayoutTab` |
## definePageLayout
Tüm ayrıntı sayfasına sahip olduğunuz durumlarda bunu kullanın — genellikle kendiniz tanımladığınız özel bir nesne için.
```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.VERTICAL_LIST,
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`, `'RECORD_INDEX'`, `'RECORD_PAGE'`, `'DASHBOARD'` veya `'STANDALONE_PAGE'` değerlerinden biridir. Belirli bir nesnenin ayrıntı görünümünü özelleştirmek için `'RECORD_PAGE'` kullanın.
* `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: kayıt sayfaları ve bağımsız sayfalar için `VERTICAL_LIST`, panolar için `GRID` ve sekmenin görüntü alanını doldurması gereken tek bir widget için `CANVAS`. Bir `VERTICAL_LIST` sekmesi widget'ları dikey olarak sıralar. Zaman çizelgeleri, dosyalar, notlar, görevler ve iş akışları gibi kendi kaydırmalarını yöneten yerleşik widget'lar tek bir görüntü alanını doldurur; alanlar, ön bileşenler, grafikler ve içeriğe sığan diğer widget'lar ise içeriklerinde veya yapılandırılmış yüksekliklerinde oluşturulur. Bir `GRID` sekmesi, widget'larını her zaman 12 sütunlu bir ızgarada kartlar olarak düzenler. Bir `CANVAS` widget'ının açıkça belirtilmiş bir konumu yoktur; bir canvas sekmesi birden fazla widget içeriyorsa, görüntü alanını doldurmak yerine içerik yüksekliklerinde işlenirler.
* `layoutMode` değerini açıkça ayarlayın. Bunu atlamak, bir `STANDALONE_PAGE` üzerinde size `VERTICAL_LIST` ve diğer her yerde `GRID` verir; bu da, bir kayıt sayfasında nadiren istediğiniz davranıştır.
* Bir sekmenin içindeki her `widget`, bir [ön uç bileşeni](/l/tr/developers/extend/apps/layout/front-components), bir ilişki listesi veya diğer yerleşik widget türlerini oluşturabilir.
* Bir `FRONT_COMPONENT` pencere öğesi, `headerCommandMenuItemUniversalIdentifiers` özelliğini aynı uygulamadaki komut menüsü öğelerinin evrensel tanımlayıcılarından oluşan sıralı bir diziye ayarlayabilir. Bu eylemler, pencere öğesi kart başlığında simge düğmeleri olarak görünür ve komut düzeyindeki kullanılabilirliklerini ve izin denetimlerini korur. Tanımlayıcılar benzersiz olmalı ve uygulama yüklendiğinde çözümlenebilmelidir.
* 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).
### Alan bileşenleri
Bir `FIELD` bileşeni, kaydın tek bir alanını görüntüler. İlişki alanları için, ilişkili kayıtların bir listesini de gömebilir:
```ts
{
universalIdentifier: 'c1c2c3c4-c5c6-4000-8000-000000000003',
title: 'People → Opportunities',
type: 'FIELD',
configuration: {
configurationType: 'FIELD',
fieldMetadataId: PEOPLE_FIELD_UNIVERSAL_IDENTIFIER,
fieldDisplayMode: 'TABLE',
nestedRelationFieldMetadataId: OPPORTUNITIES_FIELD_UNIVERSAL_IDENTIFIER,
},
}
```
* `fieldMetadataId`, yerleşimin nesnesindeki bir alanın evrensel tanımlayıcısını alır.
* `fieldDisplayMode`, `'FIELD'`, `'CARD'`, `'EDITOR'`, `'VIEW'` veya `'TABLE'` değerlerinden biridir. `TABLE`, bire-çok ilişki alanının kayıtlarını listeleyen bir görünümü gömer.
* `nestedRelationFieldMetadataId` isteğe bağlıdır ve ilişki hedef nesnesi üzerinde, iki ilişki adımı uzaktaki kayıtları listelemek için bire-çok ilişki alanının evrensel tanımlayıcısını alır (örneğin, bir Şirket sayfasının şirketin kişilerinin fırsatlarını listelemesi veya bir Kişi sayfasının kişinin şirketinin fırsatlarını listelemesi gibi). İlk adım bire-çok veya çoktan-bire ilişki alanı olabilir, ikincisi mutlaka bire-çok olmalıdır (bağlantı ilişkileri desteklenmez) ve `fieldDisplayMode: 'TABLE'` gerektirir — bunu başka herhangi bir görüntüleme modu ile birleştirmek bir doğrulama hatasıdır, çünkü iç içe bir bileşen her zaman gömülü bir görünüm olarak işlenir.
## definePageLayoutTab
Bunu yalnızca mevcut bir düzene **sekme eklemek** istediğinizde kullanın — örneğin, standart Company sayfasında bir analitik sekmesi veya kendi nesnenizin düzenine eklenmiş bir yapay zeka özeti sekmesi.
```ts src/page-layouts/example-extra-tab.ts
import {
definePageLayoutTab,
PageLayoutTabLayoutMode,
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world';
export default definePageLayoutTab({
universalIdentifier: 'b1b2b3b4-b5b6-4000-8000-000000000001',
pageLayoutUniversalIdentifier:
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.companyRecordPage
.universalIdentifier,
title: 'Hello World',
position: 1000,
icon: 'IconWorld',
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
widgets: [
{
universalIdentifier: 'b1b2b3b4-b5b6-4000-8000-000000000002',
title: 'Hello World',
type: 'FRONT_COMPONENT',
configuration: {
configurationType: 'FRONT_COMPONENT',
frontComponentUniversalIdentifier:
HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
},
},
],
});
```
### Önemli noktalar
* `pageLayoutUniversalIdentifier` **zorunludur** ve kurulum anında zaten var olan bir sayfa düzenini işaret etmelidir — standart bir Twenty düzeni veya kendi uygulamanız tarafından tanımlanan bir düzen olabilir. Başka yüklü bir uygulamaya ait düzenlere uygulamalar arası referanslar bugün desteklenmemektedir. Üst düzen eksik olduğunda, kurulum net bir doğrulama hatasıyla başarısız olur.
* Standart Twenty yerleşimleri için tanımlayıcıları `twenty-sdk/define` içinden içe aktarın:
```ts
import { STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.companyRecordPage.universalIdentifier
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.personRecordPage.universalIdentifier
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.universalIdentifier
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.opportunityRecordPage.universalIdentifier
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.noteRecordPage.universalIdentifier
// …
```
Her yerleşim girdisi ayrıca `tabs` ve bunların `widgets` ögelerini de açığa çıkarır, böylece herhangi bir düzeye başvurabilirsiniz:
```ts
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.tabs.home
.universalIdentifier;
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.tabs.home.widgets
.fields.universalIdentifier;
```
Kısa bir takma ad `STANDARD_PAGE_LAYOUT` da kullanılabilir:
```ts
import { STANDARD_PAGE_LAYOUT } from 'twenty-sdk/define';
STANDARD_PAGE_LAYOUT.companyRecordPage.universalIdentifier;
```
* `widgets` yalnızca bu sekmeyle sınırlıdır — satır içi olarak `definePageLayout` içinde tanımlanan widget'larda olduğu gibi, [ön uç bileşenlerine](/l/tr/developers/extend/apps/layout/front-components), görünümlere vb. tam olarak aynı şekilde referans verirler.
* `position`, hedeflenen düzende mevcut sekmelere göre sıralamayı kontrol eder. Yerleşik sekmelere göre sekmenizi istediğiniz konuma yerleştirecek bir değer seçin.
* Yalnızca mevcut bir düzene ekleme yapmak istediğinizde `definePageLayout` yerine bunu kullanın. Tüm düzene sahip olduğunuzda `definePageLayout` kullanın.

View File

@@ -0,0 +1,113 @@
---
title: Görünümler
description: Uygulamanızdaki nesneler için önceden yapılandırılmış kaydedilmiş görünümleri — sütun sırası, filtreler, gruplar — sunun.
icon: list
---
Bir **görünüm**, bir nesnenin kayıtlarının nasıl görüntüleneceğine ilişkin kaydedilmiş bir yapılandırmadır: hangi alanların görüneceği, sıraları, görünür olup olmadıkları ve uygulanan tüm filtreler veya gruplar. Uygulamanızla önceden yapılandırılmış görünümler sunmak için `defineView()` kullanın.
Her nesne, sunucu tarafından otomatik olarak sağlanan kendi ana liste görünümünü (bir `OBJECT` gezinme öğesinin açtığı "All ..." tablosu) alır — buna, uygulamanızın oluşturduğu nesneler de dahildir; uygulamanızın bildirdiği her alan için bir sütunla birlikte. `defineView()` ile tanımladığınız görünümler, bu varsayılan görünümün üzerine eklenen her zaman **ek** görünümlerdir.
```ts src/views/example-view.ts
import { defineView } 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',
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. Bu, tanımladığınız özel bir nesne veya standart bir Twenty nesnesi olabilir.
* Nesnenin ana liste görünümü sunucuya aittir: `key` kullanımdan kaldırılmıştır ve yok sayılır, bu nedenle bir manifest görünümü bunu asla talep edemez. Görünümünüzün kenar çubuğunda tek tık uzağınızda olmasını istiyorsanız bir `VIEW` gezinme öğesi ekleyin.
* `fields`, hangi sütunların görüneceğini ve hangi sırayla görüneceğini kontrol eder. Her alan bir `fieldMetadataUniversalIdentifier` öğesine referans verir. Otomatik oluşturulan `createdAt` gibi bir sistem alanına başvurmak için bkz. [Sistem Metaverilerini Hedefleme](/l/tr/developers/extend/apps/data/system-fields).
* Daha gelişmiş yapılandırmalar için `filters`, `filterGroups`, `sorts`, `groups` ve `fieldGroups` da tanımlayabilirsiniz.
* `position`, aynı nesne için birden fazla görünüm olduğunda sıralamayı kontrol eder.
## İsteğe bağlı özellikler
| Özellik | Değerler | Açıklama |
| ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | `ViewType.TABLE` (varsayılan), `ViewType.LIST`, `ViewType.KANBAN`, `ViewType.CALENDAR` | Kayıtların nasıl düzenlendiği. (`FIELDS_WIDGET`, `TABLE_WIDGET`, `KANBAN_WIDGET`, `LIST_WIDGET` ve `CALENDAR_WIDGET` da mevcuttur ancak sayfa düzeni bileşenleri tarafından dahili olarak kullanılır.) |
| `visibility` | `ViewVisibility.WORKSPACE` (varsayılan), `ViewVisibility.UNLISTED` | Görünümün tüm çalışma alanı için listelenip listelenmediği veya seçicilerden gizlenip gizlenmediği. |
| `openRecordIn` | kullanımdan kaldırıldı | Artık okunmuyor: Kayıtların nerede açılacağı artık [object](/l/tr/developers/extend/apps/data/objects) (`defineObject()` içindeki `openRecordIn`) özelliğiyle belirlenir; ayar yoksa her üyenin kendi tercihi esas alınır. |
| `sorts` | `{ fieldMetadataUniversalIdentifier, direction: ViewSortDirection.ASC \| DESC }[]` | Varsayılan sıralama düzeni. |
| `isCompact` | `boolean` | Kompakt satır görünümü. |
| `mainGroupByFieldMetadataUniversalIdentifier` + `shouldHideEmptyGroups` | — | Kayıtları bir alana göre gruplayın (ör. kanban sütunları). |
| `kanbanAggregateOperation`, `kanbanAggregateOperationFieldMetadataUniversalIdentifier`, `kanbanColumnWidth` | `AggregateOperations.*` | Kanban sütunu toplamları ve boyutlandırması. |
| `calendarLayout`, `calendarFieldMetadataUniversalIdentifier`, `calendarEndFieldMetadataUniversalIdentifier` | `ViewCalendarLayout.DAY` / `WEEK` / `MONTH` | Takvim görünümleri: düzen, kayıtları konumlandıran tarih alanı ve isteğe bağlı bir bitiş tarihi alanı. |
Yukarıdaki tüm enum'lar `twenty-sdk/define` içinden dışa aktarılır.
## Filtreler
Bir görünüm, önceden uygulanmış filtrelerle gelebilir. Her filtrenin üç koordinatı vardır: filtrelenen **alan**, **işleç** (nasıl karşılaştırılacağı) ve **değer** (neyle karşılaştırılacağı). Üçünün de hizalı olması gerekir — bir alan türüne uygulanmayan bir işlecin kullanılması, senkronizasyon sırasında reddedilir.
```ts
import { ViewFilterOperand } from 'twenty-sdk/define';
filters: [
{
universalIdentifier: '...',
fieldMetadataUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
operand: ViewFilterOperand.IS,
value: ['ACTIVE'],
},
],
```
### Alan türüne göre desteklenen işleçler
| Alan tipi | Desteklenen işleçler |
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `TEXT`, `EMAILS`, `FULL_NAME`, `ADDRESS`, `LINKS`, `PHONES`, `RAW_JSON`, `FILES`, `ACTOR`, `ARRAY` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `ACTOR.source`, `ACTOR.workspaceMemberId` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `SELECT` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `MULTI_SELECT` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `RELATION` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `NUMBER` | `IS`, `IS_NOT`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `RATING` | `IS`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `CURRENCY`, `CURRENCY.amountMicros` | `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `CURRENCY.currencyCode` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `DATE`, `DATE_TIME` | `IS`, `IS_RELATIVE`, `IS_IN_PAST`, `IS_IN_FUTURE`, `IS_TODAY`, `IS_BEFORE`, `IS_AFTER`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `BOOLEAN` | `IS` |
| `UUID` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `TS_VECTOR` | `VECTOR_SEARCH` |
> Benzer adlara sahip alan türleri tamamen farklı işleçler kullanabilir — buna `SELECT` ve `MULTI_SELECT` yaygın bir örnektir.
### Her işleç için değer biçimi
`value` alanı her zaman JSON olarak serileştirilebilir bir değerdir, ancak beklenen biçim işlece bağlıdır:
| İşleç ailesi | Değer biçimi | Örnek |
| ------------------------------------------------------------- | ------------------------------------- | ------------------------ |
| `SELECT` üzerinde `IS`, `IS_NOT` | seçenek anahtarları dizisi (metinler) | `['ACTIVE', 'PENDING']` |
| `MULTI_SELECT` üzerinde `CONTAINS`, `DOES_NOT_CONTAIN` | seçenek anahtarları dizisi (metinler) | `['TAG_A']` |
| `RELATION` üzerinde `IS`, `IS_NOT` | kayıt kimlikleri dizisi (uuid'ler) | `['c5a1...']` |
| metin benzeri alanlar üzerinde `CONTAINS`, `DOES_NOT_CONTAIN` | metin | `'acme'` |
| `NUMBER` üzerinde `IS`, `IS_NOT` | metin (değer) | `'5'` |
| `RATING` / `UUID` üzerinde `IS` | metin (değer) | `'5'` |
| `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL` | metin (sınır) | `'10'` |
| `DATE` / `DATE_TIME` üzerinde `IS`, `IS_BEFORE`, `IS_AFTER` | ISO 8601 dizesi | `'2025-01-01T00:00:00Z'` |
| `IS_EMPTY`, `IS_NOT_EMPTY` | boş dize | `''` |
| `BOOLEAN` üzerinde `IS` | `'true'` veya `'false'` | `'true'` |
## Görünümlerin kullanıcı arayüzünde görüntülenme şekli
Tek başına bir görünüm, kenar çubuğundan erişilebilir değildir. Orada görünmesini sağlamak için, türü `VIEW` olan ve görünümün `universalIdentifier` değerini işaret eden bir [navigasyon menüsü öğesi](/l/tr/developers/extend/apps/layout/navigation-menu-items) ile eşleştirin. Bu, kanonik desendir: Her özel nesne, oluşturma sırasında otomatik olarak oluşturulan varsayılan bir indeks görünümü alır; dolayısıyla yalnızca onu açan kenar çubuğu öğesini eklemeniz yeterlidir.

View File

@@ -0,0 +1,197 @@
---
title: Arka Plan İşleri
description: Uzun süren veya hız sınırına takılan işleri, her şeyi satır içi yapmak yerine başka bir mantık fonksiyonu çalıştırmasını kuyruğa alarak Twenty işleyicilerine devredin.
icon: layer-group
---
Bir mantık fonksiyonu çalıştırması, `timeoutSeconds` değeriyle sınırlandırılır (en fazla 900 saniye). Bu sürede tamamlanamayan her şey — tam bir yeniden eşitleme, kayıt başına dağıtım, sizi hız sınırına takan üçüncü taraf bir API — daha küçük çalıştırmalara bölünmek zorundadır.
`enqueueJobs` tam olarak bunu yapar: Twenty işleyicilerinden, uygulamanızın mantık fonksiyonlarından birini daha sonra, her veri yükü için bir kez, her çalıştırma kendi işleminde ve kendi zaman aşımı bütçesiyle çalıştırmasını ister. Çağıran hemen döner.
```text
┌─────────────────┐ enqueueJobs(...) ┌──────────────┐ ┌────────────────────┐
│ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
│ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
└─────────────────┘ └──────────────┘ └────────────────────┘
```
## Çalıştırmaları kuyruğa al
`enqueueJobs` fonksiyonunu `twenty-sdk/logic-function` içinden içe aktarın, çalıştırılacak mantık fonksiyonunun `universalIdentifier` değerini belirtin ve kuyruğa almak üzere her çalıştırma için bir veri yükü iletin.
```ts src/logic-functions/sync-all-contacts.ts
import { enqueueJobs } from 'twenty-sdk/logic-function';
await enqueueJobs({
logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
payloads: [{ page: 1 }],
});
```
Her çalıştırma, tıpkı diğer tetikleyicilerde olduğu gibi, veri yükünü işleyici argümanı olarak alır. Hedef, çağrıyı yapanla **aynı uygulamaya** ait olmalıdır — başka bir uygulamanın fonksiyonunu kuyruğa alma girişimi `Logic function not found` hatasıyla reddedilir ve hiçbir şey kuyruğa alınmaz. Tek bir çağrı en fazla `200` veri yükünü kabul eder.
<Note>
`enqueueJobs`, işler kabul edilir edilmez döner; işler çalıştığında değil. Hedeflerin sonuçlarını döndürmez — bunları daha sonra okumanız gerekiyorsa, her hedefin ürettiği veriyi [anahtar-değer deposuna](/l/tr/developers/extend/apps/logic/key-value-store) veya bir çalışma alanı kaydına yazmasını sağlayın.
</Note>
<Note>
Her çağrıda tek bir işi kuyruğa alan eski `enqueueJob` yardımcısı kullanım dışı bırakılmıştır. Bunun yerine tek öğeli bir `payloads` listesiyle `enqueueJobs` kullanın.
</Note>
## İş seçenekleri
Seçenekler, toplu işlemdeki her çalıştırma için geçerlidir.
| Seçenek | Varsayılan | Aralık | Ne yapar |
| ------------ | ---------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `retryLimit` | `0` | `0`–`10` | Toplam ek kuyruk denemeleri. Uygulama tarafından istenen yeniden denemeler `3` ile sınırlandırılmıştır. Bunu yalnızca iki kez çalıştırılması güvenli olan işleyiciler için artırın. |
| `delayMs` | `0` | `0`–`604800000` (7 gün) | Çalıştırmalar uygun hale gelmeden önce bu kadar süre bekleyin. |
```ts
await enqueueJobs({
logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
payloads: [{ page: 1 }],
retryLimit: 3,
delayMs: 60_000,
});
```
<Note>
**Öncelik henüz yapılandırılamıyor.** Kuyruğa alınan işler her zaman en düşük öncelikte çalışır, bu nedenle platform işleri asla uygulama işlerinin arkasında gecikmez. Öncelik üzerinde denetim yakında geliyor.
</Note>
Kuyruğa alınan çalıştırma, onu kuyruğa alan fonksiyonun etkin kullanıcı bilgisini devralır; böylece aynı izinlerle hareket eder.
## Geçici bir hatayı yeniden dene.
Twenty, uygulama kodundan kaynaklanan her özel durumu yeniden denemez. Normal şekilde fırlatılan bir hata kalıcı hata olarak değerlendirilir. Geçici bir hata için, kuyruktaki bir mantık işlevi `RetryableLogicFunctionError` fırlatarak en fazla üç yeniden deneme isteyebilir.
```ts
import {
type LogicFunctionExecutionContext,
RetryableLogicFunctionError,
} from 'twenty-sdk/logic-function';
export const handler = async (
_payload: unknown,
{ retryCount, maxRetries }: LogicFunctionExecutionContext,
) => {
const response = await fetch('https://api.example.com/contacts');
if (response.status === 429 || response.status >= 500) {
throw new RetryableLogicFunctionError(
`The contacts API is temporarily unavailable (${response.status}); retry ${retryCount} of ${maxRetries}`,
);
}
};
```
Mümkün olduğunda doğrudan `RetryableLogicFunctionError` fırlatın. Bunu genişletirseniz `name` değerini değiştirmeyin: Twenty, serileştirilmiş `RetryableLogicFunctionError` adını yürütme çalışma zamanları arasında tanır.
`retryCount`, ilk yürütme için `0` değerindedir ve yalnızca uygulama kodu yeniden deneme istediğinde artar. `maxRetries` en fazla `3` değerindedir ve kuyruktaki işin genel yeniden deneme sınırı daha düşük olduğunda daha düşük olabilir. Platform hataları, kuyruğun genel güvenlik bütçesini tüketmeye devam etse de `retryCount` değerini artırmaz.
Kuyruk, yeniden deneme girişimlerini üstel geri çekilme ve titreşimle geciktirir. Kesin gecikme kasıtlı olarak garanti edilmez; bu nedenle uygulama kodu, yeniden denemenin kesin bir zamanda gerçekleşmesine bağlı olmamalıdır. `maxRetries` değerine ulaşıldığında, başka bir yürütme olmadan başka bir `RetryableLogicFunctionError` nihai uygulama hatası olarak kaydedilir.
<Warning>
Yeniden denemeler tüm işleyiciyi yeniden çalıştırır ve bazı yan etkiler başarılı olduktan sonra gerçekleşebilir. Yeniden deneme istemeden önce işleyiciyi idempotent hale getirin.
</Warning>
## Kullanım örneği: uzun bir eşitleme boyunca sayfalama yapma
Klasik biçim, bir sonraki imleçle *kendini* kuyruğa alan bir fonksiyondur. Her çalıştırma, kendi zaman aşımı süresinin oldukça altında bir sayfa işi tamamlar ve hiçbir şey kalmadığında zincir durur.
```ts src/logic-functions/sync-contacts-page.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { enqueueJobs } from 'twenty-sdk/logic-function';
const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
const handler = async (params: { cursor?: string }) => {
const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
await importContacts(contacts);
if (nextCursor) {
await enqueueJobs({
logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
payloads: [{ cursor: nextCursor }],
delayMs: 2_000,
});
}
return { imported: contacts.length, done: !nextCursor };
};
export default defineLogicFunction({
universalIdentifier: SYNC_CONTACTS_PAGE,
name: 'sync-contacts-page',
timeoutSeconds: 120,
handler,
});
```
## Kayıt başına fan-out
İş doğal olarak öğe bazındaysa, tek bir çağrıda her öğe için bir işi kuyruğa alın ve döngüyü satır içi çalıştırmak yerine çalışanların bunları paralel olarak işlemesine izin verin.
```ts
const companies = await listCompaniesToEnrich();
await enqueueJobs({
logicFunctionUniversalIdentifier: ENRICH_COMPANY,
payloads: companies.map((company) => ({ companyId: company.id })),
retryLimit: 2,
});
```
## Uzun süre çalışan işler için iyi uygulamalar
Hemen hemen tüm uzun işleri kapsayan iki kural: **döngü kurmak yerine özyineleme (recurse) kullanın** ve **her çalıştırmada sınırlı büyüklükte bir parçayı işleyin**.
Her şeyi tek seferde yapmaya çalışan bir çalıştırma, hata senaryosudur — zaman aşımına takılır ve yeniden denemede her şeyi baştan başlatır. Bunun yerine, bir parçayı `timeoutSeconds` süresinin içinde rahatça bitecek şekilde boyutlandırın, konumunuzu kalıcı hale getirin ve sonraki çalıştırmayı kuyruğa alın.
```ts src/logic-functions/enrich-companies-batch.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { enqueueJobs, kv } from 'twenty-sdk/logic-function';
const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
const CHUNK_SIZE = 50;
const handler = async (params: { offset?: number }) => {
const offset = params.offset ?? 0;
const companies = await listCompaniesToEnrich({
offset,
limit: CHUNK_SIZE,
});
for (const company of companies) {
await enrichCompany(company);
}
await kv.set('enrich:progress', { offset: offset + companies.length });
if (companies.length === CHUNK_SIZE) {
await enqueueJobs({
logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
payloads: [{ offset: offset + CHUNK_SIZE }],
});
}
return { processed: companies.length, done: companies.length < CHUNK_SIZE };
};
export default defineLogicFunction({
universalIdentifier: ENRICH_COMPANIES_BATCH,
name: 'enrich-companies-batch',
timeoutSeconds: 300,
handler,
});
```
Bunu işe yarar kılanlar:
* **Parça boyutunu ortalamaya değil, en yavaş öğeye göre belirleyin.** `CHUNK_SIZE × en kötü durum öğe süresi` değeri, pay bırakacak şekilde `timeoutSeconds` içine sığmalıdır, yoksa parça kuyruğunun sonundakiler çalıştırma kesildiğinde kaybolur.
* **Sonlandırma koşulunu açık hale getirin.** Yalnızca tam dolu bir parça döndüğü sürece özyineleme yapın. Yalnızca "sonuç yok" durumunda duran bir zincir, kaynak ortada bir yerde kısa bir sayfa döndürürse sonsuza dek devam eder.
* **Sonraki çalıştırmayı kuyruğa almadan önce ilerlemeyi kalıcı hale getirin,** böylece başarısız olan bir halka baştan başlamak yerine son tamamlanan parçadan yeniden başlar.
* **Her parçayı idempotent tutun.** Bir yeniden denemeden sonra aynı parçanın yeniden işlenmesi, iki kez yazma yapmamalıdır — yazma işlemlerini, işlediğiniz kayıt veya harici kimlik üzerinde anahtarlandırın.
* **Oran sınırlamalı bir üçüncü tarafla çalışırken, tek bir dev fan-out yerine parçalara bölünmüş bir zinciri tercih edin;** `delayMs` içeren bir zincir kendi hızını ayarlar, oysa binlerce işi bir kerede kuyruğa almak, hepsinin aynı anda uygun hale gelmesine neden olur.

View File

@@ -0,0 +1,269 @@
---
title: Bağlantılar
description: Uygulamanızın, OAuth aracılığıyla üçüncü taraf hizmetlerde kullanıcı adına işlem yapmasına izin verin.
icon: plug
---
Bağlantılar, bir kullanıcının harici bir hizmet için (Linear, GitHub, Slack, ...) sahip olduğu kimlik bilgileridir. Uygulamanız bu kimlik bilgilerinin **nasıl** elde edildiğini — bir **bağlantı sağlayıcısı** — bildirir ve çalışma zamanında üçüncü taraf API'sine kimlik doğrulamalı çağrılar yapmak için bunları kullanır.
Bugün yalnızca OAuth 2.0 destekleniyor. Gelecekteki kimlik bilgisi türleri (kişisel erişim belirteçleri, API anahtarları, basic auth) aynı yüzeye bağlanacak — halihazırda `defineConnectionProvider({ type: 'oauth', ... })` kullanan uygulamaların geçiş yapması gerekmeyecek.
<AccordionGroup>
<Accordion title="defineConnectionProvider" description="Uygulamanızın bağlantılarının nasıl elde edildiğini belirtin">
Bir bağlantı sağlayıcısı, uygulamanızın ihtiyaç duyduğu OAuth el sıkışmasını açıklar. Kullanıcı, uygulamanızın ayarlarında "Bağlantı ekle"ye tıklar, sağlayıcının izin ekranını tamamlar ve çalışma alanında bir `ConnectedAccount` satırı oluşturulur.
Çalışan bir kurulum **iki dosya** gerektirir — bağlantı sağlayıcısı ve OAuth istemci kimlik bilgilerini tutan `defineApplication` üzerindeki eşleşen bir `serverVariables` bildirimi.
```ts src/connection-providers/linear-connection.ts
import { defineConnectionProvider } from 'twenty-sdk/define';
export default defineConnectionProvider({
universalIdentifier: '9c7d1f5e-6a0b-4d44-be0c-3f8b5a9d4e6f',
name: 'linear',
displayName: 'Linear',
icon: 'IconBrandLinear',
type: 'oauth',
oauth: {
authorizationEndpoint: 'https://linear.app/oauth/authorize',
tokenEndpoint: 'https://api.linear.app/oauth/token',
scopes: ['read', 'write'],
// These must match keys in `defineApplication.serverVariables` below.
clientIdVariable: 'LINEAR_CLIENT_ID',
clientSecretVariable: 'LINEAR_CLIENT_SECRET',
// Optional: defaults to 'json'. Some providers (Linear, Slack) want
// 'form-urlencoded' for the token request.
tokenRequestContentType: 'form-urlencoded',
// Optional: defaults to true. Disable only if the provider rejects PKCE.
usePkce: false,
// Optional: extra query params on the authorize URL.
// authorizationParams: { prompt: 'consent' },
// Optional: provider's RFC 7009 token revocation endpoint, called on disconnect.
// revokeEndpoint: 'https://example.com/oauth/revoke',
},
// Optional: a logic function in this app to run right after a connection is
// established. See "Run a logic function on connect".
// onConnectLogicFunction: { universalIdentifier: '3a2b1c0d-...-...' },
// Optional: a logic function in this app to run right after a connection is
// removed. See "Run a logic function on disconnect".
// onDisconnectLogicFunction: { universalIdentifier: '4d5e6f70-...-...' },
});
```
```ts src/application.config.ts
import { defineApplication } from 'twenty-sdk/define';
export default defineApplication({
universalIdentifier: '...',
displayName: 'Linear',
description: 'Connect Linear to Twenty.',
// OAuth client credentials live on the app registration (one OAuth app per
// Twenty server, configured by the admin) — not per-workspace. Declare them
// as serverVariables so the admin can fill them in once for all installs.
serverVariables: {
LINEAR_CLIENT_ID: {
description: 'OAuth client ID from your Linear OAuth application.',
isSecret: false,
isRequired: true,
},
LINEAR_CLIENT_SECRET: {
description: 'OAuth client secret from your Linear OAuth application.',
isSecret: true,
isRequired: true,
},
},
});
```
Önemli noktalar:
* `name`, `listConnections({ providerName })` içinde kullanılan benzersiz tanımlayıcı dizedir (kebab-case, `^[a-z][a-z0-9-]*$` ile eşleşmelidir).
* `displayName` uygulama başına ayarlar sekmesinde ve Yapay Zeka araç listesinde gösterilir.
* `clientIdVariable` / `clientSecretVariable` değer değil, **isimdir** — `defineApplication.serverVariables` içinde bildirilen anahtarlarla eşleşmelidir. Gerçek `client_id` ve `client_secret`, sunucu yöneticisi tarafından uygulama kayıt arayüzü üzerinden girilir; deponuza asla commit edilmez.
* `serverVariables` kullanın (`applicationVariables` değil) — OAuth kimlik bilgileri sunucu genelidir ve her Twenty sunucusu için bir OAuth uygulaması vardır.
* Her iki `serverVariables` da doldurulana kadar, uygulama başına ayarlar sekmesi "sunucu yöneticisine ihtiyaç var" ipucunu gösterir ve "Bağlantı ekle" düğmesi devre dışı bırakılır.
* `type: 'oauth'` bugün desteklenen tek değerdir. Ayırt edici alan ileriye dönük uyumludur: gelecekteki türler (`'pat'`, `'api-key'`, ...) `oauth` yanında yeni alt yapılandırma blokları eklenecektir.
Sağlayıcınızın beyaz listeye alması gereken OAuth geri çağrı URL'si şudur:
```
https://<your-twenty-server>/auth/apps/callback
```
</Accordion>
<Accordion title="Bağlanıldığında bir mantık fonksiyonu çalıştırın" description="Bir bağlantı kurulur kurulmaz tepki verin">
Bazı sağlayıcılar, bağlantı anında size, bağlantı kullanılabilir hale gelmeden önce kalıcı hale getirmeniz gereken veriler verir — klasik örnek Slack'tir; burada OAuth yanıtı, gelen olayların anahtarlanacağı çalışma alanının `team_id` bilgisini sağlar. `onConnectLogicFunction` değerini, aynı uygulamadaki bir mantık fonksiyonuna (onun `universalIdentifier` değeriyle) referans verecek şekilde ayarlayın; böylece `ConnectedAccount` oluşturulduktan hemen sonra çalışır.
```ts src/connection-providers/slack-connection.ts
export default defineConnectionProvider({
universalIdentifier: '...',
name: 'slack',
displayName: 'Slack',
type: 'oauth',
oauth: {
/* ... */
},
// Runs claimSlackTeam after every successful Slack connection.
onConnectLogicFunction: {
universalIdentifier: '3a2b1c0d-1111-4222-8333-444455556666',
},
});
```
Kanca, **bağlanan çalışma alanında eşzamansız olarak** çalışır (kuyruğa alınır, beklenmez), bu nedenle yavaşlayan veya başarısız olan bir kanca hiçbir zaman OAuth geri çağrısını engellemez veya bozmaz — onu idempotent yapın ve kendi tekrar denemelerini kendisi yönetsin. İşleyici şunları alır:
```ts
type OnConnectPayload = {
connectionProviderId: string;
connectionProviderName: string; // e.g. 'slack'
connectedAccountId: string;
};
```
Buradan `getConnection(connectedAccountId)` kullanarak yeni erişim belirtecini okuyun ve sağlayıcının API'sini (ör. Slack `auth.test`) çağırın veya [key-value store](/l/tr/developers/extend/apps/logic/key-value-store) ile bir eşleme kalıcı hale getirin.
</Accordion>
<Accordion title="Bağlantı kesildiğinde bir mantık fonksiyonu çalıştırın" description="Bir bağlantı kaldırıldığında temizleme işlemi yapın">
Bir uygulamanın bağlanma anında talep ettiği her şey, bağlantı sona erdiğinde serbest bırakılmalıdır. Örneğin, bağlanırken bir `team_id` talep eden bir Slack entegrasyonu, başka bir çalışma alanının aynı Slack ekibini bağlayabilmesi için bu talebi serbest bırakmak zorundadır. Aynı uygulamadaki bir mantık fonksiyonuna referans vermesi için `onDisconnectLogicFunction` değerini ayarlayın; böylece `ConnectedAccount` silindikten hemen sonra çalışır.
```ts src/connection-providers/slack-connection.ts
export default defineConnectionProvider({
universalIdentifier: '...',
name: 'slack',
displayName: 'Slack',
type: 'oauth',
oauth: {
/* ... */
},
// Runs releaseSlackTeam after every Slack disconnection.
onDisconnectLogicFunction: {
universalIdentifier: '4470aba8-5ff5-4800-88db-2a427cd8677c',
},
});
```
Bağlanma kancası gibi, **bağlantısı kesilmekte olan çalışma alanında eşzamansız olarak** çalışır ve bağlantı kesme işlemini asla engellemez. İşleyici aynı payload biçimini alır:
```ts
type OnDisconnectPayload = {
connectionProviderId: string;
connectionProviderName: string; // e.g. 'slack'
connectedAccountId: string;
};
```
Kanca çalıştığında `ConnectedAccount` zaten kaldırılmıştır, bu yüzden `getConnection(connectedAccountId)` artık sonuç döndürmez. Temizliğin ihtiyaç duyduğu her şey (`team_id`, harici abonelik kimliği gibi) bağlanma sırasında, `connectedAccountId` anahtarıyla [key-value store](/l/tr/developers/extend/apps/logic/key-value-store) içine yazılmış olmalıdır.
Kanca, bir bağlantı kendi başına kaldırıldığında tetiklenir. Uygulamanın kaldırılması, bağlantılarını bunun yerine bir veritabanı zincirleme silme işlemiyle kaldırır, bu nedenle kanca burada çalışmaz. Bu yol için `defineApplication` üzerinde bir `uninstallLogicFunction` bildirin: bu işlev, uygulamanın meta verileri silinmeden önce çalışır, böylece hâlâ `listConnections` çağırabilir ve geride kalan her şeyi temizleyebilir.
</Accordion>
<Accordion title="listConnections / getConnection" description="Bir mantık işlevinden bağlantıları kullanın">
Bir mantık işlevi işleyicisi içinde, `listConnections({ providerName })`, verilen sağlayıcı için bu uygulamanın `ConnectedAccount` satırlarını, yenilenmiş erişim belirteçleriyle döndürür.
```ts src/logic-functions/handlers/create-linear-issue-handler.ts
import { listConnections } from 'twenty-sdk/logic-function';
export const createLinearIssueHandler = async (input: {
teamId?: string;
title?: string;
}) => {
if (!input.teamId || !input.title) {
return { success: false, error: 'teamId and title are required' };
}
const connections = await listConnections({ providerName: 'linear' });
// Workspace-shared credentials win when present; fall back to the first
// user-visibility one. For HTTP-route triggers you typically pick the
// request user's connection via event.userWorkspaceId instead.
const connection =
connections.find((c) => c.visibility === 'workspace') ?? connections[0];
if (!connection) {
return {
success: false,
error:
'Linear is not connected. Open the app settings and click "Add connection".',
};
}
// Use connection.accessToken to call the third-party API.
const response = await fetch('https://api.linear.app/graphql', {
method: 'POST',
headers: {
Authorization: `Bearer ${connection.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `mutation { issueCreate(input: { teamId: "${input.teamId}", title: "${input.title}" }) { success } }`,
}),
});
return { success: response.ok };
};
```
Her bağlantı şunlara sahiptir:
| Alan | Açıklama |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id` | Tekil satır kimliği; tek bir tanesini yeniden getirmek için `getConnection(id)` işlevine iletin |
| `visibility` | `'user'` (bir çalışma alanı üyesine özel) veya `'workspace'` (tüm üyelerle paylaşılan) |
| `scopes` | Üst sağlayıcı tarafından verilen OAuth izinleri (`visibility` ile karıştırılmamalıdır — bunlar ilişkili değildir) |
| `userWorkspaceId` | Sahibinin userWorkspace kimliği — HTTP rota tetikleyicilerinde "istek kullanıcısının bağlantısını" seçmek için kullanışlıdır |
| `accessToken` | Yeni OAuth erişim belirteci (süresi dolmuşsa otomatik olarak yenilenir) |
| `name` | Bağlantının görünen adı (OAuth geri çağrısında otomatik türetilir, kullanıcı tarafından yeniden adlandırılabilir) |
| `tanıtıcı` | Bağlı yukarı akış hesabının e-postası, her yeniden bağlanmada yenilenir; jeton değişiminde döndürülen OIDC `id_token` içinden belirlenir ve sağlayıcı bir e-posta döndürmezse bağlanan Twenty kullanıcısının e-postasına geri düşer. |
| `authFailedAt` | En son yenileme başarısız olduğunda ayarlanır; kullanıcı yeniden bağlanmalıdır |
Önemli noktalar:
* Sağlayıcıya göre filtrelemek için `{ providerName }` iletin; bu uygulamanın tüm sağlayıcılardaki tüm bağlantılarını almak için bunu atlayın.
* Sunucu, döndürmeden önce erişim belirtecini şeffaf bir şekilde yeniler. İşleyiciniz her zaman kullanılabilir bir belirteç görür (veya `authFailedAt` ayarlanmıştır).
* `getConnection(id)`, tek satırlık karşılığıdır.
</Accordion>
<Accordion title="Kullanıcıya özel ve çalışma alanı paylaşımlı görünürlük" description="Kullanıcıların özel ve paylaşılan kimlik bilgileri arasında nasıl seçim yaptığı">
Bir kullanıcı "Bağlantı ekle"ye tıkladığında, bir görünürlük seçmesi istenir:
* **Yalnızca benim için** — kimlik bilgisi, bağlanan kullanıcıya özeldir. Adlarına çağrılan herhangi bir mantık işlevi (`isAuthRequired: true` ile HTTP rota tetikleyicisi) bunu görür; cron tetikleyicileri ve veritabanı olayları görmez.
* **Çalışma alanı paylaşımlı** — herhangi bir çalışma alanı üyesi bu kimlik bilgisini kullanabilir. Cron / veritabanı tetikleyicileri de görür, çünkü istek kullanıcısı yoktur.
Her işleyici için doğru olanı kullanın:
```ts
// HTTP-route trigger — prefer the request user's own connection.
const conn =
connections.find((c) => c.userWorkspaceId === event.userWorkspaceId) ??
connections.find((c) => c.visibility === 'workspace');
// Cron trigger — no request user; only shared credentials are sensible.
const conn = connections.find((c) => c.visibility === 'workspace');
```
Kullanıcı ve sağlayıcı başına birden çok bağlantıya izin verilir; böylece aynı kullanıcı "Personal Linear" ve "Work Linear" bağlantılarını yan yana tutabilir.
</Accordion>
<Accordion title="Tek seferlik sağlayıcı kurulumu" description="OAuth uygulamanızı üçüncü taraf hizmete kaydedin">
Her bağlantı sağlayıcısı için, sunucu yöneticisinin önce üçüncü tarafta bir OAuth uygulaması kaydetmesi gerekir.
1. Sağlayıcının geliştirici ayarlarına gidin (örn. https://linear.app/settings/api/applications/new).
2. **Redirect URI**'yi `\<SERVER_URL>/auth/apps/callback` olarak ayarlayın.
3. Oluşturulan **Client ID** ve **Client Secret**'ı kopyalayın.
4. Yüklü uygulamayı Twenty'de bir sunucu yöneticisi olarak açın → karşılık gelen `serverVariables` üzerinde değerleri ayarlayın.
5. Ardından çalışma alanı üyeleri, uygulama başına **Bağlantılar** bölümünden bağlantılar ekleyebilir.
</Accordion>
</AccordionGroup>

View File

@@ -0,0 +1,110 @@
---
title: Anahtar-Değer Deposu
description: Yerleşik uygulama anahtar-değer deposuyla ara sonuçları kalıcı hale getirin, verileri önbelleğe alın ve mantık işlevi çalıştırmaları arasında durumu paylaşın.
icon: database
---
Mantık işlevleri, kısa ömürlü, izole Node.js süreçlerinde çalışır — bir çalıştırma tamamlandıktan sonra, bellekte tutulan hiçbir şey kalıcı olmaz. Çalıştırmalar arasında **bir şeyi hatırlamanız** gerektiğinde (maliyetli bir API yanıtını önbelleğe almak, artımlı eşitlemeler için bir imleç saklamak, işleri ertelemek ya da durumu bir işlevden diğerine aktarmak için), bunu yerleşik anahtar-değer deposunda kalıcı hale getirin.
Her uygulamanın kendi yalıtılmış ad alanı vardır: girişler, kimliği doğrulanmış uygulamaya göre anahtarlanır, böylece anahtarlarınız başka bir uygulamayla asla çakışamaz veya başka bir uygulama tarafından okunamaz.
```text
┌─────────────────┐ kv.set(key, value) ┌──────────────────────────┐
│ Logic function │ ─────────────────────▶ │ Application KV store │
│ (your handler) │ ◀───────────────────── │ key (unique) │ value │
└─────────────────┘ kv.get(key) └──────────────────────────┘
```
## Getir, ayarla, sil
`kv` öğesini `twenty-sdk/logic-function` içinden içe aktarın. Değerler, JSON olarak serileştirilebilir herhangi bir veri yükü olabilir.
```ts src/logic-functions/sync-linear-issues.ts
import { kv } from 'twenty-sdk/logic-function';
// Read a value. Returns null when the key is missing.
const cursor = await kv.get<string>('sync-cursor:linear');
// Write a value. Creates the entry on first write, updates it afterwards.
await kv.set('sync-cursor:linear', newCursor);
// Delete an entry. Returns true when an entry was removed.
await kv.delete('sync-cursor:linear');
```
## Kapsamlar
Her girişin, her çağrıda bir seçenek olarak iletilen bir kapsamı vardır. Varsayılan `WORKSPACE` değeridir.
* **`WORKSPACE`** (varsayılan) — giriş, uygulamanızın geçerli çalışma alanı kurulumuna özeldir. Uygulamayı kuran her çalışma alanı kendi bağımsız anahtar setini alır. Önbellekler, imleçler ve çalışma alanı başına durum için istediğiniz budur.
* **`SERVER`** — giriş, sunucudaki uygulamanızın **her kurulumuyla** paylaşılır. Sunucu girişleri **claim** gibi davranır: saklanan değer, anahtarı talep eden çalışma alanı kimliği (geçerli çalışma alanı için anahtarı talep etmek üzere `set` çağrısında `value` belirtmeyin) olur ve yalnızca o çalışma alanı bu değeri üzerine yazabilir veya silebilir. Her kurulum girişi okuyabilir.
Sunucu claim'leri, çalışma alanları arası yönlendirme için vardır. [server-route resolver](/l/tr/developers/extend/apps/logic/logic-functions#server-route-trigger), uygulama kaydı sahibinin çalışma alanında çalışır, ancak gelen bir web kancası genellikle yalnızca harici bir hesap kimliği taşır — bir Twenty çalışma alanı kimliği taşımaz. Her çalışma alanının, bağlanma anında kendi harici kimliğini talep etmesini sağlayın, ardından bunu rotada çözümleyin:
```ts
// In the connected workspace, when the external account is linked:
await kv.set(`slack:team:${teamId}`, undefined, { scope: 'SERVER' });
// In the server-route resolver (owner workspace), on each webhook:
const workspaceId = await kv.get<string>(`slack:team:${teamId}`, {
scope: 'SERVER',
});
```
Bir sunucu anahtarı yalnızca çağıranın kendi çalışma alanı için talep edilebildiğinden ve başka bir çalışma alanı tarafından asla üzerine yazılamadığından, bir çalışma alanı başkasına ait bir eşlemeyi ele geçiremez. Anahtar zaten başka bir çalışma alanı tarafından talep edilmişse `kv.set` hata fırlatır.
## Kullanın: maliyetli bir çağrıyı önbelleğe alın
Yaygın bir kullanım, yavaş veya hız sınırına tabi üçüncü taraf yanıtını önbelleğe almak, böylece tekrar eden çalıştırmalar her seferinde maliyet ödemek yerine bunu yeniden kullanır.
```ts src/logic-functions/getExchangeRate.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { kv } from 'twenty-sdk/logic-function';
const ONE_HOUR_MS = 60 * 60 * 1000;
type CachedRate = { rate: number; fetchedAt: number };
const handler = async (params: { from: string; to: string }) => {
const cacheKey = `cache:exchange-rate:${params.from}:${params.to}`;
const cached = await kv.get<CachedRate>(cacheKey);
if (cached && Date.now() - cached.fetchedAt < ONE_HOUR_MS) {
return { rate: cached.rate, cached: true };
}
const response = await fetch(
`https://api.example.com/rate?from=${params.from}&to=${params.to}`,
);
const { rate } = (await response.json()) as { rate: number };
await kv.set(cacheKey, { rate, fetchedAt: Date.now() });
return { rate, cached: false };
};
export default defineLogicFunction({
universalIdentifier: 'd9b2f4e6-1c83-4a07-9e52-6b1d3c8a0f47',
name: 'get-exchange-rate',
timeoutSeconds: 10,
handler,
});
```
## Kalıplar ve ipuçları
* **Ad alanları.** Farklı konuları birbirinden ayırmak için anahtarlara ön ek ekleyin — `sync-cursor:linear`, `cache:exchange-rate:USD:EUR`, `lock:nightly-report`.
* **Sona erme (TTL).** Depoda yerleşik bir sona erme özelliği yoktur. Okuma sırasında kontrol etmek için değerin içinde (önbellek örneğinde olduğu gibi) bir zaman damgası saklayın veya eski anahtarları bir [cron-triggered function](/l/tr/developers/extend/apps/logic/logic-functions) içinden temizleyin.
* **Ne saklanmalı.** Sayılar, dizeler, diziler, nesneler gibi JSON olarak serileştirilebilir herhangi bir değer. Kayıtları küçük tutun; bu, koordinasyon ve önbelleğe alma içindir, büyük blob'lar veya dosyalar için değil. Dosyalar için bir `FILES` alanı ve [`uploadFile`](/l/tr/developers/extend/apps/logic/logic-functions#uploading-files) kullanın.
* **Görünürlük.** Girişler, çalışma alanı kayıtları olarak değil, instance veritabanında tutulur — çalışma alanı arayüzünde asla görünmezler, uygulamanızın veri modelinin parçası değildirler ve herhangi bir rol veya nesne iznine ihtiyaç duymazlar.
## Alternatif: sorgulanabilir bir depo nesnesi
Yerleşik depo kasıtlı olarak opaktır: girişler kayıt değildir, bu nedenle onları arayüzde gezemez, diğer nesnelerle ilişkilendiremez veya kayıt sorgularıyla filtreleyemezsiniz. Bunlardan herhangi birine ihtiyaç duyduğunuzda — örneğin görünür bir eşitleme günlüğü veya kayıt başına durum — bunun yerine benzersiz bir `key` alanına ve bir `RAW_JSON` `value` alanına sahip küçük bir **teknik nesne** tanımlayın ve onu [typed API client](/l/tr/developers/extend/apps/logic/logic-functions#typed-api-clients-twenty-client-sdk) aracılığıyla sorgulayın. `defineObject` başvurusu için [Objects](/l/tr/developers/extend/apps/data/objects) bölümüne ve anahtar benzersizliğini zorlamak için [Data → Unique indexes](/l/tr/developers/extend/apps/data/overview#unique-indexes) bölümüne bakın.
* **Bir kayda kapsam belirleme.** Kimliği anahtarın içine kodlamak yerine, depo nesnesinden hedef nesneye bir [relation](/l/tr/developers/extend/apps/data/relations) ekleyin.
* **Görünürlük ve izinler.** Satırlar, diğer kayıtlar gibi çalışma alanı veritabanında yaşar; bu nedenle API üzerinden sorgulanabilirler ve uygulamanızın [role](/l/tr/developers/extend/apps/config/roles) yapılandırmasına uyarlar. Depoyu ana arayüzün dışında tutmak için, onu [navigation menu](/l/tr/developers/extend/apps/layout/navigation-menu-items) dışında bırakın.
<Note>
Yerleşik depodan farklı olarak, özel bir nesne her zaman tek bir çalışma alanına kapsamlanır — `SERVER` anahtarlarının yaptığı gibi kurulumlar arasında girişleri paylaşamaz.
</Note>

View File

@@ -0,0 +1,909 @@
---
title: Mantıksal işlevler
description: Sunucu tarafı TypeScript işlevlerini HTTP, cron ve veritabanı olay tetikleyicileriyle tanımlayın.
icon: bolt
---
Mantık işlevleri, Twenty platformunda çalışan sunucu tarafı TypeScript işlevleridir. HTTP istekleri, cron zamanlamaları veya veritabanı olayları tarafından tetiklenebilirler — ve ayrıca yapay zekâ ajanları için araçlar olarak sunulabilirler.
<AccordionGroup>
<Accordion title="defineLogicFunction" description="Mantık işlevlerini ve tetikleyicilerini tanımlayın">
Her işlev 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 { RoutePayload } from 'twenty-sdk/logic-function';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async (params: RoutePayload) => {
const client = new CoreApiClient();
const body = (params.body ?? {}) as { name?: string };
const name = body.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? '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: 'POST',
isAuthRequired: true,
},
/*databaseEventTriggerSettings: {
eventName: 'people.created',
},*/
/*cronTriggerSettings: {
pattern: '0 0 1 1 *',
},*/
});
```
Kullanılabilir tetikleyici türleri:
* **httpRoute**: İşlevinizi bir HTTP yolu ve yöntemiyle kullanıma sunar. Uygulama kodunda, `RestApiClient` kullanırken rota yolunun başına `/s/` önekini ekleyin; dağıtılan URL, enjekte edilen `TWENTY_FUNCTIONS_URL` temel URL'sini (veya bu ayarlı değilse `\<server-url>/s`) kullanır.
<Note>
Arayüzsüz bir ön uç bileşeninden rota tarafından tetiklenen mantık fonksiyonunu çağırmak için bkz. [Mantık fonksiyonu çağırma](/l/tr/developers/extend/apps/layout/front-components#calling-a-logic-function).
</Note>
* **cron**: Bir CRON ifadesi kullanarak işlevinizi 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 işlevi tetikler.
> örn. `person.updated`, `*.created`, `company.*`
* **serverRoute**: Kayıt kapsamına sahip tek bir HTTP rotasını erişime açar. Bir **resolver** fonksiyonu (`serverRouteTriggerSettings` ile tanımlanır) sahip çalışma alanında çalışır ve ya senkron bir `Response` ya da kuyruğa eklenecek hedef çalışma alanını VE mantık fonksiyonunu döndürür; kuyruğa ekleme yolunda platform `202` ile onay verir ve bu **hedefi** worker kuyruğunda çalıştırır. [Sunucu rota tetikleyicisine](#server-route-trigger) bakın.
<Note>
Bir işlevi CLI kullanarak manuel olarak da çalıştırabilirsiniz:
```bash filename="Terminal"
yarn twenty dev:function:exec -n create-new-post-card -p '{"key": "value"}'
```
```bash filename="Terminal"
yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
```
Günlükleri şu şekilde izleyebilirsiniz:
```bash filename="Terminal"
yarn twenty dev:function:logs
```
</Note>
#### 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/logic-function` içinden içe aktarın:
```ts
import type { RoutePayload } from 'twenty-sdk/logic-function';
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\<string, string \| undefined>` | HTTP başlıkları (`forwardedRequestHeaders` içinde listelenenlerle sınırlı) | aşağıdaki bölüme bakın |
| `queryStringParameters` | `Record\<string, string \| undefined>` | 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\<string, string \| undefined>` | 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 }` |
| `rawBody` | `string \| undefined` | JSON ayrıştırılmadan önceki özgün UTF-8 istek gövdesi. HMAC tarzı webhook imzalarını doğrulamak için kullanışlıdır (ör. GitHub'ın `X-Hub-Signature-256`, Stripe). Çalışma zamanı onu korumadığında `undefined` olur. | |
| `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 };
};
```
<Note>
Başlık adları küçük harfe normalize edilir. Onlara küçük harfli anahtarlarla erişin (örneğin, `event.headers['content-type']`).
</Note>
#### Özel HTTP yanıtı
Varsayılan olarak, işleyicinizden düz bir değer döndürmek, onu `200` yanıtı olarak geri gönderir (nesneler için JSON, stringler için `text/plain`). Durum kodunu ve yanıt üstbilgilerini kontrol etmek için, `twenty-sdk/logic-function` içinden bir `Response` döndürün:
```ts
import { Response } from 'twenty-sdk/logic-function';
const handler = async (event: RoutePayload) => {
return new Response('<h1>Hello</h1>', {
status: 201,
headers: { 'content-type': 'text/html' },
});
};
```
Güvenlik nedenleriyle, yanıt üstbilgileri bir izin listesiyle sınırlandırılmıştır. Listede yer almayan herhangi bir üstbilgi (örneğin `Set-Cookie`, `Access-Control-Allow-Origin` gibi CORS üstbilgileri veya özel `X-*` üstbilgileri), yanıt gönderilmeden önce sessizce yok sayılır. İzin verilen yanıt üstbilgileri şunlardır:
* `content-type`
* `content-language`
* `content-disposition`
* `cache-control`
* `retry-after`
<Note>
Durum kodu geçerli bir HTTP durum kodu olmalıdır (100 ile 599 arasında). Yanıt üstbilgisi adları büyük/küçük harfe duyarsız olarak eşleştirilir.
</Note>
#### Platform hata yanıtları
İşleyicinizin kendi yanıtlarının ötesinde, platform bazı durumlarda rota çağrılarına doğrudan yanıt verir: rota veya fonksiyon mevcut olmadığında `404`, uygulama durdurulduğunda `403`, yürütme hız sınırına ulaşıldığında `429` ve uygulamanın üretim `dependencies` değerleri kurulamayacak kadar büyük olduğunda `422` — bkz. [dependencies boyut sınırları](/l/tr/developers/extend/apps/getting-started/project-structure#dependencies).
#### Sunucu rota tetikleyicisi
`httpRouteTriggerSettings`, `/s/` altında bir fonksiyon sunar ve çalışma alanını istek ana bilgisayarından çözümler — bu da her çalışma alanının kendi alan adına sahip olduğu durumda işe yarar. Üçüncü taraf sağlayıcılar ise, her kiracının olaylarını **tek** URL’ye iletir. Bu durum için `serverRouteTriggerSettings` kullanın.
Tetikleyicinin iki parçası vardır:
1. Bir **resolver** mantık fonksiyonu — `serverRouteTriggerSettings` ile tanımlanır — **sahip çalışma alanınızda** (uygulama kaydına sahip olan çalışma alanı) çalışır. Gelen isteği inceler ve şu ikisinden birini döndürür:
* `{ workspaceId, targetLogicFunctionUniversalIdentifier, payload? }` — platform bu hedefi çözümlenen çalışma alanında kuyruğa ekler ve `202 { queued: true }` ile onay verir, veya
* `twenty-sdk/logic-function` içinden bir `Response` — platform bu HTTP yanıtını **senkron** şekilde yansıtır ve **hiçbir** hedefi kuyruğa eklemez (bunu Slack `url_verification` gibi doğrulama el sıkışmaları için kullanın).
Resolver, yetkilendirmenin tek noktasıdır — URL yalnızca resolver'ın tanımlayıcısını taşır. **İstek imzalarını doğrulamak için tercih edilen yer burasıdır**: resolver, herhangi bir yan etkiden önce çalışır, orijinal `rawBody` ve iletilen başlıklara erişebilir ve hedefe hiç dokunmadan isteği reddedebilir.
2. Ardından bir **hedef** mantık fonksiyonu — her çalışma alanı için normal bir mantık fonksiyonu — resolver tarafından döndürülen payload ile (veya resolver onu dönüştürmediyse orijinal istek payload'ı ile) çözümlenen çalışma alanında çalışır. Çözümleyici kuyruğa alma yolunu seçtiğinde, döndürdüğü değer HTTP çağrıcısı tarafından **gözlemlenmez**.
```ts src/logic-functions/resolve-server-route.logic-function.ts
import { createHmac, timingSafeEqual } from 'crypto';
import { defineLogicFunction } from 'twenty-sdk/define';
import { Response, type RoutePayload } from 'twenty-sdk/logic-function';
// Runs in the owner workspace. Verifies the request signature, picks
// which target function should handle the event, and returns the
// workspace + target the platform should dispatch to.
const handler = async (event: RoutePayload) => {
// Fail closed if the secret isn't configured — never fall back to an
// empty key, which would let any caller forge a matching signature.
const secret = process.env.GITHUB_WEBHOOK_SECRET;
if (!secret) {
throw new Error('GITHUB_WEBHOOK_SECRET is not configured');
}
const signature = event.headers['x-hub-signature-256'] ?? '';
const expected =
'sha256=' +
createHmac('sha256', secret).update(event.rawBody ?? '').digest('hex');
const a = Buffer.from(signature);
const b = Buffer.from(expected);
if (a.length !== b.length || !timingSafeEqual(a, b)) {
throw new Error('invalid signature');
}
const body = (event.body ?? {}) as {
challenge?: string;
metadata?: { twentyWorkspaceId?: string };
type?: string;
};
// Handshakes must be answered on this same response, so reply from the
// resolver instead of returning a dispatch target.
if (body.type === 'url_verification') {
return new Response({ challenge: body.challenge });
}
const workspaceId = body.metadata?.twentyWorkspaceId;
if (!workspaceId) {
throw new Error('event is not linked to a workspace');
}
return {
workspaceId,
// Route different event types to different target functions.
targetLogicFunctionUniversalIdentifier:
body.type === 'invoice.paid'
? 'c4e2a9b1-7d4e-4c9a-9f2b-2e1d6a4c8e10' // handle-invoice-paid
: 'd5f3b0c2-8e5f-5d0b-a0c3-3f2e7b5d9f21', // handle-other-event
};
};
export default defineLogicFunction({
universalIdentifier: 'b3c2f0a1-7d4e-4c9a-9f2b-2e1d6a4c8e10',
name: 'resolve-server-route',
handler,
serverRouteTriggerSettings: {
forwardedRequestHeaders: ['x-hub-signature-256'],
},
});
```
```ts src/logic-functions/handle-invoice-paid.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/logic-function';
// Runs in the resolved workspace. The resolver has already authenticated
// the request, so this handler can focus on the actual work.
const handler = async (event: RoutePayload) => {
// ...handle the verified event
return { received: true };
};
export default defineLogicFunction({
universalIdentifier: 'c4e2a9b1-7d4e-4c9a-9f2b-2e1d6a4c8e10',
name: 'handle-invoice-paid',
handler,
});
```
Uç noktaya şu adresten erişilebilir:
```
POST https://your-twenty-server.com/webhooks/server/:resolverLogicFunctionUniversalIdentifier
```
Tanımlayıcı, manifest'inizdeki resolver'ın `universalIdentifier` değeridir. Bu URL’yi sağlayıcıya kaydedin.
**Bir doğrulama `GET` isteğine yanıt verme.** Bazı sağlayıcılar, daha sonra olayları `POST` edecekleri aynı URL'ye bir sorgu içeren `GET` isteği göndererek, teslimat yapmadan önce bir uç noktayı doğrular — Meta'nın WhatsApp Cloud API'si bunlardan biridir. Bir sunucu rotası, aksini belirtmediğiniz sürece yalnızca `POST` isteklerine yanıt verir; bu nedenle her iki yöntemi de bildirin:
```ts
serverRouteTriggerSettings: {
forwardedRequestHeaders: ['x-hub-signature-256'],
httpMethods: ['GET', 'POST'],
}
```
Sorgu `event.queryStringParameters` içinde gelir ve bir `Response` döndürmek, onu aynı istekte sağlayıcıya geri iletir. Bir dize gövdesi `text/plain` olarak gönderilir; bu sağlayıcıların beklediği de budur:
```ts
const handler = async (event: RoutePayload) => {
if (event.requestContext.http.method === 'GET') {
return new Response(event.queryStringParameters['hub.challenge']);
}
// …handle the delivered event
};
```
`httpMethods`, varsayılan ayara ekleme yapmak yerine onun yerini alır; bu nedenle tek başına `['GET']`, rotanın `POST` isteğini reddetmesine neden olur. Yalnızca `GET` ve `POST` desteklenir. Sağlayıcı ikinci HTTP yöntemine ihtiyaç duymadığı sürece bunu ayarlanmamış bırakın: `GET` bildiren bir rotanın resolver'ı, istek göndermeden `GET` gönderen tarayıcılar ve bağlantı önizleme araçları da dahil olmak üzere, kimliği doğrulanmamış herhangi bir çağıran tarafından çalıştırılır. Platformun yöntemi olmayan her şeye, resolver hiç çalışmadan `405` yanıtı verilir.
<Note>
**Uygulama, sahip çalışma alanında sahiplenilip kurulmalıdır.** Çözücü **sahip çalışma alanında** (uygulama kaydına sahip olan çalışma alanı) çalıştığı için, bir sunucu rota tetikleyicisi yalnızca uygulama *sahiplenildikten* — yani bir sahip çalışma alanına sahiptir — **ve** o uygulama **sahip çalışma alanına kurulduğunda** çalışır. Her ikisi de doğru olana kadar çözücünün çalışacağı bir yer yoktur, bu yüzden rota çalıştırılamaz. Bu nedenle, `serverRouteTriggerSettings` mantık işlevini sunan bir uygulama, sahip çalışma alanında sahiplenilip kurulana kadar pazaryerinde listelenemez.
</Note>
**Resolver sözleşmesi.** SDK'nin `LogicFunctionConfig` türü bunu derleme zamanında zorunlu kılar: `serverRouteTriggerSettings`'i ayarladığınız anda, işleyicinizin ya bir `Response` ya da `{ workspaceId: string; targetLogicFunctionUniversalIdentifier: string; payload?: object }` (veya bunlardan birinin `Promise`'i) döndürmesi gerekir. Dispatch yolunda, `workspaceId` hedef fonksiyonun kurulu olduğu bir çalışma alanı olmalıdır, aksi takdirde istek `404` ile reddedilir. Her iki şekille de eşleşmeyen bir sonuç — tanımlayıcıları UUID olmayanlar da dahil — `502` ile reddedilir.
| Alan | Tür | Notlar |
| ---------------------------------------- | ----------------------- | -------------------------------------------------------------------------------- |
| `workspaceId` | `string` | Hedefin çalışacağı çalışma alanının UUID'si. |
| `targetLogicFunctionUniversalIdentifier` | `string` | O çalışma alanında çağrılacak mantık fonksiyonunun `universalIdentifier` değeri. |
| `payload` | `object` (isteğe bağlı) | Ayarlanırsa, hedefe gönderilen istek gövdesinin yerini alır. |
<Warning>
**İmza doğrulama sizin sorumluluğunuzdadır — doğrulamayı resolver içinde yapın.** Platform istek imzalarını doğrulamaz. Bunu yapmak için önerilen yer resolver'dır: ilk olarak çalışır, `event.rawBody` ve `forwardedRequestHeaders` içinde listelediğiniz başlıklara erişir ve fırlatılan bir hata (veya eşleşmeyen herhangi bir `workspaceId`) hedef çağrılmadan önce yönlendirmeyi durdurur. Doğrulamayı bunun yerine hedefin içine iterseniz, hedef `rawBody` ve başlıkları kaybetmemeye dikkat etmelidir — yani resolver bir `payload` döndürmemelidir. Her zaman herhangi bir yan etkiden **önce** doğrulayın ve sabit süreli bir karşılaştırma kullanın.
</Warning>
İstek imzaları için, sağlayıcıların çoğu HMAC-SHA256 ile imzalar; farklı olan kısımlar başlık adı, özet kodlaması ve imzalanan payload dizesidir. Birkaç örnek:
| Sağlayıcı | İletilecek başlıklar | İmzalanmış dize | Özet |
| ---------------------------- | ------------------------------------------------------ | ---------------------------- | ---------------------------------------------------------------- |
| Svix (Recall, Resend, Clerk) | `webhook-id`, `webhook-timestamp`, `webhook-signature` | `{id}.{timestamp}.{rawBody}` | base64 (gizli anahtar, `whsec_` kaldırıldıktan sonra base64’tür) |
| Stripe | `stripe-signature` | `{timestamp}.{rawBody}` | hex |
| GitHub | `x-hub-signature-256` | `{rawBody}` | hex (`sha256=` önekiyle) |
| Shopify | `x-shopify-hmac-sha256` | `{rawBody}` | base64 |
| Slack | `x-slack-signature`, `x-slack-request-timestamp` | `v0:{timestamp}:{rawBody}` | hex (`v0=` önekiyle) |
Yukarıdaki resolver örneği GitHub HMAC-SHA256 akışını zaten göstermektedir — tümleştirdiğiniz sağlayıcıya göre başlık adını, özet kodlamasını ve imzalanan payload dizesini uyarlayın.
<Note>
Resolver bir dispatch nesnesi döndürdüğünde, yol `202 { queued: true }` yanıtını verir ve hedef worker kuyruğunda çalıştırılır — çağıran taraf hedefin gecikmesini, sonucunu veya hatalarını asla gözlemlemez (bunlar yürütme günlüklerinde kaydedilir). Bu, göndericinin yeniden gönderimlerinin işlem yavaşlamalarını artırmasını engeller; bu da webhook alımı için istediğiniz şeydir.
Çağıran tarafın yanıt gövdesini aynı istek üzerinde okuması gerektiğinde (challenge el sıkışmaları, etkileşimli onaylar), bunun yerine **resolver**'dan bir `Response` döndürün. Platform bunu eşzamanlı olarak geri yansıtır ve kuyruğu atlar; başlıkları, HTTP route yanıtlarıyla aynı izin listesi üzerinden geçirilir. Resolver’ı hızlı tutun — bazı sağlayıcılar (örn. Slack) birkaç saniye içinde zaman aşımına uğrar. Resolver herkese açık bir uç nokta olarak erişilebilir olduğundan, onu edge'inizde hız sınırlama ile koruyun.
</Note>
#### Veritabanı olay tetikleyicisi yükü
Bir veritabanı olay tetikleyicisi mantık fonksiyonunuzu çağırdığında, değişen her kayıt için bir `DatabaseEventPayload` alır. Yük, kaynak çalışma alanı ve nesne hakkındaki üstveriyi, kayıt düzeyindeki olayla birleştirir.
```ts
import type {
DatabaseEventPayload,
ObjectRecordCreateEvent,
ObjectRecordDestroyEvent,
ObjectRecordUpdateEvent,
} from 'twenty-sdk/logic-function';
type Person = {
id: string;
emails?: { primaryEmail?: string };
};
```
Yük, şunları içerir:
| Özellik | Açıklama |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `name` | `person.updated` gibi bir olay adı. |
| `workspaceId` | Olayın gerçekleştiği çalışma alanı. |
| `objectMetadata` | Değişen nesne için üstveri. |
| `recordId` | Değişen kaydın kimliği. |
| `userId`, `userWorkspaceId`, `workspaceMemberId` | Olay bir çalışma alanı kullanıcısı tarafından tetiklendiğinde aktör alanları. |
| `properties` | İşleme bağlı olarak `before`, `after`, `diff` ve `updatedFields` içeren olay için kayıt verileri. |
| Etkinlik | Kayıt verileri |
| ------------------ | -------------------------------------------------------------------------------------------------------------- |
| `person.created` | `event.properties.after` |
| `person.updated` | `event.properties.before`, `event.properties.after`, `event.properties.diff`, `event.properties.updatedFields` |
| `person.destroyed` | `event.properties.before` |
Yumuşak silmeler için, kayıt `deletedAt` alanı değiştiğinden `.deleted`, güncelleme tarzı yapıyı izler.
Kalıcı silmeler için `.destroyed` kullanın.
<Note>
`databaseEventTriggerSettings.updatedFields`, hangi güncelleme olaylarının fonksiyonu tetikleyeceğini filtreler.
`event.properties.updatedFields`, mevcut olayda hangi alanların gerçekten değiştiğini size bildirir.
</Note>
Oluşturma olayı örneği:
```ts
type PersonCreatedEvent = DatabaseEventPayload<
ObjectRecordCreateEvent<Person>
>;
const handler = async (event: PersonCreatedEvent) => {
const person = event.properties.after;
return {
personId: event.recordId,
email: person.emails?.primaryEmail,
};
};
```
Güncelleme olayı örneği:
```ts
type PersonUpdatedEvent = DatabaseEventPayload<
ObjectRecordUpdateEvent<Person>
>;
const handler = async (event: PersonUpdatedEvent) => {
const { before, after, diff, updatedFields } = event.properties;
return {
personId: event.recordId,
updatedFields,
previousEmail: before.emails?.primaryEmail,
currentEmail: after.emails?.primaryEmail,
emailDiff: diff.emails,
};
};
```
Yalnızca e-posta güncellemelerinde tetikle:
```ts
export default defineLogicFunction({
...,
databaseEventTriggerSettings: {
eventName: 'person.updated',
updatedFields: ['emails'],
},
});
```
Yok etme olayı örneği:
```ts
type PersonDestroyedEvent = DatabaseEventPayload<
ObjectRecordDestroyEvent<Person>
>;
const handler = async (event: PersonDestroyedEvent) => {
const personBeforeDestroy = event.properties.before;
return {
personId: event.recordId,
email: personBeforeDestroy.emails?.primaryEmail,
};
};
```
#### Yürütme bağlamı
Her işleyici, onu neyin tetiklediğinden bağımsız olarak çalıştırmanın kendisini açıklayan ikinci bir bağımsız değişken alır. İlk bağımsız değişken tetikleyiciye göre biçim değiştirirken, bu değişmez:
```ts
import type { LogicFunctionExecutionContext } from 'twenty-sdk/logic-function';
const handler = async (
event: DatabaseEventPayload,
{ workspaceId, workspaceMemberId }: LogicFunctionExecutionContext,
) => {
if (workspaceMemberId === null) {
throw new Error('This function must be triggered by a person');
}
return { reviewedBy: workspaceMemberId, workspaceId };
};
```
| Alan | Tür | Açıklama |
| ------------------- | ---------------- | --------------------------------------------------------------------------------------------------- |
| `workspaceId` | `string` | Çalıştırmanın ait olduğu çalışma alanı. |
| `userWorkspaceId` | `string \| null` | Çalıştırmayı tetikleyen kişi, bu çalışma alanındaki üyeliği olarak. |
| `workspaceMemberId` | `string \| null` | Kayıtları bağladığınız çalışma alanı üyesi kaydı olarak aynı kişi. |
| `retryCount` | `number` | İlk denemede `0`. [Arka plan işleri](/l/tr/developers/extend/apps/logic/background-jobs) bölümüne bakın. |
| `maxRetries` | `number` | Uygulama tarafından istenen yeniden denemelerin üst sınırı. |
Kimse çalıştırmayı tetiklemediğinde `userWorkspaceId` ve `workspaceMemberId` `null` olur: cron zamanlamalarının, yükleme kancalarının ve kimliği doğrulanmamış webhook'ların arkasında kişi yoktur. Kişinin çalışma alanı üyesi kaydı olmadığında veya bu kayıt silindiğinde de `null` olurlar.
<Note>
**Bağlam, çalıştırmayı kimin tetiklediğini söyler.** Çalıştırmanın ne *yapabileceği* aşağıda ayrı bir bildirimdir.
</Note>
#### Bir çağrının hangi erişimi kullandığı
Her istemci — `CoreApiClient`, `MetadataApiClient` ve `RestApiClient` — çalıştırmayı tetikleyen kişi olarak davranır: kişinin rolü uygulamanızın rolüyle kesiştirilir; dolayısıyla çağrı hiçbir zaman ikinizden birinin yapabileceğinden fazlasını yapamaz. Bu varsayılandır ve bir kişinin kendi izinlerini aşmak için uygulamanızı asla kullanamayacağı anlamına gelir.
Çalıştırmayı kimse tetiklemediğinde adına davranılacak bir kişi yoktur; bu nedenle aynı istemci uygulamanızın kendi erişimine geri döner: cron zamanlamaları, yükleme kancaları ve kimliği doğrulanmamış webhook'lar özel işlem gerektirmez.
Bazı çağrılar, çalıştırmanın arkasında bir kişi *olsa bile* haklı olarak uygulamanın kendi erişimine ihtiyaç duyar — uygulamanızın yapılandırma kayıtlarını okumak veya o kişinin kendisinin yapamayacağı işleri gerçekleştirmek gibi. Bunlar için ikinci bir istemci oluşturun:
```ts
import { CoreApiClient } from 'twenty-client-sdk/core';
const asPerson = new CoreApiClient();
const asApplication = new CoreApiClient({ runAs: 'application' });
const handler = async (event: DatabaseEventPayload) => {
// config records only your app may read
const { slackUserLinks } = await asApplication.query({ ... });
// created as the person, so it is attributed and permission-checked
await asPerson.mutation({ createNote: { ... } });
};
```
Her ikisini de modül kapsamında bir kez oluşturun; ardından her çağrı noktası, çağırdığı istemciyle hangi erişimi kullandığını belirtir. `RestApiClient` aynı seçeneği alır:
```ts
import { RestApiClient } from 'twenty-client-sdk/rest';
const asApplication = new RestApiClient({ runAs: 'application' });
```
<Note>
**Kimsenin tetiklemediği bir çalıştırma uygulamanız olarak davranır.** Cron zamanlamalarının, yükleme kancalarının ve kimliği doğrulanmamış webhook'ların arkasında kişi yoktur; bu nedenle varsayılan istemci uygulamanızın kendi erişimine geri döner ve çalışmaya devam eder. `runAs: 'application'`, yalnızca bir kişinin *tetiklediği* çalıştırmada bu erişimi istediğinizde gereklidir.
Örneğin bir kaydı atfetmek için, bir işlev arkasında birinin olup olmamasına bağlı olarak farklı davranıyorsa `context.workspaceMemberId` değerini kontrol edin.
</Note>
Uygulamanızın kendi kaynaklarına erişen SDK yardımcıları her zaman kendi erişimini kullanır ve `runAs` değerini yok sayar: [anahtar-değer deposu](/l/tr/developers/extend/apps/logic/key-value-store), [bağlantılar](/l/tr/developers/extend/apps/logic/connections), `runAgent`, `getPublicAssetUrl` ve kredi ücretlendirmesi.
#### Bir işlevi bir yapay zekâ aracı veya iş akışı eylemi olarak kullanıma sunma
Mantık işlevleri, her birinin kendi tetikleyicisi olacak şekilde iki yerde kullanılabilir hâle getirilebilir:
* **`toolTriggerSettings`** — işlevi Twenty'nin yapay zekâ özellikleri (sohbet, MCP, işlev çağırma) tarafından bulunabilir hâle getirir. Standart JSON Şeması'nı kullanır; LLM'lerin doğal olarak anladığı biçimdir.
* **`workflowActionTriggerSettings`** — işlevin görsel iş akışı oluşturucusunda bir adım olarak görünmesini sağlar. Oluşturucunun uygun alan düzenleyicilerini, değişken seçicilerini ve etiketleri oluşturabilmesi için Twenty'nin zengin `InputSchema`'sını kullanır.
Bir işlev bunlardan birini, diğerini veya her ikisini de tercih edebilir. Bunlar, `cronTriggerSettings`, `databaseEventTriggerSettings` ve `httpRouteTriggerSettings` ile birlikte yer alır — aynı desen, aynı biçim.
<Note>
**İş akışındaki Code eylemiyle ilişkisi.** İş akışı oluşturucusundaki yerleşik **Code** eylemi başlı başına bir mantık fonksiyonudur — Twenty, her Code adımı için bir tane oluşturur ve düzenleyicisini satır içi olarak sunar. `workflowActionTriggerSettings`, bu tek seferlik, satır içi kodu **yeniden kullanılabilir** bir eyleme dönüştürmenin yoludur: fonksiyonu uygulamanızda bir kez tanımlarsınız ve her Code adımına kopyalanıp yapıştırılmak yerine, herhangi bir iş akışı içinde seçilebilir hale gelir. Son kullanıcı görünümü için kullanıcı rehberindeki [Code eylemine](/l/tr/user-guide/workflows/capabilities/workflow-actions#code) bakın.
</Note>
```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,
toolTriggerSettings: {},
});
```
Önemli noktalar:
* Bir işlev yüzeyleri karıştırabilir — onu sohbette VE iş akışı oluşturucusunda kullanıma sunmak için hem `toolTriggerSettings` hem de `workflowActionTriggerSettings` bildirin.
* `toolTriggerSettings.inputSchema` ve `workflowActionTriggerSettings.inputSchema` ikisi de isteğe bağlıdır. Atlandığında, manifest oluşturucu bunları işleyici kaynak kodundan çıkarır (yapay zekâ aracı için JSON Şeması, iş akışı eylemi için Twenty'nin `InputSchema`'sı). Daha zengin tipleme istediğinizde birini açıkça belirtin — örneğin, iş akışı oluşturucu için `FieldMetadataType`'ı bilen `CURRENCY` veya `RELATION` gibi alanlarla ya da yapay zekâ aracısının okuyabileceği `description` alanlarıyla:
```ts
export default defineLogicFunction({
...,
toolTriggerSettings: {
inputSchema: {
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'],
},
},
});
```
Parametrelerinizi **bir kez** tanımlayıp her iki yüzeyde de kullanmak için tek bir JSON Şeması (`InputJsonSchema`) oluşturun ve bunu iş akışı eylemi için `twenty-sdk/logic-function` içindeki `jsonSchemaToInputSchema` ile dönüştürün. `toolTriggerSettings.inputSchema`, JSON Şemasını doğrudan alırken `workflowActionTriggerSettings.inputSchema`, Twenty'nin `InputSchema` tipini bekler:
```ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema, type InputJsonSchema } from 'twenty-sdk/logic-function';
const inputSchema: InputJsonSchema = {
type: 'object',
properties: {
companyName: { type: 'string', label: 'Company name' },
domain: { type: 'string', label: 'Domain' },
},
required: ['companyName'],
};
export default defineLogicFunction({
...,
toolTriggerSettings: { inputSchema },
workflowActionTriggerSettings: {
label: 'Enrich Company',
icon: 'IconBuilding',
inputSchema: jsonSchemaToInputSchema(inputSchema),
},
});
```
##### Tam bir iş akışı eylemi örneği
`workflowActionTriggerSettings` dört alan kabul eder:
| Alan | Amaç |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `label` | İş akışı oluşturucusunun adım seçicisinde eylem için gösterilen ad. Varsayılan olarak fonksiyon `name` değerini kullanır. |
| `icon` | Eylemin yanında gösterilen simge (`tabler-icons` adı, örn. `IconBuilding`). |
| `inputSchema` | Twenty'nin gelişmiş `InputSchema`'sı — oluşturucunun yapılandırılabilir alanlar (değişken seçicilerle birlikte) olarak render ettiği şey. İsteğe bağlıdır; belirtilmediğinde handler'dan çıkarılır. |
| `outputSchema` | Handler'ın döndürdüğü yapıyı tanımlar, böylece **sonraki adımlar çıktı alanlarına eşleme yapabilir**. İsteğe bağlıdır; olmadan, çıktı tek bir opak değer olarak sunulur. |
Tümünü bir araya getirme — bir iş akışı eylemi olarak sunulan bir fonksiyon; daha sonraki adımların `taskId` değerine referans verebilmesi için tanımlanmış bir çıktıyla birlikte:
```ts src/logic-functions/enrich-company.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema, type InputJsonSchema } from 'twenty-sdk/logic-function';
import { CoreApiClient } from 'twenty-client-sdk/core';
const inputSchema: InputJsonSchema = {
type: 'object',
properties: {
companyName: { type: 'string', label: 'Company name' },
domain: { type: 'string', label: 'Domain' },
},
required: ['companyName'],
};
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,
},
});
// The keys returned here should match the `outputSchema` properties below.
return { taskId: result.createTask.id, enriched: true };
};
export default defineLogicFunction({
universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
name: 'enrich-company',
description: 'Enrich a company record with external data',
timeoutSeconds: 10,
handler,
workflowActionTriggerSettings: {
label: 'Enrich Company',
icon: 'IconBuilding',
inputSchema: jsonSchemaToInputSchema(inputSchema),
outputSchema: [
{
type: 'object',
properties: {
taskId: { type: 'string' },
enriched: { type: 'boolean' },
},
},
],
},
});
```
Uygulama kurulduktan sonra, **Enrich Company** iş akışı oluşturucusunun eylem seçicisinde görünür. Oluşturucu `companyName` ve `domain` alanlarını girdi alanları olarak render eder (her biri önceki adımlardan değer çekebilir) ve sonraki adımlar, adımın `taskId` ve `enriched` çıktılarına referans verebilir.
<Note>
**İ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.
</Note>
</Accordion>
</AccordionGroup>
<Note>
**Çalışma zamanı yardımcıları.** `twenty-sdk/utils`, işleyicilerin doğrudan `twenty-shared` içinden içe aktarma yapmasına gerek kalmaması için küçük çalışma zamanı yardımcılarını yeniden dışa aktarır. Örneğin, `isDefined(value)` hem `null` hem de `undefined` için `false` döndürür — bunu, çalışma zamanında `T | undefined` olarak yazılmış olsa bile `null` olarak gelebilen isteğe bağlı işleyici girdilerini güvenli şekilde daraltmak için kullanın:
```ts
import { isDefined } from 'twenty-sdk/utils';
const handler = async (params: { parentMessageId?: string }) => {
if (isDefined(params.parentMessageId)) {
// params.parentMessageId is narrowed to string here
}
};
```
</Note>
<Note>
**Yükleme kancaları** — ön yükleme, yükleme sonrası ve kaldırma işleyicileri — bu çalışma zamanını paylaşır ancak kendi tanımlama işlevleriyle bildirilir ve tetikleyici ayarlarını almaz. `definePreInstallLogicFunction`, `definePostInstallLogicFunction` ve `defineUninstallLogicFunction` için [Yükleme Kancaları](/l/tr/developers/extend/apps/config/install-hooks) bölümüne bakın.
</Note>
## Bir zaman çizelgesi etkinliği oluşturun.
Bir mantık işlevinden açık bir alan olayı yayımlamak için `createTimelineActivity()` kullanın. Önce olayı bir [zaman çizelgesi etkinliği türü](/l/tr/developers/extend/apps/data/timeline-activity-types) olarak tanımlayın, ardından türü ve nesneleri kararlı evrensel tanımlayıcılarıyla belirtin:
```ts
import { createTimelineActivity } from 'twenty-sdk/logic-function';
await createTimelineActivity({
timelineActivityTypeUniversalIdentifier:
'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
targetObjectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
targetRecordId: postCardId,
happensAt: new Date().toISOString(),
properties: {
deliveryProvider: 'postal-service',
},
});
```
Twenty, evrensel tanımlayıcıları kuruluma özgü meta veri kimliklerine çözümler, zaman çizelgesi etkinliği türünün çağıran uygulamaya ait olduğunu doğrular ve sunum meta verilerini yeni etkinlikte anlık görüntü olarak kaydeder.
Gerekli girdiler `timelineActivityTypeUniversalIdentifier`, `targetObjectUniversalIdentifier` ve `targetRecordId` öğeleridir. Ayrıca `happensAt`, `properties` ve `workspaceMemberId` sağlayabilirsiniz. `happensAt`, olayın zaman çizelgesindeki konumunu ve görüntülenen zamanını denetler; varsayılan olarak oluşturulma zamanıdır.
Başka bir kaydı olayla ilişkilendirmek için `linkedRecordId` ve `linkedObjectMetadataUniversalIdentifier` öğelerini birlikte sağlayın. Geçmişe yönelik bir görüntüleme yedeği olarak ayrıca `linkedRecordCachedName` sağlayabilirsiniz:
```ts
await createTimelineActivity({
timelineActivityTypeUniversalIdentifier:
'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
targetObjectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
targetRecordId: postCardId,
linkedObjectMetadataUniversalIdentifier: RECIPIENT_UNIVERSAL_IDENTIFIER,
linkedRecordId: recipientId,
linkedRecordCachedName: recipientName,
});
```
Mantık işlevinin rolü, standart `timelineActivity` nesnesi için yazma iznine ihtiyaç duyar. Açık olay türlerini bir `action` öğesine bağlı tutmayın; bir eyleme bağlı tür zaten otomatik denetim olayları alır ve aksi takdirde yinelenen satırlar üretir.
## 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 |
<AccordionGroup>
<Accordion title="CoreApiClient" description="Çalışma alanı verilerini sorgulayın ve değiştirin (kayıtlar, nesneler)">
`CoreApiClient`, çalışma alanı verilerini sorgulamak ve değiştirmek için ana istemcidir. `yarn twenty dev` veya `yarn twenty dev: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.
<Note>
**CoreApiClient geliştirme/derleme zamanında oluşturulur.** Bunu önce `yarn twenty dev` veya `yarn twenty dev: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.
</Note>
#### 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<CoreSchema.Company, 'id' | 'name'> | undefined
>(undefined);
const client = new CoreApiClient();
const result = await client.query({
company: {
__args: { filter: { position: { eq: 1 } } },
id: true,
name: true,
},
});
setCompany(result.company);
```
</Accordion>
<Accordion title="MetadataApiClient" description="Çalışma alanı yapılandırması, uygulamalar ve dosya yüklemeleri">
`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. `CoreApiClient` ile aynı `runAs` seçeneğini alır — [Bir çağrının hangi erişimi kullandığı](#whose-access-a-call-uses) bölümüne bakın.
```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.
</Accordion>
</AccordionGroup>
<Note>
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` — Varsayılan erişim için kısa ömürlü anahtar: çalıştırmanın arkasında biri olduğunda kişinin rolü uygulamanızın rolüyle kesiştirilir, kimse olmadığında uygulamanızın kendi rolü kullanılır. Kişi, bir mantık işlevinde çalıştırmayı tetikleyen kişi; bir ön bileşende ise sayfaya bakan kişidir.
* `TWENTY_APP_APPLICATION_ACCESS_TOKEN` — Yalnızca uygulamanızın kendi rolü kapsamında olan kısa ömürlü anahtar. Yalnızca mantık işlevlerinde, her zaman oraya eklenir ve `runAs: 'application'` tarafından kullanılır.
Bunları istemcilere geçirmeniz **gerekmez** — bunları `process.env` üzerinden otomatik olarak okurlar ve [Bir çağrının hangi erişimi kullandığı](#whose-access-a-call-uses) bölümü aralarından seçim yapmayı açıklar. Uygulamanızın kendi izinleri, `defineApplicationRole()` ile bildirilen role (veya `application-config.ts` içindeki `defaultRoleUniversalIdentifier` üzerinden referans verilen role) göre belirlenir; kişi olarak davranan bir çalıştırma ne bu rolü ne de kişinin rolünü aşabilir.
</Note>

View File

@@ -0,0 +1,61 @@
---
title: Genel Bakış
description: Twenty içinde çalışan sunucu tarafı TypeScript — HTTP rotaları, cron zamanlamaları, veritabanı olayları, yapay zeka araçları veya iş akışı aksiyonları tarafından tetiklenir.
icon: bolt
---
Bir Twenty uygulamasının **mantık katmanı**, *çalışan* koddur — HTTP isteklerine, cron zamanlamalarına ve kayıt değişikliklerine tepki veren sunucu tarafı TypeScript işleyicileri; çalışma alanının içinde yaşayan yapay zeka yetenekleri ve ajanları; ve işlevlerinizin üçüncü taraf servislerde bir kullanıcının adına hareket etmesini sağlayan OAuth bağlantıları.
```text
┌─ HTTP route ──┐
│ Cron schedule │
│ Database event │ ┌────────────────────┐
triggers ─┤ AI tool call ├─────▶│ Logic function │
│ Workflow action │ │ (your handler) │
│ Manual exec │ └────────────────────┘
└────────────────────┘ │
▼
┌────────────────────────────┐
│ Twenty API (records) │
│ Third-party API │
│ (via Connection token) │
└────────────────────────────┘
```
## Bu bölümde
<CardGroup cols={2}>
<Card title="Mantıksal işlevler" icon="bolt" href="/l/tr/developers/extend/apps/logic/logic-functions">
Temel yapı taşı — tetikleyici türleri, payload'lar ve tür güvenli API istemcisi.
</Card>
<Card title="Beceriler ve Ajanlar" icon="robot" href="/l/tr/developers/extend/apps/logic/skills-and-agents">
Yeniden kullanılabilir yapay zeka ajan yönergeleri ve özel sistem istemlerine sahip asistanlar.
</Card>
<Card title="Bağlantılar" icon="plug" href="/l/tr/developers/extend/apps/logic/connections">
Uygulamanızın üçüncü taraf servisler — Linear, GitHub, Slack ve daha fazlası — için tuttuğu OAuth kimlik bilgileri.
</Card>
<Card title="Anahtar-Değer Deposu" icon="database" href="/l/tr/developers/extend/apps/logic/key-value-store">
Mantık fonksiyonu çalıştırmaları arasında durumu kalıcı hale getirin — önbellekler, imleçler ve çalışma alanları arası beyanlar.
</Card>
<Card title="Arka Plan İşleri" icon="layer-group" href="/l/tr/developers/extend/apps/logic/background-jobs">
Çalıştırma başına zaman aşımı sınırını aşmak için işçi süreçlerde çalıştırılacak bir mantık işlevini kuyruğa alın.
</Card>
</CardGroup>
## Tetikleyici türlerine genel bakış
Bir mantık fonksiyonu bir veya daha fazla tetikleyici seçer — aşağıdaki her giriş, `defineLogicFunction()` üzerinde ayrı bir alandır:
| Tetikleyici | Ne zaman çalışır | Ayar |
| --------------------- | ---------------------------------------------------------------------------- | ------------------------------- |
| **HTTP rotası** | Bir istek `/s/\<path>` endpoint'inize ulaşır | `httpRouteTriggerSettings` |
| **Cron** | Bir CRON ifadesi eşleştiğinde | `cronTriggerSettings` |
| **Veritabanı olayı** | Bir çalışma alanı kaydı oluşturulduğunda, güncellendiğinde veya silindiğinde | `databaseEventTriggerSettings` |
| **Yapay zeka aracı** | Bir Twenty yapay zeka özelliği, fonksiyonunuzu çağırmaya karar verdiğinde | `toolTriggerSettings` |
| **İş Akışı Aksiyonu** | Bir iş akışı adımı fonksiyonunuzu çağırdığında | `workflowActionTriggerSettings` |
Fonksiyonlar, izole Node.js süreçlerinde sandbox içinde çalışır ve çalışma alanına, [`defineApplication()`](/l/tr/developers/extend/apps/config/application) üzerinde beyan edilen role göre kapsamlandırılmış tür güvenli bir API istemcisi aracılığıyla erişir.
<Note>
**Yükleme zamanı kancaları** — yüklemeden önce veya sonra çalışan kod — bu çalışma zamanını paylaşır ancak kendi define fonksiyonlarını kullanır ve [Config → Install Hooks](/l/tr/developers/extend/apps/config/install-hooks) altında bulunur.
</Note>

View File

@@ -0,0 +1,185 @@
---
title: Beceriler ve Ajanlar
description: Uygulamanız için yapay zekâ yetenekleri ve ajanları tanımlayın.
icon: robot
---
<Warning>
Beceriler ve ajanlar şu anda alfa aşamasında. Özellik işlevsel ancak hâlâ gelişmekte.
</Warning>
Uygulamalar, çalışma alanı içinde yer alan yapay zekâ yeteneklerini — yeniden kullanılabilir yetenek yönergeleri ve özel sistem istemlerine sahip ajanları — tanımlayabilir.
<AccordionGroup>
<Accordion title="defineSkill" description="Yapay zekâ ajanı yeteneklerini tanımlayın">
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.
</Accordion>
<Accordion title="defineAgent" description="Özel istemlerle yapay zekâ ajanları tanımlayın">
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.
* `responseFormat` (isteğe bağlı), ajan çıktısının şeklini kontrol eder. Serbest biçimli metin için varsayılan olarak `{ type: 'text' }` kullanılır. Yapılandırılmış JSON çıktısını zorunlu kılmak için `{ type: 'json', schema }` kullanın.
Varsayılan olarak bir ajan serbest biçimli metin döndürür. Yapılandırılmış çıktı almak için, `responseFormat` değerini `{ type: 'json' }` yapın ve bir `schema` sağlayın:
```ts src/agents/structured-agent.ts
import { defineAgent } from 'twenty-sdk/define';
export default defineAgent({
universalIdentifier: 'c4d5e6f7-a8b9-0123-cdef-456789012345',
name: 'lead-scorer',
label: 'Lead Scorer',
prompt: 'Score the lead and explain your reasoning.',
responseFormat: {
type: 'json',
schema: {
type: 'object',
properties: {
score: { type: 'number', description: 'Lead score from 0 to 100' },
summary: { type: 'string', description: 'Short reasoning for the score' },
},
required: ['score', 'summary'],
additionalProperties: false,
},
},
});
```
Şema notları:
* Şema düz bir nesnedir: her özelliğin `type` değeri, ilkel bir tür (`string`, `number` veya `boolean`) olmalıdır. İç içe nesneler ve diziler desteklenmez.
* Her özellik üzerindeki `description` (isteğe bağlı), modele oraya ne koyması gerektiği konusunda yol gösterir.
* `required` (isteğe bağlı), modelin her zaman döndürmesi gereken özellikleri listeler.
* `additionalProperties: false` (isteğe bağlı), `properties` içinde tanımlanmamış herhangi bir özelliği yasaklar.
</Accordion>
<Accordion title="runAgent" description="Bir mantık işlevinden bir ajan çalıştırın">
`runAgent()`, bir mantık işlevinin uygulamanızın ajanlarından birini (kendi yetenekleri ve araçlarıyla) çalıştırmasına olanak tanır. Ajanı, `defineAgent()` işlevine ilettiğiniz `universalIdentifier` ile belirleyin: Ya bir `prompt` dizesi ya da bir `messages` konuşma geçmişi iletin — ikisini birden değil:
```ts src/logic-functions/run-enricher.ts
import { runAgent } from 'twenty-sdk/logic-function';
const { result, error, success } = await runAgent({
agentUniversalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
prompt: 'Enrich House Ad <recordId>: fill empty fields from its listing URL.',
});
```
Çok turlu botlar (Slack, Discord, Teams, …) için, tek bir `prompt` yerine iş parçacığı geçmişini `messages` olarak iletin:
```ts src/logic-functions/reply-in-thread.ts
import { runAgent } from 'twenty-sdk/logic-function';
const { result, error, success } = await runAgent({
agentUniversalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
messages: [
{ role: 'user', content: 'Who owns Acme?' },
{ role: 'assistant', content: 'Sarah owns the Acme account.' },
{ role: 'user', content: 'What was the last touchpoint?' },
],
});
```
Önemli noktalar:
* `prompt` (string) veya `messages`'tan (1 ila 100 adet `{ role: 'user' | 'assistant', content: string }` girişi) **yalnızca birini** sağlayın.
* Ajan **senkron** olarak çalışır ve kendi araçları aracılığıyla kayıtları kendisi okuyup/güncelleyebilir — işlem tamamlandığında `runAgent()` çözümlenir.
* Bir uygulama yalnızca kendi ajanlarını çalıştırabilir.
* Uygulamanın [varsayılan rolü](/l/tr/developers/extend/apps/config/roles), `AI` izin bayrağını vermelidir — `permissionFlagUniversalIdentifiers` içine `SystemPermissionFlag.AI` ekleyin (veya `canAccessAllTools: true` olarak ayarlayın).
Bu olmadan, `runAgent()` bir izin hatasıyla başarısız olur.
* Mantık işlevi için cömert bir `timeoutSeconds` değeri ayarlayın — ajan çalışmaları birkaç saniye sürebilir.
* Çalışma tamamlandığında `success` değeri `true` olur ve `result` boş olmayan bir değerdir; başarısızlık durumunda `success` değeri `false` olur, `result` değeri `null` olur ve `error` nedenini tutar (örneğin, çalışma ortasında çalışma alanının AI kredileri bittiğinde).
```ts src/roles/default-role.ts
import { defineApplicationRole, SystemPermissionFlag } from 'twenty-sdk/define';
export default defineApplicationRole({
universalIdentifier: 'b648f87b-1d26-4961-b974-0908fd991061',
label: 'Default function role',
// runAgent() requires the AI permission flag on the app's default role.
permissionFlagUniversalIdentifiers: [SystemPermissionFlag.AI],
});
```
<Warning>
**Döngülerden kaçının:** Bir `*.updated` veritabanı olayı tetikleyicisinden `runAgent()` çağırırsanız ve ajan aynı kaydı güncelliyorsa, tetikleyiciyi, ajan tarafından asla yazılmayan bir alana (örneğin kaynak URL) göre `updatedFields` ile kapsamlandırın veya `runAgent()` çağırmadan önce herhangi bir hedef alanın hâlâ boş olup olmadığına göre koruma ekleyin.
</Warning>
### Bir çalışma alanı üyesi adına çalıştırma
Çalıştırma bir kişi tarafından tetiklendiğinde — örneğin bir mesajı yanıtlayan bir sohbet
botu — ajanın uygulama yerine o üye olarak davranması için `runAsWorkspaceMemberId` iletin:
```ts src/logic-functions/answer-question.ts
const { result } = await runAgent({
agentUniversalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
prompt: 'How many open opportunities do we have?',
runAsWorkspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7',
});
```
Ardından çalıştırma **üyenin kendi rolüyle** hareket eder: yalnızca o üyenin yapabildiklerini yapabilir, daha fazlasını yapamaz; oluşturduğu kayıtlar ona atfedilir ve üyenin satır düzeyi izinleri uygulanır. Ajanın rolü buna dahil olmaz — bu rol, arkalarında kimse olmayan çalıştırmalar için uygulamanın varsayılanıdır. Otonom çalıştırmalar (zamanlanmış işler, veritabanı olayı tetikleyicileri) için alanı atlayın: bunlar ajanın kendi rolünü korur. Hareket eden uygulama, üyenin izinlerini daraltmadan, kaynak bilgisini korumak için çalıştırmanın bağlamına bağlı kalır.
Çalıştırmayı tetikleyen kişiyi bir çalışma alanı üyesiyle eşleştirmekten uygulamanız sorumludur. Birini belirtmek bir uygulama erişim belirteci gerektirir ve bir belirtecin neyi
belirtebileceği, bir kullanıcı içerip içermediğine bağlıdır:
* **Kullanıcı ile ilişkili olmayan** bir belirteç herhangi bir üyeyi belirtebilir. Bir mantık fonksiyonu bunlardan biriyle çalışır ve `client_credentials` aracılığıyla ya da bir API
anahtarından oluşturulan belirteçler de aynı şekilde çalışır.
* Bir ön bileşenin aldığı gibi, **bir kullanıcının adına düzenlenmiş** bir belirteç yalnızca o kullanıcının kendi üyesini
belirtebilir.
Bunların dışındaki her şey — sıradan bir kullanıcı oturumu, bir uygulama belirteci olmayan bir API anahtarı — hiçbir şekilde
bir üyeyi belirtemez.
<Warning>
`runAgent()` çalışma alanı üyesi çözülemediğinde — bilinmeyen veya kaldırılmış bir üye ya da rolü olmayan bir üye — hata fırlatır. Bu, çağıranın istediğinden daha fazla erişim sağlayacağından asla
ajanın kendi rolüne geri dönmez.
</Warning>
</Accordion>
</AccordionGroup>

View File

@@ -0,0 +1,131 @@
---
title: CLI
description: Fonksiyonları çalıştırmak, günlükleri akış olarak izlemek, uygulama kurulumlarını yönetmek ve uzaklar arasında geçiş yapmak için yarn twenty komutları.
icon: terminal
---
`yarn twenty` CLI, uygulama ile ilgili her şey için arayüzünüzdür. Tam komut listesi:
| Komut | Ne yapar | Şurada belgelenmiştir |
| ----------------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `dev` | Kaynak dosyaları izleyin ve değişiklikleri gerçek zamanlı olarak eşitleyin | [Hızlı Başlangıç](/l/tr/developers/extend/apps/getting-started/quick-start) |
| `plan` | Meta veri değişikliklerini uygulamadan önizleyin | [Senkronizasyon ve kurtarma](/l/tr/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) |
| `apply` | Planı gösterdikten sonra meta veri değişikliklerini uygulayın | [Senkronizasyon ve kurtarma](/l/tr/developers/extend/apps/operations/sync-and-recovery) |
| `dev:build` | Uygulamayı derleyin ve API istemcisini oluşturun (`.tgz` paketlemek için `--tarball`) | [Yayınlama](/l/tr/developers/extend/apps/operations/publishing) |
| `dev:typecheck` | TypeScript tür kontrolünü çalıştırın | [Testing](/l/tr/developers/extend/apps/operations/testing) |
| `dev:add` | Yeni bir varlık iskeleti oluşturun | [İskele oluşturma](/l/tr/developers/extend/apps/getting-started/scaffolding) |
| `dev:generate-client` | Türlendirilmiş API istemcisini yeniden oluşturun | bu sayfa |
| `dev:function:exec` / `dev:function:logs` | Fonksiyonları çalıştırın ve günlüklerini akış halinde alın | bu sayfa |
| `dev:translations-extract` | Çevrilebilir dizeleri `locales/` kataloglarına çıkarın | [Çeviriler](/l/tr/developers/extend/apps/operations/translations) |
| `dev:catalog-sync` | Pazar yeri katalog eşitlemesini tetikleyin | [Yayınlama](/l/tr/developers/extend/apps/operations/publishing#how-marketplace-discovery-works) |
| `app:publish` / `app:install` / `app:uninstall` | Sürüm yaşam döngüsü | [Yayınlama](/l/tr/developers/extend/apps/operations/publishing) ve bu sayfa |
| `docker:*` | Yerel Twenty sunucu konteynerini yönetin | [Yerel Sunucu](/l/tr/developers/extend/apps/getting-started/local-server) |
| `remote:*` | Sunucu bağlantılarını yönetin | bu sayfa |
Her komut, varsayılan yerine belirli bir uzak sunucuyu hedeflemek için `-r, --remote \<name>` parametresini kabul eder.
## Fonksiyonları çalıştırma (`yarn twenty dev:function: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 dev:function:exec -n create-new-post-card
# Execute by universalIdentifier
yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
# Pass a JSON payload
yarn twenty dev:function:exec -n create-new-post-card -p '{"name": "Hello"}'
# Execute the install and uninstall hooks
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
yarn twenty dev:function:exec --uninstall
```
## Fonksiyon günlüklerini görüntüleme (`yarn twenty dev:function: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 dev:function:logs
# Filter by function name
yarn twenty dev:function:logs -n create-new-post-card
# Filter by universalIdentifier
yarn twenty dev:function:logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
```
<Note>
Bu, Docker konteyner günlüklerini gösteren `yarn twenty docker:logs` komutundan farklıdır. `yarn twenty dev:function:logs`, uygulamanızın fonksiyon yürütme günlüklerini Twenty sunucusundan gösterir.
</Note>
## Tiplendirilmiş istemciyi oluşturma (`yarn twenty dev:generate-client`)
Bir uygulamayı oluşturup eşitlemeden, etkin uzak sunucunun şemasından tiplendirilmiş API istemcisini (`twenty-client-sdk`) yeniden oluşturun. Bunu, Twenty örneğinizle iletişim kuran — örneğin ayrı bir depoda bulunan bir arka uç hizmeti — herhangi bir projede tiplendirilmiş bir istemci edinmek için kullanın:
```bash filename="Terminal"
# In your project (no Twenty app definition required)
yarn add twenty-sdk twenty-client-sdk
# Connect to the Twenty instance to generate the client from
yarn twenty remote:add
# Generate the typed client into node_modules/twenty-client-sdk
yarn twenty dev:generate-client
```
Ardından istemciyi kodunuza içe aktarın:
```typescript
import { CoreApiClient } from 'twenty-client-sdk/core';
```
Oluşturulan türleri yenilemek için veri modeliniz her değiştiğinde komutu yeniden çalıştırın.
<Note>
İstemci `node_modules` içinde oluşturulur, bu nedenle kodunuzla birlikte commit edilmez. Her kurulumdan sonra (örneğin bir `postinstall` betiğinde veya CI içinde) `yarn twenty dev:generate-client` komutunu çalıştırın.
</Note>
## Bir uygulamayı kaldırma (`yarn twenty app:uninstall`)
Uygulamanızı etkin çalışma alanından kaldırın:
```bash filename="Terminal"
yarn twenty app:uninstall
# Skip the confirmation prompt
yarn twenty app: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 --url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# List all configured remotes
yarn twenty remote:list
# Set the active remote
yarn twenty remote:use <name>
# Check that the active remote's authentication is still valid
yarn twenty remote:status
# Remove a remote
yarn twenty remote:remove <name>
```
Kimlik bilgileriniz `~/.twenty/config.json` içinde saklanır.

View File

@@ -0,0 +1,32 @@
---
title: Genel Bakış
description: Uygulamanızı oluşturun, test edin ve yayınlayın — CLI komutları, entegrasyon testleri, CI ve bir sunucuya veya npm'e yayınlama.
icon: rocket
---
**Operasyon katmanı**, uygulamayı *kullanarak* yaptıklarınızdan ziyade, uygulamanız *üzerinde* yaptığınız her şeyi kapsar: CLI komutlarını çağırmak, gerçek bir Twenty sunucusunda entegrasyon testleri çalıştırmak, CI'yi yapılandırmak ve sürümleri yayımlamak — tek bir sunucuya dağıtılan bir tarball olarak ya da pazaryerinde listelenen bir npm paketi olarak.
```text
develop ─▶ test ─▶ build ─▶ deploy / publish
─────── ──── ───── ─────────────────
yarn yarn yarn yarn twenty app:publish --private (tarball → one server)
twenty test twenty
dev dev:build yarn twenty app:publish (npm → marketplace)
```
## Bu bölümde
<CardGroup cols={2}>
<Card title="CLI" icon="terminal" href="/l/tr/developers/extend/apps/operations/cli">
`yarn twenty` referansı — exec, logs, uninstall, remotes.
</Card>
<Card title="Senkronizasyon ve kurtarma" icon="compass" href="/l/tr/developers/extend/apps/operations/sync-and-recovery">
Hangi komut ne zaman, senkronizasyon diff'ini okuma ve kurtarma basamakları.
</Card>
<Card title="Test" icon="flask" href="/l/tr/developers/extend/apps/operations/testing">
Vitest kurulumu, entegrasyon testleri, tür denetimi, CI iş akışı.
</Card>
<Card title="Yayımlama" icon="upload" href="/l/tr/developers/extend/apps/operations/publishing">
Oluşturun, bir tarball dağıtın, npm'e yayınlayın, yükleyin.
</Card>
</CardGroup>

View File

@@ -0,0 +1,293 @@
---
title: Yayımlama
icon: upload
description: Twenty uygulamanızı pazaryerine sunun ya da dahili olarak dağıtın.
---
## Genel Bakış
Uygulamanız [yerelde derlenip test edildikten sonra](/l/tr/developers/extend/apps/getting-started/concepts), dağıtım için iki yolunuz vardır:
* **Bir tar arşivi dağıtın** — uygulamanızı dahili veya özel kullanım için doğrudan belirli bir Twenty sunucusuna yükleyin.
* **npm’ye yayımlama** — uygulamanızı Twenty pazaryerinde listeleyin; böylece herhangi bir çalışma alanı keşfedip yükleyebilir.
Her iki yol da aynı **build** adımından başlar.
## Uygulamanızı derleme
Uygulamanızı derlemek ve dağıtıma hazır bir `manifest.json` oluşturmak için build komutunu çalıştırın:
```bash filename="Terminal"
yarn twenty dev:build
```
Bu işlem TypeScript kaynaklarını derler, mantık işlevlerini ve ön uç bileşenlerini transpile eder ve her şeyi `.twenty/output/` konumuna yazar. El ile dağıtım veya yayımlama komutu için bir `.tgz` paketini de üretmek amacıyla `--tarball` ekleyin.
## Sunucuya dağıtım (tarball)
Genel kullanıma açık olmasını istemediğiniz uygulamalar — sahipli araçlar, yalnızca kurumsal entegrasyonlar veya deneysel derlemeler — için bir tarball’ı doğrudan bir Twenty sunucusuna dağıtabilirsiniz.
### Ön Gereksinimler
Dağıtmadan önce, hedef sunucuyu işaret eden yapılandırılmış bir remote’a ihtiyacınız vardır. Remote’lar sunucu URL’sini ve kimlik doğrulama bilgilerini yerel olarak `~/.twenty/config.json` içinde saklar.
Bir remote ekleyin:
```bash filename="Terminal"
yarn twenty remote:add --url https://your-twenty-server.com --as production
```
### Dağıtım
Uygulamanızı tek adımda derleyip sunucuya yükleyin:
```bash filename="Terminal"
yarn twenty app:publish --private
# To deploy to a specific remote:
# yarn twenty app:publish --private --remote production
```
### Dağıtılmış bir uygulamayı paylaşma
Tarball uygulamaları genel pazar yerinde listelenmez; bu nedenle aynı sunucudaki diğer çalışma alanları gezinerek onları keşfedemez. Dağıtılmış bir uygulamayı paylaşmak için:
1. **Ayarlar > Uygulamalar > Kayıtlar** bölümüne gidin ve uygulamanızı açın
2. **Dağıtım** sekmesinde, **Paylaşım bağlantısını kopyala**’ya tıklayın
3. Bu bağlantıyı diğer çalışma alanlarındaki kullanıcılarla paylaşın — onları doğrudan uygulamanın yükleme sayfasına götürür
Paylaşım bağlantısı, sunucunun temel URL’sini (herhangi bir çalışma alanı alt alan adı olmadan) kullanır; böylece sunucudaki herhangi bir çalışma alanı için çalışır.
### Sürüm yönetimi
Halihazırda dağıtılmış bir tarball uygulamasını güncellerken, sunucu `package.json` içindeki `version` değerinin, şu anda dağıtılmış sürümden ([semver](https://semver.org) sıralamasına göre) **kesinlikle daha yüksek** olmasını gerektirir. Aynı sürümü yeniden dağıtmak veya daha düşük bir sürümü göndermek, tarball depolanmadan önce reddedilir — CLI'de `VERSION_ALREADY_EXISTS` hatasını görürsünüz.
Bir güncelleme yayımlamak için:
1. `package.json` içindeki `version` alanını artırın (ör. `1.2.3` → `1.2.4`, `1.3.0` veya `2.0.0`)
2. `yarn twenty app:publish --private` (veya `yarn twenty app:publish --private --remote production`) komutunu çalıştırın
3. Uygulamanın yüklü olduğu ve (uygulamanın Ayarlar sekmesinde) otomatik yükseltmeyi etkinleştirmiş çalışma alanları, arka planda otomatik olarak yükseltilir; diğerleri ise ayarlarında mevcut yükseltmeyi görecektir
<Note>
Ön sürüm etiketleri beklendiği gibi çalışır: `1.0.0-rc.1` → `1.0.0-rc.2` sürümünü artırmak mümkündür ve `1.0.0` gibi nihai bir sürüm, `1.0.0-rc.5` sürümünden daha yüksek olarak doğru şekilde tanınır. `package.json` içindeki sürümün kendisi geçerli bir semver dizesi olmalıdır.
</Note>
{/* TODO: add screenshot of the Upgrade button */}
### Sunucu sürümü uyumluluğu
Uygulamanız belirli bir Twenty sunucu sürümünde sunulan bir özelliği kullanıyorsa (örneğin, v2.3.0'da eklenen OAuth sağlayıcıları), uygulamanızın gerektirdiği en düşük sunucu sürümünü `package.json` içindeki `engines.twenty` alanını kullanarak belirtmelisiniz:
```json filename="package.json"
{
"name": "twenty-my-app",
"version": "1.0.0",
"engines": {
"node": "^24.5.0",
"twenty": ">=2.3.0"
}
}
```
Değer, standart bir [semver aralığı](https://github.com/npm/node-semver#ranges)dır. Yaygın Kalıplar:
| Aralık | Anlam |
| ---------------------------------- | --------------------------------------------------------- |
| `>=2.3.0` | 2.3.0'dan itibaren herhangi bir sunucu |
| `>=2.3.0 \<3.0.0` | 2.3.0 veya sonrası, ancak bir sonraki ana sürümün altında |
| `^2.3.0` | `>=2.3.0 \<3.0.0` ile aynı |
**Dağıtım ve yükleme sırasında ne olur:**
* `engines.twenty` ayarlanmışsa ve hedef sunucunun sürümü aralığı karşılamıyorsa, dağıtım (tarball yüklemesi) veya yükleme, gerekli aralığı ve gerçek sunucu sürümünü belirten bir mesajla birlikte `SERVER_VERSION_INCOMPATIBLE` hatasıyla reddedilir.
* `engines.twenty` **ayarlı değilse**, uygulama herhangi bir sunucu sürümünde kabul edilir (mevcut uygulamalarla geriye dönük uyumludur).
* Sunucuda `APP_VERSION` yapılandırılmamışsa, denetim atlanır.
<Note>
Nihai denetim sunucudadır — hem tarball yüklemesinde hem de çalışma alanı (workspace) kurulumunda `engines.twenty`'yi doğrular. Bir tarball'ı bant dışı dağıtırsanız veya marketplace'ten kurarsanız, sunucu yine de uyumluluğu zorunlu kılar.
</Note>
## Otomatik CI/CD (hazır şablonlu iş akışları)
`create-twenty-app` ile oluşturulan uygulamalar, kutudan çıktığı gibi `.github/workflows/` altında üç GitHub Actions iş akışıyla gelir. CI herhangi bir kurulum gerektirmez, CD tek bir secret gerektirir ve npm’e yayımlamak tek seferlik bir npm Trusted Publisher kurulumu gerektirir.
### CI — `ci.yml`
Entegrasyon testlerini `main` dalına yapılan her itmede ve her çekme isteğinde otomatik olarak çalıştırır.
**Ne yapar:**
1. Uygulamanızın kaynak kodunu alır.
2. `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main` bileşik eylemini kullanarak yalıtılmış bir Twenty test örneği başlatır (CI'daki `yarn twenty docker:start --test` eşdeğeridir).
3. Corepack’i etkinleştirir, `.nvmrc` dosyanızdan Node.js'i kurar ve bağımlılıkları `yarn install --immutable` ile yükler.
4. Oluşturulan örnekten `TWENTY_API_URL` ve `TWENTY_API_KEY` değerlerini aktararak `yarn test`i çalıştırır; böylece testleriniz gerçek bir sunucuyla haberleşebilir.
**Yapılandırma seçenekleri:**
* `TWENTY_VERSION` (ortam, varsayılanı `latest`) — CI’da kullanılan Twenty sunucu sürümünü `ci.yml` içinde bunu düzenleyerek sabitleyin.
* Eşzamanlılık `github.ref` bazında gruplanır ve yeni itmelerde devam eden çalışmaları iptal eder.
Gizli anahtar gerekmez — test örneği geçicidir ve yalnızca iş süresi boyunca çalışır.
### CD — `cd.yml`
`main` dalına yapılan her itmede uygulamanızı yapılandırılmış bir Twenty sunucusuna dağıtır ve isteğe bağlı olarak `deploy` etiketi uygulandığında bir çekme isteğinden de dağıtım yapar.
**Ne yapar:**
1. Etiketli PR'ler için PR'in head commit'ini ya da itilen commit'i alır.
2. `twentyhq/twenty/.github/actions/deploy-twenty-app@main` çalıştırır — `yarn twenty app:publish --private` komutunun CI eşdeğeridir.
3. `twentyhq/twenty/.github/actions/install-twenty-app@main` eylemini çalıştırır; böylece yeni dağıtılan sürüm hedef çalışma alanına kurulur.
**Gerekli yapılandırma:**
| Ayar | Nerede | Amaç |
| ----------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| `TWENTY_DEPLOY_URL` | `cd.yml` içinde `env` (varsayılan: `http://localhost:3000`) | Dağıtımın yapılacağı Twenty sunucusu. İlk kullanımdan önce bunu gerçek sunucu URL'nizle değiştirin. |
| `TWENTY_DEPLOY_API_KEY` | GitHub deposu **Settings → Secrets and variables → Actions** | Hedef sunucuda dağıtım iznine sahip API anahtarı. |
<Note>
Varsayılan `TWENTY_DEPLOY_URL` olan `http://localhost:3000` bir yer tutucudur — GitHub barındırmalı bir çalıştırıcıdan hiçbir yere erişemez. CD'yi etkinleştirmeden önce bunu sunucunuzun genel URL'siyle güncelleyin (veya ağ erişimi olan öz barındırılan bir çalıştırıcı kullanın).
</Note>
**Bir PR'den bir önizleme dağıtımını tetikleme:**
Bir çekme isteğine `deploy` etiketini ekleyin. `cd.yml` içindeki `if:` koruması, ilgili PR için işi PR'in head commit'ini kullanarak çalıştırır; böylece birleştirmeden önce hedef sunucuda değişikliği doğrulayabilirsiniz.
### Yayımla — `publish.yml`
Bir sürüm etiketi (örn. `v1.0.0`) gönderdiğinizde veya Actions sekmesinden iş akışını manuel olarak çalıştırdığınızda, uygulamanızı provenance ile npm’e yayımlar.
**Ne yapar:**
1. Uygulamanızı alır, Node.js’i kurar ve npm’i günceller (güvenilir yayımlama için npm 11.5.1 veya üstü gereklidir).
2. `yarn twenty app:publish` komutunu çalıştırır; bu komut uygulamayı der ve `.twenty/output`’u npm’e yayımlar. CI ortamında otomatik olarak `--provenance` ve `--access public` ekler; bu nedenle iş akışında hiçbir bayrağa gerek yoktur.
**Tek seferlik kurulum:**
npmjs.com’da paketinizin sayfasını açın > **Settings → Trusted Publisher** bölümüne gidin ve bu depoyu `publish.yml` iş akışıyla kaydedin ([npm trusted publishing belgelerine](https://docs.npmjs.com/trusted-publishers) bakın). Provenance ile yayımlama, paketi hangi GitHub deposunun oluşturduğunu doğrular; ayrıca Twenty pazaryerinde uygulamanızın sahipliğini bu şekilde talep edersiniz.
<Note>
npm yalnızca **herkese açık** kaynak depolarından gelen provenansı kabul eder. Özel bir depodan yayınlarsanız, npm OIDC provenans paketini `E422 ... Desteklenmeyen GitHub Actions kaynak deposu görünürlüğü: "private"` hatası. Özel bir depodan yayınlamak için, yayın adımının `env` bölümünde `TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'` ayarını yaparak provenansı devre dışı bırakın (hazırlanmış `publish.yml` içinde yorum satırı olarak eklenmiş bir ipucu bulunur):
```yaml filename=".github/workflows/publish.yml"
- name: Publish to npm
env:
TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'
run: yarn twenty app:publish
```
</Note>
### Yeniden kullanılabilir eylemleri sabitleme
`ci.yml` ve `cd.yml` iş akışları, `@main` üzerindeki yeniden kullanılabilir eylemlere başvurur; bu nedenle `twentyhq/twenty` deposundaki eylem güncellemeleri otomatik olarak alınır. Deterministik derlemeler istiyorsanız, her `uses:` satırında `@main` ifadesini bir commit SHA'sı veya sürüm etiketiyle değiştirin.
## npm’ye yayımlama
npm’ye yayımlamak, uygulamanızın Twenty pazaryerinde keşfedilebilir olmasını sağlar. Herhangi bir Twenty çalışma alanı, pazaryeri uygulamalarına doğrudan arayüzden göz atabilir, yükleyebilir ve güncelleyebilir.
### Gereksinimler
* Bir [npm](https://www.npmjs.com) hesabı
* `package.json` içindeki `keywords` dizinizdeki `twenty-app` anahtar sözcüğü (elle ekleyin — varsayılan olarak `create-twenty-app` şablonunda yer almaz)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
### Pazaryeri meta verileri
`defineApplication()` yapılandırması, uygulamanızın pazar yerinde nasıl görüneceğini kontrol eden isteğe bağlı alanları destekler. `public/` klasöründeki görsellere başvurmak için `logo` ve `galleryImages` kullanın:
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
logo: 'public/logo.png',
galleryImages: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
Pazar yeri alanlarının tam listesi için Uygulama Oluşturma sayfasındaki [defineApplication akordeonu](/l/tr/developers/extend/apps/config/application#marketplace-metadata)'na bakın (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, vb.).
#### Önerilen galeri görseli boyutları
Marketplace, `galleryImages`'ı sabit `8:5` oranlı bir kapsayıcıda görüntüler (örneğin, `1600×1000 px`).
<Note>
Herhangi bir en-boy oranındaki galeri görselleri eksiksiz görüntülenir ve asla kırpılmaz, ancak `8:5`'ten belirgin ölçüde daha uzun veya daha dar olanlarda yanlarda boş bantlar görünür.
</Note>
#### Görsel boyut sınırı
`logo` ve her bir `galleryImages` dosyasının boyutu **10 MB**'ı aşmamalıdır. Pazar yeri yayınladığınız varlıkları yeniden barındırdığında daha büyük dosyalar atlanır, bu nedenle görüntülenmezler.
### Yayımla
```bash filename="Terminal"
yarn twenty app:publish
```
Belirli bir dist-tag altında yayımlamak için (ör. `beta` veya `next`):
```bash filename="Terminal"
yarn twenty app:publish --tag beta
```
### Pazar yerinde keşif nasıl çalışır
Twenty sunucusu pazar yeri kataloğunu npm kayıt defterinden **her saat** eşitler.
Beklemek yerine eşitlemeyi hemen tetikleyebilirsiniz:
```bash filename="Terminal"
yarn twenty dev:catalog-sync
# To target a specific remote:
# yarn twenty dev:catalog-sync --remote production
```
Pazaryerinde gösterilen meta veriler, `defineApplication()` yapılandırmanızdan gelir — yukarıdaki [Pazaryeri meta verileri](#marketplace-metadata) bölümüne bakın.
<Note>
Uygulamanız `defineApplication()` içinde bir `aboutDescription` tanımlamıyorsa, pazaryeri, hakkında sayfasının içeriği olarak paketinizin npm'deki `README.md` dosyasını otomatik olarak kullanır. Bu, hem npm hem de Twenty pazaryeri için tek bir README dosyası kullanabileceğiniz anlamına gelir. Pazaryerinde farklı bir açıklama istiyorsanız, `aboutDescription` değerini açıkça ayarlayın.
</Note>
### CI üzerinden yayımlama
Yukarıda açıklanan, şablon olarak sağlanan `publish.yml` iş akışı sürüm etiketlerinde provenance ile npm’e otomatik olarak yayımlar. `yarn twenty app:publish`, CI içinde çalıştığında sizin için `--provenance` ve `--access public` eklediği için, iş akışının herhangi bir npm bayrağına ihtiyacı yoktur — yalnızca tek seferlik güvenilir yayımlayıcı kurulumuna ihtiyaç duyar.
Diğer CI sistemleri (GitLab CI, CircleCI vb.) için önce `yarn install`, ardından `yarn twenty app:publish` komutunu çalıştırın. Ortam bir OIDC jetonu oluşturabildiğinde provenance üretilir, aksi durumda ise otomatik olarak atlanır.
<Note>
**npm provenance**, npm listenize bir güven rozeti ekler ve kullanıcıların paketin herkese açık bir CI ardışık düzenindeki belirli bir commit’ten oluşturulduğunu doğrulamasını sağlar. Ayrıca Twenty pazaryerinde uygulamanızın sahipliğini talep etmenizi de sağlar. Ayrıntılar için [npm provenance belgelerine](https://docs.npmjs.com/generating-provenance-statements) bakın.
</Note>
## Uygulamaları yükleme
Bir uygulama yayımlandığında (npm) veya dağıtıldığında (tarball), çalışma alanları onu kullanıcı arayüzü (UI) üzerinden yükleyebilir.
Twenty içinde **Ayarlar > Uygulamalar** sayfasına gidin; burada hem pazar yerindeki hem de tarball ile dağıtılmış uygulamalar görüntülenip yüklenebilir.
{/* TODO: add screenshot of the UI when the app is registered */}
Uygulamaları komut satırından da yükleyebilirsiniz:
```bash filename="Terminal"
yarn twenty app:install
```
<Note>
Sunucu, kurulum sırasında semver sürümlemesini zorunlu kılar ve dağıtımdaki kuralları yansıtır:
* Çalışma alanınızda zaten yüklü olanla aynı sürümün kurulumu, `APP_ALREADY_INSTALLED` hatasıyla reddedilir.
* Halihazırda yüklü olandan daha düşük bir sürümü kurmak, `CANNOT_DOWNGRADE_APPLICATION` hatasıyla reddedilir.
Daha yeni bir sürümü kurmak için önce onu dağıtın veya yayımlayın, ardından `yarn twenty app:install` komutunu yeniden çalıştırın.
</Note>

View File

@@ -0,0 +1,126 @@
---
title: Senkronizasyon ve kurtarma
description: Hangi komutu ne zaman kullanmanız gerektiği, senkronizasyon çıktısının nasıl okunacağı ve yerel üst veriler sapmaya başladığında — tam sıfırlamaya ulaşmadan önce — uygulanacak bir kurtarma merdiveni.
icon: compass
---
Yerel uygulama geliştirme **senkronizasyon** etrafında döner: CLI manifestinizi yeniden oluşturur ve sunucu, onu çalışma alanınızda hâlihazırda bulunan üst verilerle karşılaştırarak yalnızca aradaki farkı uygular. Bu sayfa, hangi komuta başvurmanız gerektiğini, bir senkronizasyonun neleri değiştirdiğini nasıl okuyacağınızı ve yerel durum tutarsız göründüğünde — sırayla — ne yapmanız gerektiğini açıklar.
## Hangi komut, ne zaman
<Note>
Günlük yerel yinelemelerde neredeyse her zaman `yarn twenty dev` kullanmak istersiniz. Dağıtım ve yayımlama, sürümleri göndermek içindir, yerel döngü için **değil**.
</Note>
| Şunu yapmak istiyorsunuz… | Komut | Notlar |
| ---------------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Canlı senkronizasyonla yerelde yineleyin | `yarn twenty dev` | Dosyalarınızı izler ve her değişiklikte senkronize eder. |
| Bir kez senkronize et ve çık (CI, betikler, kancalar) | `yarn twenty apply` | Tek bir derleme + senkronizasyon yapar ve ardından çıkar. Yıkıcı değişiklik onayını atlamak için `--force` ekleyin. |
| Değişiklikleri **uygulamadan** önizleyin | `yarn twenty plan` | Farkı hesaplar ve yazdırır; hiçbir şey yazmaz. |
| Uygulamayı çalışma alanından kaldırın | `yarn twenty app:uninstall` | İstemi atlamak için `--yes` ekleyin. |
| Bir tarball'ı sunucuya gönderin | `yarn twenty app:publish --private` | `package.json` içinde **kesin olarak daha yüksek** bir sürüm gerektirir — bkz. [Publishing](/l/tr/developers/extend/apps/operations/publishing). |
| Pazaryerine (npm) yayımlayın | `yarn twenty app:publish` | — |
| Dağıtılmış bir sürümü yükleyin / yükseltin | `yarn twenty app:install` | Şu anda dağıtılmış olan sürümü yükler. |
| Yerel sunucuyu silin ve temiz bir şekilde yeniden başlatın | `yarn twenty docker:reset` | Yerel verilerin **tamamını** siler — son çare. |
<Note>
`yarn twenty dev --once` ve `yarn twenty dev --once --dry-run`, `yarn twenty apply` ve `yarn twenty plan` için kullanımdan kaldırılmış takma adlar olsalar da hâlâ çalışırlar.
</Note>
### Yerel senkronizasyon için sürüm artırmaya gerek yoktur
Sıkı artan `version` kuralı (dağıtımda `VERSION_ALREADY_EXISTS`, yüklemede `APP_ALREADY_INSTALLED` / `CANNOT_DOWNGRADE_APPLICATION`) **`app:publish` / `app:install`** için — yani yayın yolu için — geçerlidir. `yarn twenty dev`, manifestinizi yerinde senkronize eder ve hiçbir zaman bir sürüm değişikliği gerektirmez, bu yüzden yineleme yapmak için `package.json` dosyasına dokunmanız gerekmez. Yerel bir değişikliği test etmek için kendinizi sürümü artırırken buluyorsanız, ihtiyacınız olan geliştirme döngüsü yerine yayın yolunu kullanıyorsunuz demektir.
## Senkronizasyon çıktısını okuma
Her senkronizasyon, uyguladığı (veya `plan` ile uygulayacağı) üst veri değişikliklerini, Terraform tarzında — varlık başına bir blok olacak şekilde, öznitelikleriyle birlikte ve ardından bir özet satırıyla yazdırır:
```text filename="Terminal"
# objectMetadata "rocket" will be created
+ icon = "IconRocket"
+ labelSingular = "Rocket"
+ ...
# fieldMetadata "launchedAt" will be updated
~ isNullable = false -> true
Plan: 2 to add, 1 to change, 1 to destroy.
✓ Synced My App (4 files)
```
Bu, ilk tanı aracınızdır: tam olarak hangi nesnelerin, alanların ve düzenlerin değiştiğini size bildirir; böylece bir senkronizasyonun beklediğiniz gibi davranıp davranmadığını, arayüzü kontrol etmeden önce doğrulayabilirsiniz.
Yıkıcı değişiklikler (`to destroy`), neyi kaldırdıklarıyla birlikte listelenir (ör. `objectMetadata "auditNote" — drops the table and all its rows`) ve etkileşimli onay gerektirir ya da betiklerde `--force` kullanılmasını gerektirir.
Bir senkronizasyon tek bir varlıkta başarısız olduğunda, hata mesajı sorunlu varlığı ve onun `universalIdentifier` değerini adlandırır, örneğin:
```text
Migration action 'create' for 'fieldMetadata' (universalIdentifier: 2020...4337) failed
```
Bu tanımlayıcıyı, çakışanın hangisi olduğunu tahmin etmek yerine, manifestinizdeki (ve gerekirse çalışma alanındaki) varlığı bulmak için kullanın.
## Değişiklikleri önizleme (plan)
`yarn twenty plan`, manifestinizi derler, sunucudan geçiş planını ister ve onu — **hiçbir şeyi uygulamadan** — yazdırır. Bu, ona taahhüt etmeden önce "Bu senkronizasyon neyi değiştirir?" sorusunu yanıtlamanın güvenli yoludur.
```bash filename="Terminal"
yarn twenty plan
```
```text filename="Terminal"
Building manifest...
Computing metadata plan (read-only, nothing will be applied)...
# fieldMetadata "crewCapacity" will be created
+ ...
Plan: 1 to add, 1 to change, 0 to destroy.
✓ Plan complete for My App — no changes were applied
```
Bir plan:
* **Hiçbir şey yazmaz** — üst veri geçişi, uygulama kaydı güncellemesi, varsayılan rol/sekme değişiklikleri ve API istemcisi oluşturma işlemleri yapılmaz.
* Gerçek bir senkronizasyonun uygulayacağı **aynı farkı** döndürür; böylece oluşturulan/güncellenen/silinen varlıkları en baştan inceleyebilirsiniz.
* Riskli bir değişiklikten önce, bir yapay zekâ tarafından oluşturulan değişikliği gözden geçirirken veya beklenmedik bir değişiklik gerçekleşmek üzereyse betiğin başarısız olması gereken durumlarda kullanışlıdır.
<Note>
Bir plan yalnızca **üst veri** değişikliklerini önizler ve uygulamanın en az bir kez senkronize edilmiş olmasını gerektirir (böylece çalışma alanı ondan haberdar olur). Hiç senkronize edilmemiş bir uygulamaya karşı çalıştırırsanız, sunucu uygulamanın yüklü olmadığını bildirir — önce bir kez `yarn twenty dev` çalıştırın.
</Note>
## Kurtarma merdiveni
Yerel üst veriler hatalı görünüyorsa, bu adımları sırayla uygulayın ve engeliniz kalkar kalkmaz durun. Her adım bir öncekinden daha yıkıcıdır.
1. **Yeniden senkronize edin.** `yarn twenty apply` komutunu tekrar çalıştırın. Senkronizasyonlar idempotenttir — temiz bir manifesti yeniden çalıştırmak güvenlidir ve çoğu zaman geçici bir aksaklığı giderir.
2. **Planı önizleyin.** Bir sonraki senkronizasyonun tam olarak neyi değiştirmeyi amaçladığını, uygulamadan görmek için `yarn twenty plan` komutunu çalıştırın.
3. **Adlandırılmış hatayı okuyun.** Bir senkronizasyon başarısız olursa, iletideki üst veri türünü ve `universalIdentifier` değerini not alın (yukarıya bakın) ve manifestinizdeki o varlığı bulun. Bir çakışma genellikle yinelenen veya tekrar kullanılan bir tanımlayıcıya işaret eder.
4. **Kaldırın ve yeniden yükleyin.** `yarn twenty app:uninstall` komutunu çalıştırın, ardından yeniden senkronize edin (`yarn twenty dev`). Bu, uygulamanın üst verilerini temiz bir başlangıçtan yeniden oluşturur ve çalışma alanınızın geri kalanını olduğu gibi bırakır.
5. **Tam sıfırlama (son çare).** `yarn twenty docker:reset` komutunu çalıştırın, ardından yeniden tohumlayın ve yeniden senkronize edin.
<Warning>
`yarn twenty docker:reset`, yerel örneğinizdeki verilerin **tamamını** siler — tüm çalışma alanlarını, kayıtları ve uygulamaları. Yalnızca önceki adımlar başarısız olduktan sonra kullanın.
</Warning>
<Note>
Bir üst veri hatasıyla mı karşılaştınız? Lütfen [issue açın](https://github.com/twentyhq/twenty/issues/new/choose) ve başarısız olan geçiş iletisini (üst veri türü ve `universalIdentifier` ile birlikte), senkronizasyondan gelen `Metadata changes` çıktısını ve çalıştırdığınız komutları ekleyin.
</Note>
## Tek bir çalışma alanında eşzamanlı senkronizasyonlardan kaçının
Senkronizasyon, üst veri geçişlerini uygular. Aynı çalışma alanına karşı aynı anda birden çok senkronizasyon, dağıtım veya yükleme işlemi çalıştırmak — örneğin, birden çok terminal veya paralel yineleme yapan yapay zekâ ajanları — bu geçişlerin iç içe geçmesine ve üst verilerin kısmen uygulanmış bir durumda kalmasına neden olabilir.
Sunucu, bunu önlemek için çalışma alanı başına senkronizasyonları sıralı hâle getirir, ancak yine de hassas üst veri işlemlerini aynı anda tetiklemek yerine **tek bir** süreç üzerinden geçirmeniz gerekir. Geliştirmeyi birden fazla ajanla orkestre ediyorsanız, onların senkronizasyon/dağıtım/yükleme çağrılarını tek bir kuyruğa yönlendirin ki aynı anda yalnızca biri çalışsın.
## Hataları birbirinden ayırma
Bir şeyler ters gittiğinde, üst veri farkı ve adlandırılmış hatalar, hatanın nerede oluştuğunu belirlemenizi sağlar:
* **Manifest derleme hatası** — CLI, senkronizasyondan önce başarısız olur (`MANIFEST_BUILD_FAILED`, `TYPECHECK_FAILED`); uygulama kaynağınızı düzeltin.
* **Senkronizasyon / geçiş hatası** — derleme başarılı olur ancak farkı uygulama işlemi, varlığı ve `universalIdentifier` değerini adlandırarak başarısız olur; çakışan üst veriyi düzeltin.
* **Bağımlılık boyutu hatası** — senkronizasyon veya kurulum, uygulamanın üretim `dependencies` bağımlılıklarının çalışma zamanı katmanı olarak kurulamayacak kadar büyük olması nedeniyle başarısız olur (`LOGIC_FUNCTION_DEPENDENCIES_SIZE_EXCEEDED`); mantık fonksiyonlarınızın çalışma zamanında içe aktarmadığı paketleri (UI kitaplıkları, geliştirme araçları) `devDependencies` içine taşıyın.
* **Uygulama kodu çalışma zamanı hatası** — senkronizasyon başarılı olur ancak mantık fonksiyonlarınız veya bileşenleriniz çalışma zamanında beklenmedik şekilde davranır; [function logs](/l/tr/developers/extend/apps/operations/cli) bölümünü kontrol edin.
* **Yerel örnek durumu** — yukarıdakilerin hiçbiri geçerli değil ve çalışma alanı hâlâ hatalı görünüyorsa; kurtarma merdiveninde aşağı doğru ilerleyin.

View File

@@ -0,0 +1,264 @@
---
title: Test
description: Vitest kurulumu, gerçek bir Twenty sunucusunda entegrasyon testleri, tip denetimi ve GitHub Actions ile CI.
icon: flask
---
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.
## npm paketlerini kullanma
Uygulamanızda herhangi bir npm paketini yükleyip kullanabilirsiniz. Hem mantık işlevleri hem de ön uç bileşenleri, bağımlılıklarını çı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<any> => {
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 <p>Today is {format(new Date(), 'MMMM do, yyyy')}</p>;
};
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. İçe aktarılan paketler, aşağıda listelenenler hariç paketin içine satır içi olarak 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 npm paketleri kullanılabilir. Korumalı alanın *kısmi* bir DOM uyguladığını unutmayın; bu nedenle bir paket hatasız derlenip çalışma zamanında yine de başarısız olabilir. Bkz. [Geçerli kısıtlamalar](/l/tr/developers/extend/apps/layout/front-components#current-limitations).
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.
Ön uç bileşenleri ayrıca bir [paylaşılan bağımlılıklar paketi](/l/tr/developers/extend/apps/layout/front-components#sharing-dependencies-across-front-components) aracılığıyla bağımlılıkları paylaşabilir. Uygulamanın package.json dosyasındaki `frontComponentSharedDependencies` alanında listelenen paketler, ayrı bir dosyaya bir kez derlenir ve her bir bileşene satır içi olarak eklenmek yerine çalışma zamanında buradan yüklenir; bu nedenle, yalnızca paylaşılan paketleri içe aktaran bir bileşen, neredeyse hiç kendi bağımlılık kodunu taşımaz.
## 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';
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? '<the pre-seeded local dev key>';
// Make env vars available to globalSetup (test.env only applies to workers)
process.env.TWENTY_API_URL = TWENTY_API_URL;
process.env.TWENTY_API_KEY = TWENTY_API_KEY;
export default defineConfig({
plugins: [
tsconfigPaths({
projects: ['tsconfig.spec.json'],
ignoreConfigErrors: true,
}),
],
test: {
testTimeout: 120_000,
hookTimeout: 120_000,
fileParallelism: false,
include: ['src/**/*.integration-test.ts'],
globalSetup: ['src/__tests__/global-setup.ts'],
env: {
TWENTY_API_URL,
TWENTY_API_KEY,
},
},
});
```
Sunucunun erişilebilir olduğunu doğrulayan, SDK için bir test yapılandırması (`~/.twenty/config.test.json`) yazan ve testler çalışmadan önce uygulamayı eşitleyen genel bir kurulum dosyası oluşturun:
```ts src/__tests__/global-setup.ts
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { appDevOnce, appUninstall } from 'twenty-sdk/cli';
const APP_PATH = process.cwd();
const CONFIG_DIR = path.join(os.homedir(), '.twenty');
export async function setup() {
const apiUrl = process.env.TWENTY_API_URL!;
const apiKey = process.env.TWENTY_API_KEY!;
// Verify the server is running
const response = await fetch(`${apiUrl}/healthz`);
if (!response.ok) {
throw new Error(`Twenty server is not reachable at ${apiUrl}.`);
}
// Write the SDK's test config (the CLI reads config.test.json when NODE_ENV=test)
fs.mkdirSync(CONFIG_DIR, { recursive: true });
fs.writeFileSync(
path.join(CONFIG_DIR, 'config.test.json'),
JSON.stringify({
remotes: { local: { apiUrl, apiKey } },
defaultRemote: 'local',
}, null, 2),
);
// Start from a clean slate, then sync the app
await appUninstall({ appPath: APP_PATH }).catch(() => {});
const result = await appDevOnce({ appPath: APP_PATH });
if (!result.success) {
throw new Error(`Dev sync failed: ${result.error?.message}`);
}
}
export async function teardown() {
await appUninstall({ appPath: APP_PATH });
}
```
## 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 |
| `appDevOnce` | Uygulamayı bir kez oluşturun ve eşitleyin (`yarn twenty apply` ile aynıdır) |
| `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 dev:typecheck
```
Bu, uygulamanızın `tsconfig.json` dosyasına göre `tsc --noEmit` komutunu çalıştırır ve tüm tip hatalarını raporlar. İskelet olarak oluşturulan uygulamalar ayrıca test dosyalarını da kapsayan (`tsconfig.spec.json`) bir `yarn typecheck` betiği ile birlikte gelir.
## GitHub Actions ile CI
İskelet oluşturucu, `.github/workflows/ci.yml` konumunda kullanıma hazır bir iş akışı üretir. `main` dalına yapılan her itmede ve her çekme isteğinde, çalıştırıcı içinde geçici bir Twenty sunucusu başlatır (`twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test` eylemi aracılığıyla) ve ardından `yarn lint`, `yarn typecheck`, `yarn test:unit` ve `yarn test` komutlarını, `TWENTY_API_URL` / `TWENTY_API_KEY` bu sunucuyu işaret edecek şekilde çalıştırır. Herhangi bir gizli bilgi gerekmez ve iş akışının en üstündeki `TWENTY_VERSION` ortam değişkeni aracılığıyla sunucu sürümünü sabitleyebilirsiniz.
Üç iskelet iş akışının (`ci.yml`, `cd.yml` dağıtım hattı ve npm yayınlama için `publish.yml`) eksiksiz adım adım anlatımı için [Yayınlama → Otomatik CI/CD](/l/tr/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows) bölümüne bakın.

View File

@@ -0,0 +1,128 @@
---
title: Çeviriler
description: Uygulamanızı birden çok dilde yayınlayın — manifest etiketlerini ve kullanıcı arayüzü (UI) dizelerini tek bir yerel ayar kataloğu üzerinden çevirin.
icon: language
---
Twenty uygulamaları **İngilizce** olarak oluşturulur: kaynak ve manifestinizdeki dizeler İngilizce kaynak metindir, `en` çevirdiğiniz kaynak yerel ayarıdır ve çevrilmeden bırakılan herhangi bir yerel ayar buna geri döner.
Uygulamanızda iki tür çevrilebilir metin vardır ve her ikisi de aynı `locales/` kataloğundan geçer:
* **Manifest etiketleri** — nesne ve alan adları, görünüm başlıkları, menü öğeleri ve uygulamanızın üst verilerinde (metadata) tanımlanan diğer dizeler.
* **Ön bileşen dizeleri** — React ön bileşenlerinizin render ettiği UI metni.
Çevrilebilir dizeleri işaretlersiniz, bunları yerel başına kataloglara çıkarırsınız,
bu katalogları çevirirsiniz ve build, geçerli kullanıcı için
herhangi bir ek bağlantıya gerek kalmadan doğru dili sunar.
## Ön bileşen dizelerini işaretleme
Çeviri yardımcılarını `twenty-sdk/front-component` içinden içe aktarın:
```tsx
import { Trans, t, msg, useTranslate } from 'twenty-sdk/front-component';
const STATUSES = [
{ id: 'draft', label: msg('Draft') },
{ id: 'sent', label: msg('Sent') },
];
const Card = ({ count, name }: { count: number; name: string }) => {
const { t } = useTranslate();
return (
<section>
{/* Static text — reactive to the user's locale */}
<Trans>Loading postcard…</Trans>
{/* Disambiguate identical sources with a context */}
<Trans context="card-title">Untitled</Trans>
{/* Interpolation: pass values explicitly */}
<p>{t('Hi {name}', { name })}</p>
<p>{t('Saved {count} cards', { count })}</p>
{/* Resolve a lazily-declared descriptor */}
<ul>{STATUSES.map((s) => <li key={s.id}>{t(s.label)}</li>)}</ul>
</section>
);
};
```
### Ne zaman hangisi kullanılmalı
* **`Trans`** — JSX içindeki statik metin. Değişkelerin yerleştirilmesi için `message` ve `values` prop'larını kullanın (`Trans message="Hi {name}" values={{ name }}`);
çocukların içine doğrudan yerleştirme statik olarak çıkarılamaz.
* **`useTranslate().t`** — bir bileşen içindeki dinamik dizeler. Kullanıcı dili değiştirdiğinde
yeniden render edilir. Render içinde bunu tercih edin.
* **`t(...)`** (doğrudan içe aktarılır) — bileşen render'ının içinde olmasına gerek olmadan,
event handler'lar, yardımcı fonksiyonlar ve modül kapsamı dahil **her yerde** kullanılabilen erken çeviri.
* **`msg(...)`** — veri olarak (sabitler, yapılandırma) bildirilen dizeler için ertelenmiş bir tanımlayıcı. Bunu daha sonra `t(descriptor)` ile çözün.
### Bağlam
Aynı kaynak dize farklı şekilde çevrildiğinde bunları ayırt etmek için `context` geçirin:
```tsx
t({ message: 'Open', context: 'door' });
t({ message: 'Open', context: 'window' });
<Trans context="card-title">Untitled</Trans>
```
Bağlamsal dizeler, yerel ayar kataloglarında bağlamlarına göre anahtarlanmış bir grup olarak görünür: `"\<context>": { "\<message>": "\<translation>" }`. Manifest etiketleri, tasarım gereği bağlamsaldır — her biri, oynadığı meta veri rolünü taşır (`objectMetadata.labelSingular`, `fieldMetadata.label`, `view.name`, …); dolayısıyla aynı kelime, bir nesne adı olarak ve bir görünüm adı olarak farklı şekilde çevrilebilir.
## Çıkarma ve çeviri
extract komutunu uygulama dizininizden çalıştırın:
```bash
twenty dev:translations-extract # collect strings into locales/en.json
twenty dev:translations-extract --locale fr-FR # also scaffold a target locale
```
Çıkarma işlemi, manifest etiketlerinizi ve ön bileşen kaynaklarınızdan gelen `t()`/`msg()`/`Trans`
dizelerini, kaynak dizeyi anahtar olarak kullanarak `locales/\<locale>.json` içine toplar. Çevirileri doldurun:
```json
// locales/fr-FR.json
{
"Loading postcard…": "Chargement de la carte…",
"Hi {name}": "Bonjour {name}",
"Saved {count} cards": "{count} cartes enregistrées",
"door": {
"Open": "Ouvrir"
},
"objectMetadata.labelSingular": {
"Postcard": "Carte postale"
}
}
```
Bağlamdan bağımsız dizeler düz girdilerdir; bağlamsal dizeler — her bir manifest etiketi de dahil olmak üzere — bağlam anahtarlarının altında iç içe yerleştirilir.
`{name}` gibi yer tutucular çalışma zamanında değiştirilir — bunları çeviride koruyun. Boş bırakılan herhangi bir dize, kaynak metne geri döner.
## Nasıl çalışır
`twenty dev:build`, katalogları derler ve geçerli kullanıcı için doğru dili sunar:
manifest etiketleri sunucu tarafında çözülür ve ön bileşen
katalogları her bileşen paketine gömülür. Çalışma zamanında bir bileşen, yereli yürütme bağlamından (ana makinenin geçerli dili) okur
ve her dizeyi kataloğuna göre çözer; bir çeviri eksik olduğunda
kaynak metne geri döner. Ana makinede dilin değiştirilmesi, `Trans` ve
`useTranslate().t` dizelerini canlı olarak yeniden render eder.
Manifest etiketleri, her senkronizasyonda — `twenty dev:build`, `twenty apply`
ve sürekli `twenty dev` izleme işlemi de dahil olmak üzere — derlenir; bu nedenle bir yerel ayar dosyasını düzenleyip
senkronize etmek, yeni metni görmek için yeterlidir. Ön uç bileşen katalogları, derleme sırasında
her bileşen paketine dahil edilir; dolayısıyla bunlardan birini değiştirmek yine de
`twenty dev:build`'in yeniden çalıştırılması (ve yeniden dağıtım) anlamına gelir.
`Trans` metin alt öğeleri birden fazla satıra yayılabilir — boşluklar, JSX'in boşlukları daraltma biçimiyle aynı şekilde daraltılır; bu nedenle her ikisi de `Welcome back` anahtarına dönüştürülür:
```tsx
<Trans>Welcome back</Trans>
<Trans>
Welcome
back
</Trans>
```

View File

@@ -0,0 +1,70 @@
---
title: 5. Bir yapay zekâ ajanı
icon: robot
description: Bir ajanın, sizin aracınızı kullanarak bir sohbetten belgeler oluşturmasına izin verin.
---
`generate-document` bir **araç** olarak sunulduğu için, bir yapay zekâ ajanı onu çağırabilir.
Kullanıcıların yalnızca *"Jeffery Griffin için bir teklif hazırla"* diyebilmesi için bir ajan ve bir beceri ekleyelim.
## Beceri
Bir [skill](/l/tr/developers/extend/apps/logic/skills-and-agents), ajanlara eklediğiniz bilgi — yeniden kullanılabilir talimatlardır. Bizimki modele aracı nasıl kullanacağını öğretir.
```ts filename="src/skills/document-drafting.skill.ts"
import { defineSkill } from 'twenty-sdk/define';
export default defineSkill({
universalIdentifier: DOCUMENT_SKILL_UNIVERSAL_IDENTIFIER,
name: 'document-drafting',
label: 'Document drafting',
icon: 'IconFileText',
content: [
'To generate a document, call the `generate-document` tool with:',
'- `templateId`: the id of the document template to use.',
'- `recordId`: the id of the Person or Company the document is for.',
'',
'If the user names a template or person instead of an id, find the record first,',
'then pass its id. Make sure the template target matches the record type.',
].join('\n'),
});
```
## Ajan
Bir [ajan](/l/tr/developers/extend/apps/logic/skills-and-agents), bir istemi bir modelle eşleştirir. Bir derleme uyarısından kaçınmak için `responseFormat` değerini açıkça ayarlayın.
```ts filename="src/agents/document-assistant.agent.ts"
import { defineAgent } from 'twenty-sdk/define';
export default defineAgent({
universalIdentifier: DOCUMENT_AGENT_UNIVERSAL_IDENTIFIER,
name: 'document-assistant',
label: 'Document Assistant',
description: 'Generates documents from your templates and CRM records.',
icon: 'IconFileText',
responseFormat: { type: 'text' },
prompt: [
'You are the Document Assistant for a CRM.',
'You help users generate personalized documents from reusable templates',
'and the data already in their CRM. Use the generate-document tool, and',
'always confirm what you created.',
].join(' '),
});
```
<Note>
Ajan yalnızca rolü izin veriyorsa aracı çağırabilir. Uygulamanın rolünde `canAccessAllTools: true` ve `canBeAssignedToAgents: true` değerlerini [Bölüm 2](/l/tr/developers/extend/apps/tutorials/document-generator/generating-documents#grant-it-access) içinde zaten ayarladık.
</Note>
## Deneyin
**Document Assistant** ile bir sohbet açın ve CRM’inizdeki bir kişi için bir belge taslağı hazırlamasını isteyin. Kaydı bulur, `generate-document` çağrısını yapar ve oluşturduğu belgeyi size bildirir — bu belge artık **Documents** görünümünüzde, komut menüsü ve iş akışı yollarındakiyle tam olarak aynı şekilde görünür.
Mantığı bir araç olarak açığa çıkarmanın getirisi budur: **tek fonksiyon, birçok ön kapı** — komut menüsü, HTTP, iş akışı adımı ve şimdi de doğal dil.
**Bu adımdan sonra:** uygulama özellik açısından tamamlanmış ve gerçekten kullanışlıdır. Artık onu yayımlama zamanı.
<Card title="Sonraki: yayımlama →" icon="rocket" href="/l/tr/developers/extend/apps/tutorials/document-generator/publishing">
Pazar yeri meta verilerini ekleyin ve yayımlayın.
</Card>

View File

@@ -0,0 +1,281 @@
---
title: 4. UI'yi oluşturma
icon: table-columns
description: Görünümler, kenar çubuğu gezinmesi, bir komut ve ön uç bileşenleri.
---
Şu anda nesnelere yalnızca Settings üzerinden erişilebiliyor. Uygulamaya UI'de gerçek bir varlık kazandıralım: liste görünümleri, kenar çubuğu girişleri, tek tıkla **Generate document** komutu, bir belgeyi **önizlemek** için kayıt sayfası ön uç bileşeni ve şablonlar için yerel zengin metin **editör** sekmesi.
## Görünümler ve gezinme
Bir [görünüm](/l/tr/developers/extend/apps/layout/views), belirli bir nesnenin kaydedilmiş bir listesidir.
Bir [gezinme menüsü öğesi](/l/tr/developers/extend/apps/layout/navigation-menu-items)
bu görünümü kenar çubuğuna yerleştirir. Sunucu, her nesnenin varsayılan "Tümü ..." görünümünü zaten otomatik olarak sağladığından, bir uygulamanın tanımladığı görünümler ek, özenle seçilmiş görünümlerdir.
```ts filename="src/views/documents.view.ts"
import { defineView } from 'twenty-sdk/define';
export default defineView({
universalIdentifier: DOCUMENTS_VIEW_UNIVERSAL_IDENTIFIER,
name: 'All documents',
objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconFile',
position: 0,
fields: [
{ universalIdentifier: DOCUMENTS_VIEW_NAME_FIELD_UNIVERSAL_IDENTIFIER,
fieldMetadataUniversalIdentifier: DOCUMENT_NAME_FIELD_UNIVERSAL_IDENTIFIER,
position: 0, isVisible: true, size: 280 },
{ universalIdentifier: DOCUMENTS_VIEW_STATUS_FIELD_UNIVERSAL_IDENTIFIER,
fieldMetadataUniversalIdentifier: DOCUMENT_STATUS_FIELD_UNIVERSAL_IDENTIFIER,
position: 1, isVisible: true, size: 120 },
{ universalIdentifier: DOCUMENTS_VIEW_TEMPLATE_FIELD_UNIVERSAL_IDENTIFIER,
fieldMetadataUniversalIdentifier: DOCUMENT_TEMPLATE_FIELD_UNIVERSAL_IDENTIFIER,
position: 2, isVisible: true, size: 200 },
],
});
```
```ts filename="src/navigation-menu-items/documents.navigation-menu-item.ts"
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
export default defineNavigationMenuItem({
universalIdentifier: DOCUMENTS_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
name: 'Documents',
icon: 'IconFile',
color: 'green',
position: 1,
type: NavigationMenuItemType.VIEW,
viewUniversalIdentifier: DOCUMENTS_VIEW_UNIVERSAL_IDENTIFIER,
});
```
Aynı çifti şablonlar için de ekleyin. İkisi de artık kenar çubuğunda görünüyor:
<Frame caption="Kenar çubuğunda Documents ve Templates, oluşturulmuş belge listelenmiş şekilde.">
<img src="/images/docs/developers/extends/apps/document-generator/04-documents-view.png" alt="Oluşturulmuş belgeli Documents görünümü" />
</Frame>
## Bir ön uç bileşeni
Bir [ön uç bileşeni](/l/tr/developers/extend/apps/layout/front-components), Twenty içinde izole edilmiş bir React bileşenidir. Bizimki seçili kaydı okur, kişi şablonlarını `CoreApiClient` aracılığıyla yükler ve bir önceki bölümdeki rotaya POST isteği gönderir.
```tsx filename="src/front-components/generate-document-form.front-component.tsx"
import { useEffect, useState } from 'react';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { RestApiClient } from 'twenty-client-sdk/rest';
import { defineFrontComponent } from 'twenty-sdk/define';
import { enqueueSnackbar, useSelectedRecordIds } from 'twenty-sdk/front-component';
const GenerateDocumentForm = () => {
const selectedRecordIds = useSelectedRecordIds();
const recordId = selectedRecordIds.length === 1 ? selectedRecordIds[0] : null;
const [templates, setTemplates] = useState<{ id: string; name: string }[]>([]);
const [templateId, setTemplateId] = useState('');
useEffect(() => {
new CoreApiClient()
.query({ documentTemplates: {
__args: { filter: { target: { eq: 'PERSON' } }, first: 100 },
edges: { node: { id: true, name: true } } } })
.then(({ documentTemplates }) => {
const list = documentTemplates?.edges?.map((e) => e.node) ?? [];
setTemplates(list);
if (list[0]) setTemplateId(list[0].id);
});
}, []);
const generate = async () => {
const res = await new RestApiClient().post<{ success: boolean }>(
'/s/documents/generate',
{ templateId, recordId },
);
await enqueueSnackbar({
message: res.success ? 'Document generated.' : 'Generation failed.',
variant: res.success ? 'success' : 'error',
});
};
// ...render a <select> of templates and a Generate button
};
export default defineFrontComponent({
universalIdentifier: GENERATE_DOCUMENT_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
name: 'generate-document-form',
component: GenerateDocumentForm,
});
```
<Warning>
Değerleri `twenty-ui` içe aktarmak yerine satır içi CSS değişkenleriyle (`var(--t-color-blue)`) stillendirin. SDK, derleme sırasında bu paketi taklit eder, bu nedenle tema sabitlerinin modül düzeyindeki içe aktarımları `undefined` olur. Tam bileşene bakın:
[full component](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/front-components/generate-document-form.front-component.tsx).
</Warning>
## Onu açacak bir komut
Bir [komut menüsü öğesi](/l/tr/developers/extend/apps/layout/command-menu-items) `availabilityType: 'RECORD_SELECTION'` ile, bir Person seçildiğinde görünür ve bileşeni yan panelde açar.
```ts filename="src/command-menu-items/generate-document.command-menu-item.ts"
import { defineCommandMenuItem, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: GENERATE_DOCUMENT_COMMAND_UNIVERSAL_IDENTIFIER,
label: 'Generate document',
availabilityType: 'RECORD_SELECTION',
availabilityObjectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
frontComponentUniversalIdentifier:
GENERATE_DOCUMENT_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
});
```
## Tüm akışı deneyin
**People**'ı açın, bir kişiyi işaretleyin ve <kbd>⌘K</kbd> / <kbd>Ctrl K</kbd> tuşlarına basın.
"Generate document" komutu, uygulamanızla etiketlenmiş şekilde görünür:
<Frame caption="Bir Person seçildiğinde komut görünür.">
<img src="/images/docs/developers/extends/apps/document-generator/06-command-menu.png" alt="Generate document ile komut menüsü" />
</Frame>
Çalıştırın — bileşeniniz yan panelde açılır. Bir şablon seçin, **Generate**'a tıklayın ve **Documents** içinde yeni bir kayıt oluşur.
<Frame caption="Ön uç bileşeni, şablonları yüklüyor ve tıklamayla oluşturuyor.">
<img src="/images/docs/developers/extends/apps/document-generator/06b-front-component.png" alt="Generate document yan paneli" />
</Frame>
Oluşturulan her belge, uygulamanızı yazarı olarak kaydeder:
<Frame caption="Document Generator tarafından oluşturuldu, durum Generated.">
<img src="/images/docs/developers/extends/apps/document-generator/05-document-record.png" alt="Oluşturulmuş bir belge kaydı" />
</Frame>
## Bir belgenin kayıt sayfasında önizleme
Bir ön uç bileşeni sadece komut menüleri için değildir — onu bir **kayıt sayfasında sekme** olarak da bağlayabilirsiniz. Belge kaydına, Markdown gövdesini şık, yazdırılabilir bir sayfa olarak işleyen bir *Preview* sekmesi ekleyelim.
Bileşen, çalışma bağlamından geçerli kayıt kimliğini okur, belgeyi yükler ve işler. Ön uç bileşenleri, yalnızca belirli bir HTML etiketleri beyaz listesine izin veren bir **sandbox** içinde çalışır — ham HTML enjeksiyonu (`dangerouslySetInnerHTML`) ve `\<style>` engellenir — bu nedenle Markdown'ı, satır içi stillerle, küçük bir [`Markdown`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/utils/markdown-to-react.tsx) yardımcısı aracılığıyla React öğeleri olarak işliyoruz.
```tsx filename="src/front-components/document-viewer.front-component.tsx"
import { CoreApiClient } from 'twenty-client-sdk/core';
import { RestApiClient } from 'twenty-client-sdk/rest';
import { defineFrontComponent } from 'twenty-sdk/define';
import { useFrontComponentExecutionContext } from 'twenty-sdk/front-component';
import { Markdown } from 'src/utils/markdown-to-react';
const DocumentViewer = () => {
const recordId = useFrontComponentExecutionContext((c) => c.recordId ?? null);
// ...load { content, file } for recordId, then derive the links:
const pdfUrl = document.file?.[0]?.url;
const webUrl = new RestApiClient().resolveUrl('/s/documents/view', {
query: { id: recordId },
});
// Render the template body, plus quick links to the web page and the PDF.
// Links open in a new tab so they don't navigate the embedded component.
return (
<div style={styles.scroll}>
<div style={styles.actions}>
<a style={styles.actionLink} href={webUrl} target="_blank" rel="noopener noreferrer">
Open web page
</a>
{pdfUrl ? (
<a style={styles.actionLink} href={pdfUrl} target="_blank" rel="noopener noreferrer">
Download PDF
</a>
) : null}
</div>
<div style={styles.paper}>
<div style={styles.body}>
<Markdown content={document.content} />
</div>
</div>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: DOCUMENT_VIEWER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
name: 'document-viewer',
component: DocumentViewer,
});
```
Bunu bir [sayfa düzeni](/l/tr/developers/extend/apps/layout/page-layouts) ile bağlayın. Bir
`RECORD_PAGE` düzeni, bir nesnenin kayıt görünümüne sekmeler ekler; tek bir
`FRONT_COMPONENT` widget'ına sahip bir sekme, bileşeni kenardan kenara barındırır:
```ts filename="src/page-layouts/document-record.page-layout.ts"
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define';
export default definePageLayout({
universalIdentifier: DOCUMENT_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
name: 'Document record page',
type: 'RECORD_PAGE',
objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
tabs: [{
universalIdentifier: DOCUMENT_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
title: 'Preview',
icon: 'IconEye',
position: 50,
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
widgets: [{
universalIdentifier: DOCUMENT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER,
title: 'Document preview',
type: 'FRONT_COMPONENT',
configuration: {
configurationType: 'FRONT_COMPONENT',
frontComponentUniversalIdentifier: DOCUMENT_VIEWER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
},
}],
}],
});
```
Herhangi bir belgeyi açın — **Preview** sekmesi, belgeyi paylaşılabilir web sayfasına ve PDF'e bağlantılarla birlikte, güzel bir şekilde işler:
<Frame caption="Preview sekmesi, belgeyi satır içi stillerle ve hızlı bağlantılarla işler.">
<img src="/images/docs/developers/extends/apps/document-generator/09-document-viewer.png" alt="Kayıt sayfası sekmesinde belge görüntüleyici ön uç bileşeni" />
</Frame>
## Zengin metin editörüyle bir şablonu düzenleme
Şablonların özel bir bileşene hiç ihtiyacı yoktur. `body` bir
`RICH_TEXT` alanı olduğu için, Twenty bunun için zaten tam özellikli bir zengin metin editörü sunar — standart Note ve Task nesnelerinin kullandığı editörün aynısı. Biz sadece onu şablon kayıt sayfasında görünür hale getiriyoruz.
`FIELD` widget'lı, `EDITOR` görüntü modunda ve `fieldMetadataId` aracılığıyla `body` alanını işaret eden bir sekme ekleyin:
```ts filename="src/page-layouts/template-record.page-layout.ts"
{
universalIdentifier: TEMPLATE_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
title: 'Template',
position: 1,
layoutMode: PageLayoutTabLayoutMode.GRID,
widgets: [{
universalIdentifier: TEMPLATE_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER,
title: 'Template',
type: 'FIELD',
position: { layoutMode: PageLayoutTabLayoutMode.GRID, row: 0, column: 0, rowSpan: 6, columnSpan: 12 },
configuration: {
configurationType: 'FIELD',
fieldMetadataId: TEMPLATE_BODY_FIELD_UNIVERSAL_IDENTIFIER,
fieldDisplayMode: 'EDITOR',
},
}],
}
```
Bir `RICH_TEXT` alanı, hem editörün blok JSON'unu hem de bir Markdown izdüşümünü saklar. Oluşturma hattı bu Markdown izdüşümünü okur, böylece yer tutucular, PDF ve paylaşılabilir web sayfası hiç değişmeden çalışmaya devam eder — tam [`template-record.page-layout.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/page-layouts/template-record.page-layout.ts) dosyasına bakın.
`FIELD` bileşenleri, ayrıca iki adım uzaktaki iç içe geçmiş bir ilişkideki
kayıtların listelenmesi de dahil olmak üzere, diğer görüntüleme modlarını ve seçenekleri de destekler — bkz.
[alan bileşeni referansı](/l/tr/developers/extend/apps/layout/page-layouts#field-widgets).
Artık editörler, şablonları düzgün bir zengin metin editöründe yazıyor:
<Frame caption="Template sekmesi: Twenty'nin yerel zengin metin editörü, body alanına bağlı.">
<img src="/images/docs/developers/extends/apps/document-generator/10-template-editor.png" alt="Yerel zengin metin editörü sekmesine sahip şablon kaydı" />
</Frame>
**Bu adımdan sonra:** belgeler harika bir şekilde önizlenir ve şablonlar uygulama içinde düzenlenebilir. Sırada, bir AI temsilcisinin bunları bir sohbetten oluşturmasını sağlamak var.
<Card title="Sırada: bir AI temsilcisi →" icon="robot" href="/l/tr/developers/extend/apps/tutorials/document-generator/ai-agent">
Aracınızı çağıran bir temsilci ve bir yetenek ekleyin.
</Card>

View File

@@ -0,0 +1,132 @@
---
title: 1. Veri modeli
icon: database
description: Belgeleri ve şablonları nesneler, alanlar ve bir ilişki ile modelleyin.
---
Uygulamamızın iki özel nesneye ihtiyacı var: **belge şablonları** (ne yazılacağını belirten) ve
**belgeler** (oluşturulan sonuç). Bunları tanımlayalım.
CLI ile her varlık dosyasını iskelet olarak oluşturun — sizin için geçerli bir UUID ve doğru klasörü oluşturur:
```bash filename="Terminal"
yarn twenty dev:add object
```
Aşağıda tamamlanmış dosyaları gösteriyoruz.
<Note>
Her `*_UNIVERSAL_IDENTIFIER` sabiti
`src/constants/universal-identifiers.ts` içinde bulunur ve kullanıldığı yerde içe aktarılır. Aşağıdaki kod parçacıkları
kısalık için bu içe aktarmaları atlar — kendi dosyalarınızda bunları eklemeyi unutmayın.
</Note>
## Şablon nesnesi
Bir şablonun bir `name` alanı, `{{placeholders}}` içeren bir `body` alanı ve bunun bir Person mı yoksa bir Company için mi yazıldığını belirten bir `target` alanı vardır. `body` bir
`RICH_TEXT` alanıdır, bu nedenle Twenty ona tam özellikli bir zengin metin düzenleyicisi verir.
```ts filename="src/objects/document-template.object.ts"
import { defineObject, FieldType } from 'twenty-sdk/define';
export default defineObject({
universalIdentifier: DOCUMENT_TEMPLATE_OBJECT_UNIVERSAL_IDENTIFIER,
nameSingular: 'documentTemplate',
namePlural: 'documentTemplates',
labelSingular: 'Document template',
labelPlural: 'Document templates',
icon: 'IconFileText',
labelIdentifierFieldMetadataUniversalIdentifier:
TEMPLATE_NAME_FIELD_UNIVERSAL_IDENTIFIER,
fields: [
{ universalIdentifier: TEMPLATE_NAME_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.TEXT, name: 'name', label: 'Name', icon: 'IconAbc' },
{ universalIdentifier: TEMPLATE_BODY_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.RICH_TEXT, name: 'body', label: 'Body', icon: 'IconFileText',
description: 'Use {{placeholders}} like {{name.firstName}} or {{jobTitle}}.' },
{ universalIdentifier: TEMPLATE_TARGET_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.SELECT, name: 'target', label: 'Target', icon: 'IconTarget',
defaultValue: `'PERSON'`,
options: [
{ id: TEMPLATE_TARGET_OPTION_PERSON_UNIVERSAL_IDENTIFIER,
value: 'PERSON', label: 'Person', color: 'blue', position: 0 },
{ id: TEMPLATE_TARGET_OPTION_COMPANY_UNIVERSAL_IDENTIFIER,
value: 'COMPANY', label: 'Company', color: 'green', position: 1 },
] },
],
});
```
<Warning>
`SELECT` seçenek **değerleri** `UPPER_CASE` (`PERSON`, `person` değil) olmalıdır ve
`defaultValue` fazladan tırnak içine alınır: `` `'PERSON'` ``. `label`,
kullanıcıların gördüğü şeydir.
</Warning>
## Belge nesnesi
Oluşturulan belge, işlenmiş `content` ve bir `status` saklar. Bunu
yine aynı şekilde tanımlayın; `status` için `DRAFT` / `GENERATED` seçenekli bir select alanı kullanın. Tam dosya:
[`document.object.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/objects/document.object.ts).
## Bir ilişkiyle bunları birbirine bağlama
Her belge, geldiği şablonu işaret etmelidir. İlişkiler
**çift yönlüdür** — her iki tarafı da tanımlarsınız ve her birini kendi alan dosyasında belirtirsiniz.
```ts filename="src/fields/document-template-relation.field.ts"
import { defineField, FieldType, OnDeleteAction, RelationType } from 'twenty-sdk/define';
// The "many" side: each document belongs to one template.
export default defineField({
universalIdentifier: DOCUMENT_TEMPLATE_FIELD_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'template',
label: 'Template',
relationTargetObjectMetadataUniversalIdentifier:
DOCUMENT_TEMPLATE_OBJECT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier:
TEMPLATE_DOCUMENTS_FIELD_UNIVERSAL_IDENTIFIER,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'templateId',
},
});
```
Diğer taraf (`template-documents-relation.field.ts`),
ters yönde işaret eden ve adı `documents` olan bir `RelationType.ONE_TO_MANY` alanıdır.
Tam desen için [Relations](/l/tr/developers/extend/apps/data/relations) bölümüne bakın.
## Bunu Twenty'de görün
`yarn twenty dev` çalışırken **Settings → Data model** bölümünü açın. Her iki nesne de
uygulamanızla etiketlenmiş olarak görünür.
<Frame caption="Her iki özel nesne, Document Generator uygulamasına aittir.">
<img src="/images/docs/developers/extends/apps/document-generator/01-data-model.png" alt="Veri modeli ayarları, Documents ve Document templates nesnelerini gösteriyor" />
</Frame>
Test etmek için bir şablon oluşturun — adını *Sales proposal* koyun, **Target** alanını
*Person* olarak ayarlayın ve birkaç yer tutucu içeren bir gövde yapıştırın:
```text
Dear {{name.firstName}} {{name.lastName}},
As {{jobTitle}} at {{company.name}}, we think you'll love our product.
Best,
The Team
```
<Frame caption="Bir şablon kaydı. Gövde, bir belge oluşturulana kadar yer tutucularını korur.">
<img src="/images/docs/developers/extends/apps/document-generator/03-template-record.png" alt="Yer tutucu gövdesine sahip bir Sales proposal şablon kaydı" />
</Frame>
**Bu adımdan sonra:** bir ilişkiyle birbirine bağlı `documentTemplate` ve `document` nesnelerine ve bunlardan belge oluşturmak için bir şablona sahipsiniz. Sırada, onu dolduran mantık var.
<Card title="Sıradaki: belgeleri oluşturma →" icon="bolt" href="/l/tr/developers/extend/apps/tutorials/document-generator/generating-documents">
Şablonu dolduran mantık fonksiyonunu yazın.
</Card>

View File

@@ -0,0 +1,208 @@
---
title: 2. Belgeleri oluşturma
icon: bolt
description: Yapay zekâ aracı ve iş akışı eylemi olarak sunulan tek bir mantık işlevi.
---
Şimdi esas kısma geçelim: bir şablon ve bir kaydı yükleyen, yer tutucuları dolduran ve yeni bir belge kaydeden bir [mantık işlevi](/l/tr/developers/extend/apps/logic/logic-functions).
İş mantığını **handler** olarak bir kez yazacağız, sonra bunu birkaç tetikleyici üzerinden kullanıma sunacağız. Bu bölüm bunlardan ikisini birleştiriyor — bir **yapay zekâ aracı** ve bir **iş akışı eylemi**.
## Oluşturma yardımcısı
Saf mantığı kendi dosyasında tutun ki birim testi yapmak kolay olsun. Bu, bir kaydı `{{dot.path}}` belirteçlerine düzleştirir ve bunların yerine değerlerini koyar.
```ts filename="src/logic-functions/utils/render-template.ts"
const PLACEHOLDER_PATTERN = /\{\{\s*([\w.]+)\s*\}\}/g;
export const renderTemplate = (body: string, values: Record<string, string>) => {
const missingTokens = new Set<string>();
const content = body.replace(PLACEHOLDER_PATTERN, (_m, token: string) => {
const value = values[token];
if (value === undefined || value === '') { missingTokens.add(token); return ''; }
return value;
});
return { content, missingTokens: [...missingTokens] };
};
```
<Tip>
Bu dosyanın yan etkisi olmadığından, onu hızlı birim testleriyle kapsayabilirsiniz (`yarn test:unit`). Bkz. [Testing](/l/tr/developers/extend/apps/operations/testing).
</Tip>
## İşleyici
Handler, CRM verilerini okumak ve yazmak için oluşturulmuş [`CoreApiClient`](/l/tr/developers/extend/apps/logic/logic-functions) kullanır. Şablonu yükler, hedef kaydı yükler, gövdeyi doldurur ve bir `document` oluşturur.
```ts filename="src/logic-functions/handlers/generate-document-handler.ts"
import { CoreApiClient } from 'twenty-client-sdk/core';
import { loadRecordValues } from 'src/logic-functions/utils/load-record-values';
import { renderTemplate } from 'src/logic-functions/utils/render-template';
export const generateDocumentHandler = async (
input: { templateId: string; recordId: string },
) => {
const client = new CoreApiClient();
// Use a filtered list query, not the singular lookup: the singular query
// throws when nothing matches, which would become a 500 instead of a 404.
const { documentTemplates } = await client.query({
documentTemplates: {
__args: { filter: { id: { eq: input.templateId } }, first: 1 },
edges: { node: { id: true, name: true, body: true, target: true } },
},
});
const documentTemplate = documentTemplates?.edges?.[0]?.node;
if (!documentTemplate?.id) return { success: false, status: 404, message: 'Template not found.' };
const record = await loadRecordValues(client, documentTemplate.target, input.recordId);
if (!record.found) return { success: false, status: 404, message: 'Record not found.' };
const { content, missingTokens } = renderTemplate(documentTemplate.body ?? '', record.values);
const { createDocument } = await client.mutation({
createDocument: {
__args: { data: {
name: `${documentTemplate.name} — ${record.displayName}`,
content, status: 'GENERATED', templateId: documentTemplate.id,
} },
id: true, name: true,
},
});
return { success: true, documentId: createDocument.id, content, missingTokens };
};
```
`loadRecordValues`, Kişi ile Şirket için farklı bir sorgu çalıştırır ve sonucu düzleştirir — bkz.
[`load-record-values.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/logic-functions/utils/load-record-values.ts).
## Bunu bir araç ve iş akışı eylemi olarak kullanıma açın
Tek bir `defineLogicFunction` birden çok tetikleyici barındırabilir. Burada, `toolTriggerSettings` onu AI aracılarının çağırabileceği hâle getirir ve `workflowActionTriggerSettings` onu görsel iş akışı oluşturucusundaki bir adıma dönüştürür. Her ikisi de girdilerini bir JSON şemasıyla tanımlar.
```ts filename="src/logic-functions/generate-document.ts"
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema } from 'twenty-sdk/logic-function';
import { GENERATE_DOCUMENT_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { generateDocumentHandler } from 'src/logic-functions/handlers/generate-document-handler';
import { generateDocumentInputSchema } from 'src/logic-functions/schemas/generate-document-input.schema';
export default defineLogicFunction({
universalIdentifier: GENERATE_DOCUMENT_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
name: 'generate-document',
description: 'Generate a document from a template and a CRM record.',
timeoutSeconds: 30,
toolTriggerSettings: {
inputSchema: generateDocumentInputSchema,
},
workflowActionTriggerSettings: {
label: 'Generate Document',
icon: 'IconFileText',
inputSchema: jsonSchemaToInputSchema(generateDocumentInputSchema),
outputSchema: [{ type: 'object', properties: {
success: { type: 'boolean' }, documentId: { type: 'string' },
} }],
},
handler: generateDocumentHandler,
});
```
Girdi şeması, `templateId` ve `recordId` değerlerini tanımlayan basit bir JSON şemasıdır — bkz. [`generate-document-input.schema.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/logic-functions/schemas/generate-document-input.schema.ts).
## Erişim verin
Mantık işlevleri uygulamanın rolü olarak çalışır. Şablonları ve kayıtları okuması ve belgeler oluşturması gerektiğinden, `src/roles/default-role.ts` içinde buna izin verin:
```ts
export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Document Generator default role',
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: true,
canAccessAllTools: true,
canBeAssignedToAgents: true,
permissionFlagUniversalIdentifiers: [SystemPermissionFlag.UPLOAD_FILE],
});
```
`UPLOAD_FILE`, bir sonraki bölümde oluşturulan PDF'yi işlevin yüklemesine izin verir.
Daha ayrıntılı izinler için bkz. [Roles](/l/tr/developers/extend/apps/config/roles).
## Gerçek bir PDF dosyası ekleyin
Oluşturulmuş bir metin alanı kullanışlıdır, ancak kullanıcılar gerçek bir belge ister. Bir **PDF** oluşturalım ve onu kayıt üzerinde indirilebilir bir dosya olarak saklayalım.
Önce, PDF'yi tutmak için `document` nesnesine bir `FILES` alanı verin. Uygulamalar **kendi** dosya alanlarına yükleme yapar, dolayısıyla yüklemenin yönlendirildiği yer bu alandır:
```ts filename="src/objects/document.object.ts"
{
universalIdentifier: DOCUMENT_FILE_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.FILES,
name: 'file',
label: 'File',
icon: 'IconFileTypePdf',
universalSettings: { maxNumberOfValues: 1 },
}
```
Şimdi o PDF'yi oluşturun. Bir uygulama gerçek bir Node projesidir, bu yüzden ihtiyacınız olan herhangi bir npm paketini ekleyebilir ve onu başka yerlerde olduğu gibi içe aktarabilirsiniz. Biz, PDF'yi çizmek için **[pdf-lib](https://pdf-lib.js.org/)** ve Markdown gövdesini ayrıştırmak için **[marked](https://marked.js.org/)** kullanıyoruz — CLI bunları işlevin çalışma zamanına sizin için kurar:
```bash filename="Terminal"
yarn add pdf-lib marked
```
Tam yardımcı işlev şuradadır: [`generate-document-pdf.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/logic-functions/utils/generate-document-pdf.ts).
Bu, Markdown'ı `marked.lexer` ile belirteçlere ayrıştırır, ardından bunları pdf-lib ile yerleştirir: gerçek başlıklar, **kalın**/*italik* metinler, madde ve numaralı listeler, alıntılar ve çizgiler — şablonun kendisinin duvar gibi bir metin yerine cilalı, çok sayfalı A4 işlenmesi.
<Frame caption="Oluşturulan PDF: gerçek tipografi ve Markdown biçimlendirmesiyle şablon gövdesinin işlenmiş hâli.">
<img src="/images/docs/developers/extends/apps/document-generator/07b-generated-pdf.png" alt="Cilalı, pazarlanabilir oluşturulmuş bir PDF" />
</Frame>
<Note>
pdf-lib'in yerleşik yazı tipleri WinAnsi kodlaması kullanır, bu nedenle Batı Avrupa aksanları kutudan çıktığı gibi oluşturulur; yardımcı, akıllı tırnak işaretlerini ve tireleri eşler ve kodlayamadığı karakterleri atar. Latin olmayan yazı sistemlerini (Çince, Arapça, Kiril) işlemek, bir Unicode yazı tipi gömmeyi gerektirir.
</Note>
Sonra onu yükleyin ve referansını kayıt üzerinde saklayın. `uploadFile`, baytları uygulamanıza ait dosya alanına yönlendirir; döndürülen `id`, kaydettiğiniz değerdir:
```ts filename="src/logic-functions/handlers/generate-document-handler.ts"
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { generateDocumentPdf } from 'src/logic-functions/utils/generate-document-pdf';
const documentName = `${documentTemplate.name} — ${record.displayName}`;
const bytes = await generateDocumentPdf(documentName, content);
const fileName = 'proposal.pdf';
const uploaded = await new MetadataApiClient().uploadFile(
Buffer.from(bytes),
fileName,
'application/pdf',
DOCUMENT_FILE_FIELD_UNIVERSAL_IDENTIFIER,
);
await client.mutation({
updateDocument: {
__args: {
id: documentId,
data: { file: [{ fileId: uploaded.id, label: fileName }] },
},
id: true,
},
});
```
Oluşturulan belge artık indirilebilir bir PDF taşıyor:
<Frame caption="Oluşturulan PDF, belgenin Dosya alanında saklanır.">
<img src="/images/docs/developers/extends/apps/document-generator/08-document-with-pdf.png" alt="Oluşturulmuş PDF dosyasına sahip bir belge kaydı" />
</Frame>
<Note>
`uploadFile` yalnızca **uygulamaya ait** dosya alanlarını hedefler (bu nedenle yüklemeler her zaman alanın sahibi olan bir uygulama ve ayrıca `UPLOAD_FILE` rol bayrağı gerektirir). Bu nedenle PDF, kaydın kendi `file` alanına düşer — [call-recorder app](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/public/call-recorder) uygulamasının kayıtlar için kullandığı desenle aynıdır.
</Note>
**Bu adımdan sonra:** her oluşturulan belgenin gerçek, indirilebilir bir PDF'si vardır. Ancak henüz hiçbir şey oluşturucuyu arayüzden *çağıramıyor* — bunun için bir HTTP rotasına ihtiyacımız var.
<Card title="Sırada: HTTP rotaları →" icon="globe" href="/l/tr/developers/extend/apps/tutorials/document-generator/http-routes">
Fonksiyonu HTTP üzerinden sunun ve belgeleri web sayfaları olarak oluşturun.
</Card>

View File

@@ -0,0 +1,138 @@
---
title: 3. HTTP rotaları
icon: globe
description: İşlevi HTTP üzerinden tetikleyin ve belgeleri web sayfaları olarak oluşturun.
---
Aynı işleyici HTTP isteklerine de yanıt verebilir. İki rota ekleyeceğiz:
* belge oluşturmak için arayüzün çağırdığı bir **POST** uç noktası ve
* belgeyi yazdırılabilir bir web sayfası olarak oluşturan herkese açık bir **GET** uç noktası.
Her ikisi de `httpRouteTriggerSettings` kullanır. Uygulama rotalarına `/s/` yolu ile erişilir; yerel
ve kendi sunucunuzda barındırılan örneklerde, bu yol Twenty sunucunuz
tarafından sunulur (örneğin `http://localhost:2020/s/documents/generate`). Twenty Cloud'da, `RestApiClient`
bu yolu çalışma alanının functions alan adı üzerinden çözümler.
## POST rotası — istek üzerine oluşturma
Bu, `generateDocumentHandler`’ı yeniden kullanır, bu nedenle tekrarlanacak bir mantık yoktur — yalnızca istek gövdesini okuyan ince bir adaptör vardır.
```ts filename="src/logic-functions/generate-document-route.ts"
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
import { Response } from 'twenty-sdk/logic-function';
import { generateDocumentHandler } from 'src/logic-functions/handlers/generate-document-handler';
const handler = async (event: RoutePayload): Promise<Response> => {
const body = event.body as Record<string, unknown> | null;
const result = await generateDocumentHandler({
templateId: (body?.templateId as string) ?? '',
recordId: (body?.recordId as string) ?? '',
});
// Map the handler's failure reason onto a real HTTP status (400/404/500)
// instead of always returning 200.
return new Response(JSON.stringify(result), {
status: result.success ? 200 : (result.status ?? 400),
headers: { 'Content-Type': 'application/json' },
});
};
export default defineLogicFunction({
universalIdentifier: GENERATE_DOCUMENT_ROUTE_UNIVERSAL_IDENTIFIER,
name: 'generate-document-route',
timeoutSeconds: 30,
handler,
httpRouteTriggerSettings: {
path: '/documents/generate',
httpMethod: 'POST',
isAuthRequired: true,
},
});
```
Paylaşılan işleyici, hata durumunda önerilen bir `status` döndürür; böylece rota uygun bir `4xx`/`5xx` koduyla yanıt verebilir. `isAuthRequired: true`, çağıranın geçerli bir belirteç sunması gerektiği anlamına gelir — bir sonraki bölümdeki ön uç bileşeni, kullanıcının erişim belirtecini otomatik olarak iletir.
## GET rotası — web sayfası olarak oluşturma
JSON yerine HTML döndürmek için gövdeyi `Content-Type` başlığıyla birlikte bir `Response` içine alın. Bu rota herkese açıktır (`isAuthRequired: false`), böylece oluşturulan bir belge bağlantı olarak paylaşılabilir.
```ts filename="src/logic-functions/view-document.ts"
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
import { Response } from 'twenty-sdk/logic-function';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { documentHtmlPage } from 'src/utils/render-document';
const htmlResponse = (html: string, status = 200): Response =>
new Response(html, { status, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
const handler = async (event: RoutePayload): Promise<Response> => {
const documentId = event.queryStringParameters?.id;
if (!documentId) {
return htmlResponse(documentHtmlPage('Missing document id', 'Provide ?id=<documentId>.'), 400);
}
// Filtered list query so an unknown id renders a clean 404 page instead of throwing.
const { documents } = await new CoreApiClient().query({
documents: {
__args: { filter: { id: { eq: documentId } }, first: 1 },
edges: { node: { id: true, name: true, content: true } },
},
});
const document = documents?.edges?.[0]?.node;
if (!document?.id) {
return htmlResponse(documentHtmlPage('Document not found', `No document with id ${documentId}.`), 404);
}
return htmlResponse(documentHtmlPage(document.name ?? 'Document', document.content ?? ''));
};
export default defineLogicFunction({
universalIdentifier: VIEW_DOCUMENT_ROUTE_UNIVERSAL_IDENTIFIER,
name: 'view-document',
timeoutSeconds: 15,
handler,
httpRouteTriggerSettings: {
path: '/documents/view',
httpMethod: 'GET',
isAuthRequired: false,
},
});
```
`documentHtmlPage`, Markdown gövdesini HTML’ye dönüştürür ([marked](https://marked.js.org/) ile, temizlenmiş) ve onu yalnızca şablon içeriğini gösteren, temiz, yazdırılabilir bir sayfaya yerleştirir — PDF ve uygulama içi önizleme ile aynı görünüme sahiptir.
[Yardımcıyı inceleyin](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/utils/render-document.ts).
## Deneyin
Çalışma alanınızda bir şablon ve bir Kişi ile rotayı çağırın (**Ayarlar → API'ler ve Web kancaları** bölümünden bir belirteç alın):
```bash filename="Terminal"
curl -X POST http://localhost:2020/s/documents/generate \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"templateId":"<templateId>","recordId":"<personId>"}'
# → {"success":true,"documentId":"...","content":"Dear Jeffery Griffin, ..."}
```
Döndürülen belgeyi tarayıcınızda açın:
```
http://localhost:2020/s/documents/view?id=<documentId>
```
<Frame caption="Herkese açık GET rotası, belgeyi yazdırılabilir bir sayfa olarak oluşturur.">
<img src="/images/docs/developers/extends/apps/document-generator/07-rendered-document.png" alt="Oluşturulan bir belgenin web sayfası" />
</Frame>
<Tip>
Ayrıca test ederken bir işlevin günlüklerini `yarn twenty dev:function:logs` ile gerçek zamanlı izleyebilir veya `yarn twenty dev:function:exec` ile doğrudan çağırabilirsiniz.
</Tip>
**Bu adımdan sonra:** uygulama HTTP üzerinden belgeler oluşturabilir ve bunları web sayfaları olarak sunabilir. Şimdi bunu `curl` olmadan kullanılabilir hale getirelim.
<Card title="Sıradaki: arayüzü oluşturma →" icon="table-columns" href="/l/tr/developers/extend/apps/tutorials/document-generator/building-the-ui">
Görünümler, gezinme, bir komut ve bir ön uç bileşeni.
</Card>

View File

@@ -0,0 +1,61 @@
---
title: "Eğitim: Belge Oluşturucu"
icon: wand-magic-sparkles
description: CRM verilerinizden kişiselleştirilmiş belgeler oluşturan gerçek bir Twenty uygulaması geliştirin.
---
Bu eğitimde, yeniden kullanılabilir şablonları CRM’inizde zaten bulunan verileri kullanarak kişiselleştirilmiş belgelere dönüştüren bir uygulama olan **Belge Oluşturucu**’yu geliştireceksiniz.
`{{placeholders}}` ile bir kez şablon yazın, sonra komut menüsünden, bir yapay zekâ ajanından veya bir iş akışından tek tıklamayla herhangi bir Kişi veya Şirket için doldurulmuş bir belge oluşturun.
<Frame caption="Belirli bir kişi için oluşturulmuş, yazdırılabilir bir sayfa olarak açılan bir şablon.">
<img src="/images/docs/developers/extends/apps/document-generator/07-rendered-document.png" alt="Oluşturulmuş bir Satış teklifi belgesi" />
</Frame>
## Neler öğreneceksiniz
Her bölüm bir yetenek ekler. Sonunda SDK'nin büyük kısmını kullanmış olacaksınız.
| Bölüm | Yetenek | Başvuru |
| -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| [1. Veri Modeli](/l/tr/developers/extend/apps/tutorials/document-generator/data-model) | Nesneler, alanlar ve bir ilişki | [Veri](/l/tr/developers/extend/apps/data/overview) |
| [2. Belgeler oluşturma](/l/tr/developers/extend/apps/tutorials/document-generator/generating-documents) | Bir Markdown şablonunu dolduran ve cilalı bir PDF ekleyen mantık işlevi (Yapay zekâ aracı + iş akışı eylemi) | [Mantık işlevleri](/l/tr/developers/extend/apps/logic/logic-functions) |
| [3. HTTP rotaları](/l/tr/developers/extend/apps/tutorials/document-generator/http-routes) | Rotalardan JSON ve paylaşılabilir bir HTML sayfası sunma | [Mantık işlevleri](/l/tr/developers/extend/apps/logic/logic-functions) |
| [4. Kullanıcı arayüzünü oluşturma](/l/tr/developers/extend/apps/tutorials/document-generator/building-the-ui) | Görünümler, gezinme, komut menüsü ve bir belgeyi önizleyen ve bir şablonu düzenleyen ön uç bileşenleri | [Düzen](/l/tr/developers/extend/apps/layout/overview) |
| [5. Bir yapay zekâ ajanı](/l/tr/developers/extend/apps/tutorials/document-generator/ai-agent) | Ajan + beceri | [Beceriler ve Temsilciler](/l/tr/developers/extend/apps/logic/skills-and-agents) |
| [6. Yayınlama](/l/tr/developers/extend/apps/tutorials/document-generator/publishing) | Onu pazaryerine gönderin | [Yayınlama](/l/tr/developers/extend/apps/operations/publishing) |
## Ön Gereksinimler
[Hızlı Başlangıç](/l/tr/developers/extend/apps/getting-started/quick-start) bölümünü tamamlamış olmalısınız:
`2020` portunda çalışan yerel bir Twenty sunucusu ve ona kimliği doğrulanmış CLI.
Değilse, şimdi bir tane iskeletini oluşturup başlatın:
```bash filename="Terminal"
npx create-twenty-app@latest document-generator
cd document-generator
yarn twenty dev
```
<Note>
Bitmiş kodu okumayı mı tercih edersiniz? Tam uygulama şurada bulunur:
[`packages/twenty-apps/examples/document-generator`](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/document-generator).
Aşağıdaki her kod parçası oradan kopyalanmıştır.
</Note>
## Uygulamanın nasıl bir araya geldiği
<Frame>
<img src="/images/docs/developers/extends/apps/document-generator/how-it-fits.svg" alt="Yer tutucular içeren bir şablon, komut menüsünden, bir yapay zekâ ajanından, bir iş akışından veya paylaşılabilir bir bağlantıdan tetiklenerek PDF’li cilalı bir belgeye dönüştürülür" />
</Frame>
`{{placeholders}}` ile zengin metin düzenleyicide bir **şablonu** bir kez yazarsınız. Bir şablon ve bir CRM kaydı seçmek, yer tutucuları doldurur ve cilalı bir **belgeyi** (PDF dosyasıyla birlikte) depolar. Geri kalan her şey — komut menüsü, yapay zekâ ajanı, iş akışı adımı, paylaşılabilir bağlantı — o tek oluşturucuyu tetiklemenin sadece farklı bir yoludur.
## Bu döngüyü çalışır durumda tutun
Tüm eğitim boyunca bir terminalde `yarn twenty dev` komutunu çalışır durumda bırakın. `src/` altında her dosya eklediğinizde veya düzenlediğinizde, birkaç saniye içinde sunucunuzla yeniden eşitlenir; böylece siz oluştururken her yeteneğin kullanıcı arayüzünde nasıl ortaya çıktığını izleyebilirsiniz.
<Card title="Oluşturmaya başlayın →" icon="database" href="/l/tr/developers/extend/apps/tutorials/document-generator/data-model">
1. Bölüm: belgeleri ve şablonları modelleyin.
</Card>

View File

@@ -0,0 +1,128 @@
---
title: 6. Yayımlama
icon: rocket
description: Pazaryeri meta verilerini ekleyin ve uygulamanızı yayımlayın.
---
Uygulamanız çalışıyor. Son adım, onu pazaryeri için tanımlamak ve yayımlamaktır.
## Pazaryeri meta verilerini ekleyin
[Application config](/l/tr/developers/extend/apps/config/application), pazaryerinde görünen kimliği taşır: yazar, kategori, logo ve destek bağlantıları. `public/` içine bir logo yerleştirin ve `logo` ile referans verin.
```ts filename="src/application-config.ts"
import { defineApplication } from 'twenty-sdk/define';
export default defineApplication({
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
displayName: 'Document Generator',
description:
'Create reusable document templates and generate personalized documents from your CRM records.',
logo: 'public/document-generator.svg',
author: 'Twenty',
category: 'Productivity',
websiteUrl: 'https://docs.twenty.com/l/tr/developers/extend/apps',
termsUrl: 'https://www.twenty.com/terms',
emailSupport: 'contact@twenty.com',
issueReportUrl: 'https://github.com/twentyhq/twenty/issues',
});
```
<Tip>
Varsayılan rol, kendi dosyasında `defineApplicationRole()` ile tanımlanır — artık burada `defaultRoleUniversalIdentifier` iletmiyorsunuz.
</Tip>
Ayrıca uygulamanın bulunabilir olması için `package.json` dosyasına `twenty-app` anahtar sözcüğünü ekleyin:
```json filename="package.json"
{ "keywords": ["twenty-app"] }
```
## Galeri ekran görüntüleri ekleyin
Bir pazaryeri listesi, kendini ekran görüntüleriyle satar. Birkaç PNG dosyasını
`public/gallery/` içine bırakın ve bunlara `galleryImages` ile referans verin — listeleme sayfasında, dizi sırasına göre bir galeri olarak görüntülenirler.
```ts filename="src/application-config.ts"
export default defineApplication({
// ...identity from above
galleryImages: [
'public/gallery/01-generated-document.png',
'public/gallery/02-command-menu.png',
'public/gallery/03-template-editor.png',
'public/gallery/04-documents.png',
],
});
```
<Tip>
Sonuçla başlayın: ilk ekran görüntüsü bitmiş sonuç (oluşturulmuş bir belge) olsun, ardından bunun nasıl tetiklendiğini ve oluşturulduğunu gösterin. Net, yüksek çözünürlüklü görüntüler kullanın — bunlar bir kullanıcının gördüğü ilk şeydir.
</Tip>
`README.md` dosyasına da aynı özeni gösterin — npm ve GitHub üzerindeki ön sayfadır.
Değer önerisi ve bir ekran görüntüsüyle başlayın, öne çıkan özellikleri listeleyin ve derleme ayrıntılarını sayfanın alt kısmında tutun.
## Yayımlamadan önce kontrol edin
CI ile aynı denetimleri çalıştırın:
```bash filename="Terminal"
yarn lint # oxlint
yarn typecheck # tsgo
yarn test:unit # unit tests
yarn twenty plan # preview the metadata diff
```
Plan, sunucuda neyin değişeceğini uygulamadan tam olarak gösterir —
iyi bir son sağlama kontrolüdür. Bkz.
[Testing](/l/tr/developers/extend/apps/operations/testing) ve
[Syncing & recovery](/l/tr/developers/extend/apps/operations/sync-and-recovery).
## Yayımla
```bash filename="Terminal"
# Public app → npm (default)
yarn twenty app:publish
# Or deploy privately to a specific server's registry
yarn twenty app:publish --private -r <remote>
```
`app:publish` varsayılan olarak oluşturur ve npm'e yayımlar; `--private` bunun yerine bir tarball dosyasını bir Twenty sunucusunun özel kaydına yükler. Yayımlanmış bir uygulamayı bir örneğin pazaryerinde görünür kılmak için, bir katalog eşitlemesini tetikleyin:
```bash filename="Terminal"
yarn twenty dev:catalog-sync -r <remote>
```
Tüm ayrıntılar ve yayımlama kontrol listesi:
[Publishing](/l/tr/developers/extend/apps/operations/publishing).
## Bir uygulama geliştirdiniz 🎉
Altı bölümde SDK yüzeyinin büyük kısmını kullandınız:
* Verileri modellemek için **nesneler, alanlar ve bir ilişki**
* Bir **mantık işlevinin**, bir **yapay zekâ aracı**, bir **iş akışı eylemi** ve **HTTP yolları** olarak sunulması
* Arayüz için **görünümler, gezinme, bir komut ve bir ön uç bileşeni**
* Doğal dil üretimi için bir **ajan + beceri**
* **Pazaryeri meta verileri** ve yayımlama akışı
Bitmiş uygulama şurada bulunur:
[`packages/twenty-apps/examples/document-generator`](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/document-generator).
## Sırada ne var
<CardGroup cols={2}>
<Card title="Veri başvurusu" icon="database" href="/l/tr/developers/extend/apps/data/overview">
Her alan türü, ilişki ve indeks seçeneği.
</Card>
<Card title="Mantık başvurusu" icon="bolt" href="/l/tr/developers/extend/apps/logic/overview">
Cron ve veritabanı olayı tetikleyicileri, anahtar-değer deposu, OAuth bağlantıları.
</Card>
<Card title="Düzen başvurusu" icon="table-columns" href="/l/tr/developers/extend/apps/layout/overview">
Sayfa düzenleri, pano bileşenleri ve daha fazla arayüz yüzeyi.
</Card>
<Card title="İşlemler" icon="rocket" href="/l/tr/developers/extend/apps/operations/overview">
CLI, test, uzak depolar ve CI.
</Card>
</CardGroup>

View File

@@ -0,0 +1,147 @@
---
title: API'ler
description: CRM verilerinizi REST veya GraphQL kullanarak programatik olarak sorgulayın ve değiştirin.
---
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.
## Öncelik Geliştiricide Yaklaşımı
Twenty, veri modeliniz için özel olarak API'ler oluşturur:
* **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.
<Note>
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.
</Note>
## İki API Türü
### Temel API
`/rest/` veya `/graphql/` üzerinden erişilebilir.
Gerçek **kayıtlarınızla** (verilerin kendisiyle) çalışın:
* People, Companies, Opportunities vb. oluşturun, okuyun, güncelleyin, silin.
* Verileri sorgulayın ve filtreleyin
* Kayıt ilişkilerini yönetin
### Meta Veriler 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 Core hem de Metadata 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}/` |
## 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. Alanı Yapılandır:
* **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
<VimeoEmbed videoId="928786722" title="API anahtarı oluşturma" />
<Warning>
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.
</Warning>
### Bir API Anahtarına Rol Atama
Daha iyi güvenlik için, erişimi sınırlamak amacıyla belirli bir rol atayın:
1. **Ayarlar → Üyeler → 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
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 |
<Tip>
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.
</Tip>

View File

@@ -0,0 +1,112 @@
---
title: Webhooklar
description: CRM'inizde olaylar gerçekleştiğinde gerçek zamanlı bildirimler alın.
---
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.
## Webhook oluştur
1. **Ayarlar → API'ler ve Webhook'lar → Webhook'lar**'a gidin
2. **+ Webhook oluştur**'a tıklayın
3. Webhook URL'nizi girin (herkese açık olarak erişilebilir olmalıdır)
4. **Kaydet**'e tıklayın
Webhook anında etkinleşir ve bildirim göndermeye başlar.
<VimeoEmbed videoId="928786708" title="Webhook oluşturma" />
### Webhook'ları Yönet
**Düzenle**: Webhook'u tıklayın → URL'yi güncelleyin → **Kaydet**
**Sil**: Webhook'u tıklayın → **Sil** → Onayla
## Etkinlikler
Twenty, şu olay türleri için webhook'lar gönderir:
| Etkinlik | Örnek |
| --------------------- | ---------------------------------------------------------- |
| **Kayıt Oluşturuldu** | `person.created`, `company.created`, `note.created` |
| **Kayıt Güncellendi** | `person.updated`, `company.updated`, `opportunity.updated` |
| **Kayıt Silindi** | `person.deleted`, `company.deleted` |
Tüm olay türleri webhook URL'nize gönderilir. Olay filtreleme özelliği ilerideki sürümlerde eklenebilir.
## Yük Biçimi
Her webhook, JSON gövdeli bir HTTP POST gönderir:
```json
{
"event": "person.created",
"data": {
"id": "abc12345",
"firstName": "Alice",
"lastName": "Doe",
"email": "alice@example.com",
"createdAt": "2025-02-10T15:30:45Z",
"createdBy": "user_123"
},
"timestamp": "2025-02-10T15:30:50Z"
}
```
| Alan | Açıklama |
| --------------- | --------------------------------------------- |
| `etkinlik` | Ne oldu (ör. `person.created`) |
| `veri` | Oluşturulan/güncellenen/silinen kaydın tamamı |
| `zaman damgası` | Olayın ne zaman gerçekleştiği (UTC) |
<Note>
Alındığını onaylamak için **2xx HTTP durumu** (200-299) ile yanıt verin. 2xx dışı yanıtlar teslimat hataları olarak kaydedilir.
</Note>
## Webhook Doğrulaması
Twenty, güvenlik için her webhook isteğini imzalar. İsteklerin gerçekliğini sağlamak için imzaları doğrulayın.
### Üstbilgiler
| Başlık | Açıklama |
| ---------------------------- | ------------------- |
| `X-Twenty-Webhook-Signature` | HMAC SHA256 imzası |
| `X-Twenty-Webhook-Timestamp` | İstek zaman damgası |
### Doğrulama Adımları
1. `X-Twenty-Webhook-Timestamp` değerinden zaman damgasını alın
2. Dizeyi oluşturun: `{timestamp}:{JSON payload}`
3. Webhook gizli anahtarınızı kullanarak HMAC SHA256 hesaplayın
4. `X-Twenty-Webhook-Signature` ile karşılaştırın
### Örnek (Node.js)
```javascript
const crypto = require("crypto");
const timestamp = req.headers["x-twenty-webhook-timestamp"];
const payload = JSON.stringify(req.body);
const secret = "your-webhook-secret";
const stringToSign = `${timestamp}:${payload}`;
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(stringToSign)
.digest("hex");
const isValid = expectedSignature === req.headers["x-twenty-webhook-signature"];
```
## Webhook'lar ve İş Akışları
| Yöntem | Yön | Kullanım Senaryosu |
| ---------------------------------- | --- | --------------------------------------------------------------------------- |
| **Webhook'lar** | OUT | Herhangi bir kayıt değişikliğini harici sistemlere otomatik olarak bildirin |
| **İş Akışı + HTTP İsteği** | OUT | Özel mantıkla (filtreler, dönüşümler) verileri dışarı gönderin |
| **İş Akışı Webhook Tetikleyicisi** | IN | Harici sistemlerden Twenty'ye veri alın |
Harici verileri almak için bkz. [Bir Webhook Tetikleyicisi Kurun](/l/tr/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger).

View File

@@ -0,0 +1,32 @@
---
title: Genişlet
description: Twenty'nin işlevselliğini API'ler, webhook'lar ve özel uygulamalarla genişletin.
---
<Frame>
<img src="/images/user-guide/integrations/plug.png" alt="AI" />
</Frame>
## Genel Bakış
Twenty, genişletilebilir olacak şekilde tasarlanmıştır. Mevcut araçlarınızla entegre olmak ve özel işlevler oluşturmak için API'lerimizi, webhook'larımızı ve uygulama çerçevemizi kullanın.
## Yapabilecekleriniz
* **API'ler**: CRM verilerinizi REST veya GraphQL kullanarak programatik olarak sorgulayın ve değiştirin
* **Webhook'lar**: Twenty'de olaylar gerçekleştiğinde gerçek zamanlı bildirimler alın
* **Uygulamalar**: Twenty'nin yeteneklerini genişleten özel uygulamalar oluşturun
## Başlarken
<CardGroup cols={2}>
<Card title="API'ler" icon="code" href="/l/tr/developers/extend/api">
Twenty'ye programatik olarak bağlanın
</Card>
<Card title="Webhooklar" icon="bell" href="/l/tr/developers/extend/webhooks">
Olaylardan gerçek zamanlı olarak haberdar olun
</Card>
<Card title="Uygulamalar" icon="puzzle-piece" href="/l/tr/developers/extend/apps/getting-started/quick-start">
Özelleştirmeleri kod olarak oluşturun
</Card>
</CardGroup>

View File

@@ -0,0 +1,189 @@
---
title: OAuth
icon: key
description: PKCE'li yetkilendirme kodu akışı ve sunucudan sunucuya erişim için istemci kimlik bilgileri.
---
Twenty, kullanıcıya dönük uygulamalar için yetkilendirme kodu + PKCE'yi ve sunucudan sunucuya erişim için istemci kimlik bilgilerini kullanarak OAuth 2.0'ı uygular. İstemciler [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) aracılığıyla dinamik olarak kaydedilir — bir kontrol panelinde manuel kurulum gerekmez.
## OAuth Ne Zaman Kullanılır
| Senaryo | Kimlik Doğrulama Yöntemi |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Dahili betikler, otomasyon | [API Anahtarı](/l/tr/developers/extend/api#authentication) |
| Bir kullanıcının adına hareket eden harici uygulama | **OAuth — Yetkilendirme Kodu** |
| Sunucudan sunucuya, kullanıcı bağlamı yok | **OAuth — İstemci Kimlik Bilgileri** |
| UI uzantılarına sahip Twenty Uygulaması | [Uygulamalar](/l/tr/developers/extend/apps/getting-started/quick-start) (OAuth otomatik olarak yönetilir) |
## Bir İstemci Kaydedin
Twenty, [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) uyarınca **dinamik istemci kaydını** destekler. Manuel kurulum gerekmez — programatik olarak kaydedin:
```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"
}
```
**Yanıt:**
```json
{
"client_id": "abc123",
"client_secret": "secret456",
"client_name": "My Integration",
"redirect_uris": ["https://myapp.com/callback"]
}
```
<Warning>
`client_secret` değerini güvenli bir şekilde saklayın — daha sonra geri alınamaz.
</Warning>
## Kapsamlar
| Kapsam | Erişim |
| --------- | ---------------------------------------------------------- |
| `api` | Core ve Metadata API'lerine tam okuma/yazma erişimi |
| `profile` | Kimliği doğrulanmış kullanıcının profil bilgilerini okuyun |
Kapsamları boşlukla ayrılmış bir dize olarak isteyin: `scope=api profile`
## Yetkilendirme Kodu Akışı
Uygulamanız bir Twenty kullanıcısı adına hareket ettiğinde bu akışı kullanın.
### 1. Kullanıcıyı yetkilendirmek için yönlendirin
```
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 | Kayıtlı istemci kimliğiniz |
| `response_type` | Evet | `code` olmalıdır |
| `redirect_uri` | Evet | Kayıtlı bir yönlendirme URI'siyle eşleşmelidir |
| `scope` | Hayır | Boşlukla ayrılmış kapsamlar (varsayılan: `api`) |
| `state` | Önerilen | CSRF saldırılarını önlemek için rastgele bir dize |
| `code_challenge` | Önerilen | PKCE challenge (doğrulayıcının SHA-256 karması, base64url ile kodlanmış) |
| `code_challenge_method` | Önerilen | PKCE kullanılırken `S256` olmalıdır |
Kullanıcı bir onay ekranı görür ve erişimi onaylar veya reddeder.
### 2. Geri dönüşü işleyin
Yetkilendirmeden sonra, Twenty `redirect_uri` adresinize geri yönlendirir:
```
https://myapp.com/callback?code=AUTH_CODE&state=random_state_value
```
`state` değerinin gönderdiğinizle eşleştiğini doğrulayın.
### 3. Kodu belirteçlerle değiş tokuş edin
```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
```
**Yanıt:**
```json
{
"access_token": "eyJhbG...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBh..."
}
```
### 4. Erişim belirtecini kullanın
```bash
GET /rest/companies
Authorization: Bearer ACCESS_TOKEN
```
### 5. Süresi dolduğunda yenileyin
```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
```
## İstemci Kimlik Bilgileri Akışı
Sunucudan sunucuya, kullanıcı etkileşimi olmayan entegrasyonlar için:
```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
```
Döndürülen belirteç, belirli bir kullanıcıya bağlı olmayan, çalışma alanı düzeyinde erişime sahiptir.
## Sunucu Keşfi
Twenty, OAuth yapılandırmasını standart bir keşif uç noktasında yayımlar:
```
GET /.well-known/oauth-authorization-server
```
Bu, tüm uç noktaları, desteklenen grant türlerini, kapsamları ve yetenekleri döndürür — genel OAuth istemcileri oluşturmak için kullanışlıdır.
## API Uç Noktaları Özeti
| Uç nokta | Amaç |
| ----------------------------------------- | ----------------------------- |
| `/.well-known/oauth-authorization-server` | Sunucu üstverisi keşfi |
| `/oauth/register` | Dinamik istemci kaydı |
| `/oauth/authorize` | Kullanıcı yetkilendirmesi |
| `/oauth/token` | Belirteç değişimi ve yenileme |
| Ortam | Temel URL |
| ---------------------------- | ------------------------ |
| **Bulut** | `https://api.twenty.com` |
| **Kendi Kendine Barındırma** | `https://{your-domain}` |
## OAuth ve API Anahtarları
| | API Anahtarları | OAuth |
| ------------------------- | -------------------------- | -------------------------------------------------- |
| **Kurulum** | Ayarlar'da oluşturun | Bir istemci kaydedin, akışı uygulayın |
| **Kullanıcı bağlamı** | Yok (çalışma alanı düzeyi) | Belirli bir kullanıcının izinleri |
| **En uygun** | Betikler, dahili araçlar | Harici uygulamalar, çok kullanıcılı entegrasyonlar |
| **Belirteç döndürme** | Manuel | Yenileme belirteçleri aracılığıyla otomatik olarak |
| **Kapsama dayalı erişim** | Tam API erişimi | Kapsamlar aracılığıyla ayrıntılı |

View File

@@ -0,0 +1,117 @@
---
title: Webhook'lar
icon: satellite-dish
description: Kayıtlar değiştiğinde bildirim alın — her oluşturma, güncelleme veya silme işleminde uç noktanıza HTTP POST gönderilir.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
Bir kayıt oluşturulduğunda, güncellendiğinde veya silindiğinde Twenty URL'inize bir HTTP POST gönderir. Özel nesneler dahil tüm nesne türleri kapsanır.
## Webhook oluştur
1. **Ayarlar → API'ler ve Webhook'lar → Webhook'lar**'a gidin
2. **+ Webhook oluştur**'a tıklayın
3. Webhook URL'nizi girin (herkese açık olarak erişilebilir olmalıdır)
4. **Kaydet**'e tıklayın
Webhook anında etkinleşir ve bildirim göndermeye başlar.
<VimeoEmbed videoId="928786708" title="Webhook oluşturma" />
### Webhook'ları Yönet
**Düzenle**: Webhook'u tıklayın → URL'yi güncelleyin → **Kaydet**
**Sil**: Webhook'u tıklayın → **Sil** → Onayla
## Etkinlikler
Twenty, şu olay türleri için webhook'lar gönderir:
| Etkinlik | Örnek |
| --------------------- | ---------------------------------------------------------- |
| **Kayıt Oluşturuldu** | `person.created`, `company.created`, `note.created` |
| **Kayıt Güncellendi** | `person.updated`, `company.updated`, `opportunity.updated` |
| **Kayıt Silindi** | `person.deleted`, `company.deleted` |
Tüm olay türleri webhook URL'nize gönderilir. Olay filtreleme özelliği ilerideki sürümlerde eklenebilir.
## Yük Biçimi
Her webhook, JSON gövdeli bir HTTP POST gönderir:
```json
{
"event": "person.created",
"data": {
"id": "abc12345",
"firstName": "Alice",
"lastName": "Doe",
"email": "alice@example.com",
"createdAt": "2025-02-10T15:30:45Z",
"createdBy": "user_123"
},
"timestamp": "2025-02-10T15:30:50Z"
}
```
| Alan | Açıklama |
| ----------- | --------------------------------------------- |
| `event` | Ne oldu (ör. `person.created`) |
| `data` | Oluşturulan/güncellenen/silinen kaydın tamamı |
| `timestamp` | Olayın ne zaman gerçekleştiği (UTC) |
<Note>
Alındığını onaylamak için **2xx HTTP durumu** (200-299) ile yanıt verin. 2xx dışı yanıtlar teslimat hataları olarak kaydedilir.
</Note>
## Webhook Doğrulaması
Twenty, güvenlik için her webhook isteğini imzalar. İsteklerin gerçekliğini sağlamak için imzaları doğrulayın.
### Üstbilgiler
| Başlık | Açıklama |
| ---------------------------- | ------------------- |
| `X-Twenty-Webhook-Signature` | HMAC SHA256 imzası |
| `X-Twenty-Webhook-Timestamp` | İstek zaman damgası |
### Doğrulama Adımları
1. `X-Twenty-Webhook-Timestamp` değerinden zaman damgasını alın
2. Dizeyi oluşturun: `{timestamp}:{JSON payload}`
3. Webhook gizli anahtarınızı kullanarak HMAC SHA256 hesaplayın
4. `X-Twenty-Webhook-Signature` ile karşılaştırın
### Örnek (Node.js)
```javascript
const crypto = require("crypto");
const timestamp = req.headers["x-twenty-webhook-timestamp"];
const payload = JSON.stringify(req.body);
const secret = "your-webhook-secret";
const stringToSign = `${timestamp}:${payload}`;
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(stringToSign)
.digest("hex");
const receivedSignature = req.headers["x-twenty-webhook-signature"];
const isValid = crypto.timingSafeEqual(
Buffer.from(expectedSignature, "hex"),
Buffer.from(receivedSignature, "hex")
);
```
## Webhook'lar ve İş Akışları
| Yöntem | Yön | Kullanım Senaryosu |
| ---------------------------------- | --- | --------------------------------------------------------------------------- |
| **Webhook'lar** | OUT | Herhangi bir kayıt değişikliğini harici sistemlere otomatik olarak bildirin |
| **İş Akışı + HTTP İsteği** | OUT | Özel mantıkla (filtreler, dönüşümler) verileri dışarı gönderin |
| **İş Akışı Webhook Tetikleyicisi** | IN | Harici sistemlerden Twenty'ye veri alın |
Harici verileri almak için bkz. [Bir Webhook Tetikleyicisi Kurun](/l/tr/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger).