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,22 @@
---
title: En İyi Uygulamalar
---
Bu belge, arka uçta çalışırken uymanız gereken en iyi uygulamaları açıklar.
## Modüler bir yaklaşım izleyin
Arka uç, NestJS ile çalışırken temel bir ilke olan modüler bir yaklaşımı benimser. Kodunuzu temiz ve düzenli bir kod tabanı sağlamak için yeniden kullanılabilir modüllere ayırdığınızdan emin olun.
Her modül belirli bir özellik veya işlevselliği kapsamalı ve iyi tanımlanmış bir kapsama sahip olmalıdır. Bu modüler yaklaşım, sorumlulukların net bir şekilde ayrılmasını sağlar ve gereksiz karmaşıklıkları ortadan kaldırır.
## Modüllerde kullanılmak üzere servisleri dışa açın
Her zaman kod okunabilirliğini ve bakım yapılabilirliğini artıran, net ve tek bir sorumluluğa sahip hizmetler oluşturun. Hizmetlerin adlarını açıklayıcı ve tutarlı bir şekilde adlandırın.
Diğer modüllerde kullanmak istediğiniz hizmetleri dışa açmalısınız. Hizmetleri diğer modüllere dışa açmak, NestJS'nin güçlü bağımlılık enjeksiyonu sistemi sayesinde mümkündür ve bileşenler arasında gevşek bağlılığı teşvik eder.
## `any` tipini kullanmaktan kaçının
Bir değişkeni `any` olarak tanımladığınızda, TypeScript'in tür denetleyicisi herhangi bir tür denetimi yapmaz ve değişkene herhangi bir türde değer atamanızı mümkün kılar. TypeScript, değere göre değişkenin türünü belirlemek için tür çıkarımı kullanır. `any` olarak tanımlandığında, TypeScript türü artık çıkaramaz. Bu, geliştirme sırasında türle ilgili hataları yakalamayı zorlaştırır, çalışma zamanı hatalarına yol açar ve kodun bakım yapılabilirliğini, güvenilirliğini ve başkaları tarafından anlaşılabilirliğini azaltır.
Bu nedenle her şeyin bir türü olması gerekir. Bu yüzden, ad ve soyadı alanları olan yeni bir nesne oluşturuyorsanız, nesnenin yapısını tanımlayan ve ad ile soyadı alanlarını içeren bir arayüz veya tür tanımlamalısınız.

View File

@@ -0,0 +1,39 @@
---
title: Özel Nesneler
---
Nesneler, bir kuruluşa özgü verileri (kayıtlar, nitelikler ve değerler) saklamanıza olanak tanır. Twenty, hem standart hem de özel nesneler sağlar.
Standart nesneler, tüm kullanıcılar için kullanılabilir bir dizi niteliğe sahip yerleşik nesnelerdir. Twenty'deki standart nesne örnekleri arasında Şirket ve Kişi yer alır. Standart nesneler, tüm Twenty kullanıcıları için de mevcut olan standart alanlara sahiptir, örneğin Şirket.görünenAd.
Özel nesneler, kuruluşunuza özgü bilgileri depolamak için oluşturabileceğiniz nesnelerdir. Yerleşik değildir; çalışma alanınızdaki üyeler, standart nesnelerin uygun olmadığı bilgileri tutmak için özel nesneler oluşturabilir ve özelleştirebilir.
## Üst Düzey Şema
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="Üst Düzey Şema" />
</div>
<br />
## Nasıl Çalışır
Özel nesneler, nesnelerin şekli, adı ve türünü belirleyen meta veri tablolarından gelir. Tüm bu bilgiler, tablolar içeren meta veri şema veritabanında bulunur:
* **VeriKaynağı**: Verinin nerede bulunduğuna dair detaylar.
* **Nesne**: Nesneyi tarif eder ve bir Veri Kaynağı'na bağlanır.
* **Alan**: Bir Nesnenin alanlarını özetler ve Nesneye bağlanır.
Özel bir nesne eklemek için, çalışma alanı üyesi /metadata API'sini sorgulayacaktır. Bu, meta verileri uygun şekilde günceller ve meta verilere dayanarak bir GraphQL şeması hesaplar ve bunu daha sonra kullanmak üzere bir GQL önbelleğine kaydeder.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/add-custom-objects.jpeg" alt="Özel nesneler eklemek için /metadata API'sini sorgulayın" />
</div>
<br />
Veri almak için süreç, /graphql uçnoktasına sorgular yapmayı ve bunları Sorgu Çözücü'den geçirmeyi içerir.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="/graphql uçnoktasını sorgulayarak verileri alın" />
</div>

View File

@@ -0,0 +1,46 @@
---
title: Özellik Bayrakları
---
Özellik bayrakları deneysel özellikleri gizlemek için kullanılır. Twenty için, çalışma alanı seviyesinde ayarlanırlar, kullanıcı seviyesinde değil.
## Yeni bir özellik bayrağı ekleme
`FeatureFlagKey.ts` dosyasında özellik bayrağını ekleyin:
```ts
type FeatureFlagKey =
| 'IS_FEATURENAME_ENABLED'
| ...;
```
Ayrıca `feature-flag.entity.ts` enumuna ekleyin:
```ts
enum FeatureFlagKeys {
IsFeatureNameEnabled = 'IS_FEATURENAME_ENABLED',
...
}
```
Bir **backend** özelliği üzerinde bir özellik bayrağı uygulamak için kullanın:
```ts
@Gate({
featureFlag: 'IS_FEATURENAME_ENABLED',
})
```
Bir **frontend** özelliği üzerinde bir özellik bayrağı uygulamak için kullanın:
```ts
const isFeatureNameEnabled = useIsFeatureEnabled('IS_FEATURENAME_ENABLED');
```
## Dağıtım için özellik bayraklarını yapılandırın
`core.featureFlag` Tablosundaki ilgili kaydı değiştirin:
| kimlik | anahtar | çalışma alanı kimliği | değer |
| -------- | ------------------------ | --------------------- | ------- |
| Rastgele | `IS_FEATURENAME_ENABLED` | Çalışma Alanı kimliği | `doğru` |

View File

@@ -0,0 +1,125 @@
---
title: Klasör Mimarisi
info: Sunucu klasör mimarimize detaylı bir bakış
---
Arka uç dizin yapısı aşağıdaki gibidir:
```
server
└───ability
└───constants
└───core
└───database
└───decorators
└───filters
└───guards
└───health
└───integrations
└───metadata
└───workspace
└───utils
```
## Yetkiler
İzinleri tanımlar ve her bir varlık için işleyiciler içerir.
## Dekoratörler
Ek işlevsellik için NestJS'te özel dekoratörleri tanımlar.
Daha fazla bilgi için [özel dekoratörler](https://docs.nestjs.com/custom-decorators) sayfasına bakın.
## Filtreler
GraphQL uç noktalarında oluşabilecek istisnaları ele almak için istisna filtreleri içerir.
## Guard'lar
Daha fazla bilgi için [guard'lar](https://docs.nestjs.com/guards) sayfasına bakın.
## Sağlık
Veritabanının beklendiği gibi çalışıp çalışmadığını doğrulamak için JSON döndüren, herkese açık bir REST API'si (healthz) içerir.
## Meta Veriler
Özel nesneleri tanımlar ve bir GraphQL API'sini (graphql/metadata) kullanıma sunar.
## İş Alanı
Meta verilere dayalı özel GraphQL şeması oluşturur ve sunar.
### Çalışma Alanı Dizin Yapısı
```
workspace
└───workspace-schema-builder
└───factories
└───graphql-types
└───database
└───interfaces
└───object-definitions
└───services
└───storage
└───utils
└───workspace-resolver-builder
└───factories
└───interfaces
└───workspace-query-builder
└───factories
└───interfaces
└───workspace-query-runner
└───interfaces
└───utils
└───workspace-datasource
└───workspace-manager
└───workspace-migration-runner
└───utils
└───workspace.module.ts
└───workspace.factory.spec.ts
└───workspace.factory.ts
```
Çalışma alanı dizininin kökünde, `createGraphQLSchema` fonksiyonunu içeren `workspace.factory.ts` dosyası bulunur. Bu fonksiyon, meta verileri kullanarak her bir çalışma alanına özgü bir şema üretir. Şema ve resolver oluşturmayı ayırarak, bu ayrık bileşenleri birleştiren `makeExecutableSchema` fonksiyonunu kullanırız.
Bu strateji sadece düzenle ilgili değildir; aynı zamanda performans ve ölçeklenebilirliği artırmak için oluşturulan tür tanımlarının önbelleğe alınması gibi optimizasyonlara da yardımcı olur.
### Çalışma Alanı Şema Oluşturucu
GraphQL şemasını oluşturur ve şunları içerir:
#### Factory'ler:
GraphQL ile ilgili yapıları oluşturmak için özelleşmiş kurucular.
* type.factory, alan meta verilerini `TypeMapperService` kullanarak GraphQL türlerine dönüştürür.
* type-definition.factory, `objectMetadata` temel alınarak GraphQL girdi veya çıktı nesneleri oluşturur.
#### GraphQL Türleri
Numaralandırmalar, girdiler, nesneler ve skalerleri içerir ve şema oluşturmanın yapı taşları olarak hizmet eder.
#### Arayüzler ve Nesne Tanımları
GraphQL varlıkları için şablonları içerir ve `MONEY` veya `URL` gibi hem önceden tanımlı hem de özel türleri kapsar.
#### Servisler
FieldMetadataType ile uygun GraphQL skalerini veya sorgu değiştiricilerini ilişkilendirmekten sorumlu servisi içerir.
#### Depolama
Yeniden kullanılabilir tür tanımlarını barındıran ve GraphQL türlerinin yinelenmesini önleyen `TypeDefinitionsStorage` sınıfını içerir.
### Çalışma Alanı Resolver Oluşturucu
GraphQL şemasında sorgulama ve mutasyon için resolver fonksiyonları oluşturur.
Bu dizindeki her bir factory, çeşitli tablolar genelinde uyarlanabilir kullanım için tasarlanmış `FindManyResolverFactory` gibi, farklı bir resolver türü üretmekten sorumludur.
### Çalışma Alanı Sorgu Çalıştırıcı
Veritabanında oluşturulan sorguları çalıştırır ve sonucu çözümleyip getirir.

View File

@@ -0,0 +1,41 @@
---
title: Mesaj Kuyruğu
---
Kuyruklar, asenkron işlemlerin gerçekleştirilmesine imkan tanır. Kayıt esnasında hoş geldin e-postası göndermek gibi arka plan görevleri gerçekleştirmek için kullanılabilirler.
Her kullanım durumu, `MessageQueueServiceBase`'den genişletilmiş kendi kuyruk sınıfına sahip olacaktır.
Şu anda yalnızca kuyruk sürücüsü olarak `bull-mq`[bull-mq](https://bullmq.io/) destekliyoruz.
## Yeni bir kuyruk oluşturma ve kullanma adımları
1. Yeni kuyruğunuz için enum `MESSAGE_QUEUES` altında bir kuyruk adı ekleyin.
2. Kuyruğun değişken adını bağımlılık belirteci olarak kullanarak kuyruk fabrikası uygulamasını sağlayın.
3. Oluşturduğunuz kuyruğu, gerekli modüle/hizmete kuyruk adıyla birlikte bağımlılık belirteci kullanarak enjekte edin.
4. Üretici gibi token tabanlı enjeksiyon ile çalışan sınıfını ekleyin.
### Örnek kullanım
```ts
class Resolver {
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {}
async onSomeAction() {
//business logic
await this.queue.add(someData);
}
}
//async worker
class CustomWorker {
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {
this.initWorker();
}
async initWorker() {
await this.queue.work(async ({ id, data }) => {
//worker logic
});
}
}
```

View File

@@ -0,0 +1,87 @@
---
title: Backend Komutları
icon: terminal
---
## Faydalı Komutlar
Bu komutlar packages/twenty-server klasöründen çalıştırılmalıdır.
Diğer herhangi bir klasörden `npx nx {command} twenty-server` (veya `npx nx run twenty-server:{command}`) çalıştırabilirsiniz.
### İlk Kurulum
```
npx nx database:reset twenty-server # setup the database with dev seeds
```
### Sunucuyu Başlatma
```
npx nx run twenty-server:start
```
### Kod Temizleme
```
npx nx run twenty-server:lint # pass --fix to fix lint errors
```
### Test
```
npx nx run twenty-server:test:unit # run unit tests
npx nx run twenty-server:test:integration # run integration tests
```
Not: Entegrasyon testlerini çalıştırmadan önce veritabanını sıfırlamanız gerekirse `npx nx run twenty-server:test:integration:with-db-reset` komutunu kullanabilirsiniz.
### Veritabanını Sıfırlama
Veritabanını sıfırlamak ve tohum yüklemek istiyorsanız, aşağıdaki komutu kullanabilirsiniz:
```bash
npx nx run twenty-server:database:reset
```
### Geçişler
#### Core/Metadata şemalarında (TypeORM) nesneler için
```bash
npx nx run twenty-server:database:migrate:generate
```
## Teknoloji Yığını
Twenty, arka uç için öncelikle NestJS kullanır.
Prisma, kullandığımız ilk ORM idi. Ancak, kullanıcıların özel alanlar ve özel nesneler oluşturmasına izin vermek için, ince ayar kontrolü gerektirdiği için alt düzey bir seviye daha mantıklı geldi. Proje şu anda TypeORM kullanmaktadır.
İşte teknoloji yığını artık böyle görünüyor.
**Çekirdek**
* [NestJS](https://nestjs.com/)
* [TypeORM](https://typeorm.io/)
* [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server)
**Veritabanı**
* [Postgres](https://www.postgresql.org/)
**Üçüncü Şahıs Entegrasyonları**
* [Sentry](https://sentry.io/welcome/) hataları izlemek için
**Test**
* [Jest](https://jestjs.io/)
**Araçlar**
* [Yarn](https://yarnpkg.com/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
**Geliştirme**
* [AWS EKS](https://aws.amazon.com/eks/)

View File

@@ -0,0 +1,85 @@
---
title: Zapier Uygulaması
---
Zapier'i kullanarak Twenty'yi 3000+ uygulama ile zahmetsizce senkronize edin. Görevleri otomatikleştirin, verimliliği artırın ve müşteri ilişkilerinizi güçlendirin!
## Zapier Hakkında
Zapier, ekibinizin her gün kullandığı uygulamaları bağlayarak iş akışlarını otomatikleştirmenize olanak tanıyan bir araçtır. Zapier'in temel konsepti, Zaps olarak adlandırılan otomasyon iş akışlarıdır ve tetikleyiciler ile işlemleri içerir.
Zapier'in nasıl çalıştığı hakkında daha fazla bilgi edinebilirsiniz [burada](https://zapier.com/how-it-works).
## Kurulum
### Adım 1: Zapier paketlerini yükleyin
```bash
cd packages/twenty-zapier
yarn
```
### Adım 2: CLI ile giriş yapın
Zapier kimlik bilgilerinizi kullanarak CLI ile oturum açın:
```bash
zapier login
```
### Adım 3: Çevre değişkenlerini ayarlayın
`packages/twenty-zapier` klasöründen şunu çalıştırın:
```bash
cp .env.example .env
```
Uygulamayı yerel ortamda çalıştırın, [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks) adresine gidin ve bir API anahtarı oluşturun.
Yeni oluşturduğunuz API anahtarı ile `.env` dosyasındaki **YOUR_API_KEY** değerini değiştirin.
## Geliştirme
<Warning>
Herhangi bir `zapier` komutundan önce `yarn build` çalıştırdığınızdan emin olun.
</Warning>
### Test
```bash
yarn test
```
### Lint
```bash
yarn format
```
### Kod düzenlerken izle ve derle
```bash
yarn watch
```
### Zapier uygulamanızı doğrulayın
```bash
yarn validate
```
### Zapier uygulamanızı dağıtın
```bash
yarn deploy
```
### Tüm Zapier CLI komutlarını listeleyin
```bash
zapier
```

View File

@@ -0,0 +1,79 @@
---
title: Hatalar, İstekler ve Çekme İstekleri
icon: bug
info: Sorunları bildirin, özellik talep edin ve koda katkıda bulunun
---
## Hataları Bildirme
Bir hatayı bildirmek için lütfen [GitHub'da bir sorun oluşturun](https://github.com/twentyhq/twenty/issues/new).
[Discord'da](https://discord.gg/cx5n4Jzs57) da yardım isteyebilirsiniz.
## Özellik İstekleri
Eğer bunun bir hata olmadığından emin değilseniz ve bir özellik isteğine daha yakın olduğunu düşünüyorsanız, muhtemelen [bunun yerine bir tartışma başlatmalısınız](https://github.com/twentyhq/twenty/discussions/new).
## Çekme İsteği Gönderin
Twenty'ye koda katkıda bulunmak bir çekme isteği (PR) ile başlar.
### Başlamadan Önce
1. İlgili çalışmalar için [mevcut sorunları](https://github.com/twentyhq/twenty/issues) kontrol edin
2. Yeni özellikler için, tartışmak üzere önce bir sorun açın
3. [Davranış Kuralları](https://github.com/twentyhq/twenty/blob/main/.github/CODE_OF_CONDUCT.md) belgemizi inceleyin
### Fork'layın ve Klonlayın
1. Depoyu GitHub'da fork'layın
2. Fork'unuzu klonlayın:
```bash
git clone https://github.com/YOUR_USERNAME/twenty.git
cd twenty
```
3. Upstream uzak depoyu ekleyin:
```bash
git remote add upstream https://github.com/twentyhq/twenty.git
```
### Bir Dal Oluşturun
```bash
git checkout -b feature/your-feature-name
```
Açıklayıcı dal adları kullanın:
* `feature/add-export-button`
* `fix/login-redirect-issue`
* `docs/update-api-guide`
### Değişikliklerinizi Yapın
1. Temiz, iyi belgelenmiş kod yazın
2. Mevcut kod stilini izleyin
3. Yeni işlevsellik için testler ekleyin
4. Gerektiğinde belgeleri güncelleyin
### PR'nizi Gönderin
1. Dalınızı gönderin:
```bash
git push origin feature/your-feature-name
```
2. GitHub'da bir PR açın
3. PR şablonunu doldurun
4. İlgili sorunları ilişkilendirin
### PR Kontrol Listesi
* [ ] Kod, proje stil yönergelerine uyuyor
* [ ] Testler yerelde geçiyor
* [ ] Belgeler güncellendi
* [ ] PR açıklaması değişiklikleri açıklar

View File

@@ -0,0 +1,329 @@
---
title: En İyi Uygulamalar
icon: star
---
Bu belge, ön yüz üzerinde çalışırken takip etmeniz gereken en iyi uygulamaları özetler.
## Durum Yönetimi
React ve Jotai, kod tabanında durumu yönetir.
### Durumu depolamak için Jotai atomlarını kullanın
Durumunuzu depolamak için ihtiyaç duyduğunuz kadar atom oluşturmak iyi bir uygulamadır.
<Warning>
Prop drilling ile gereğinden fazla sade olmaya çalışmaktansa, fazladan atom kullanmak daha iyidir.
</Warning>
```tsx
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
export const myAtomState = createAtomState<string>({
key: 'myAtomState',
defaultValue: 'default value',
});
export const MyComponent = () => {
const [myAtom, setMyAtom] = useAtomState(myAtomState);
return (
<div>
<input
value={myAtom}
onChange={(e) => setMyAtom(e.target.value)}
/>
</div>
);
}
```
### Durum saklamak için `useRef` kullanmayın.
Durum saklamak için `useRef` kullanmaktan kaçının.
Durum saklamak istiyorsanız, `useState` veya `useAtomState` ile Jotai atomlarını kullanmalısınız.
Bazı yeniden render edilmelerin olmasını önlemek için `useRef`'e ihtiyacınız varmış gibi hissediyorsanız, [yeniden render yönetimi](#managing-re-renders) konusuna bakın.
## Yeniden render yönetimi
React'ta yeniden render'ları yönetmek zor olabilir.
Gereksiz yeniden render'ları önlemek için izlenecek bazı kurallar burada.
Yeniden render'ları **her zaman** önleyebileceğinizi unutmayın, olayların nedenini anlayarak.
### Kök seviyesinde çalışın
Yeni özelliklerde yeniden render'ları önlemek, onları kök seviyesinde ortadan kaldırarak artık kolaylaştı.
`PageChangeEffect` yan bileşeni, sayfa değişikliği sırasında yürütülecek tüm mantığı içeren tek bir `useEffect` barındırır.
Bu şekilde, yalnızca bir yerin yeniden render tetikleyebileceğini bilirsiniz.
### Kod tabanına `useEffect` eklemeden önce iki kez düşünün.
Yeniden render'lar çoğunlukla gereksiz `useEffect`'lerden kaynaklanır.
`useEffect`'e ihtiyacınız olup olmadığını düşünmelisiniz ya da mantığı bir olay işleyici fonksiyonuna taşıyabilirsiniz.
Genellikle mantığı `handleClick` veya `handleChange` gibi fonksiyonlara taşımayı kolay bulursunuz.
Bunları `onCompleted`, `onError`, vb. gibi Apollo kitaplıklarında da bulabilirsiniz.
### `useEffect` veya veri çekme mantığını çıkarmak için bir yan bileşen kullanın.
Kök bileşenize bir `useEffect` eklemeniz gerektiğini hissediyorsanız, bunu bir yan bileşene çıkarmayı düşünmelisiniz.
Aynısını Apollo kancaları ile veri çekme mantığı için de uygulayabilirsiniz.
```tsx
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <div>{data}</div>;
};
export const App = () => (
<PageComponent />
);
```
```tsx
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
return <div>{data}</div>;
};
export const PageData = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <></>;
};
export const App = () => (
<>
<PageData />
<PageComponent />
</>
);
```
### Atom aile durumlarını ve seçimcilerini kullanın
Atom aile durumları ve seçimcileri, yeniden render'ları önlemenin harika bir yoludur.
Bir öğe listesini saklamanız gerektiğinde kullanılabilirdir.
### `React.memo(MyComponent)` kullanmamalısınız.
`React.memo()` kullanmaktan kaçının çünkü bu yeniden render'ın nedenini çözmez, bunun yerine yeniden render zincirini kırar, bu da beklenmeyen davranışlara yol açabilir ve kodun yeniden faktör edilmesini çok zor hale getirebilir.
### `useCallback` veya `useMemo` kullanımını sınırlayın.
Genellikle gerekli değildir ve performans kazanımı farkedilemeyecek kadar az olduğu için kodun okunmasını ve bakımını zorlaştırır.
## Console.log'lar
`console.log` ifadeleri geliştirirken değerlidir, değişken değerleri ve kod akışı hakkında gerçek zamanlı yanıtlar sağlar. Ancak, üretim kodunda bırakmak çeşitli sorunlara yol açabilir:
1. **Performans**: Aşırı loglama, özellikle istemci tarafı uygulamalarda çalışma zamanı performansını etkileyebilir.
2. **Güvenlik**: Hassas verileri loglama, tarayıcı konsolunu inceleyen herkes için kritik bilgilerin açığa çıkmasına neden olabilir.
3. **Temizlik**: Konsolu loglarla doldurmak, geliştiricilerin veya araçların görmesi gereken önemli uyarıları veya hataları gizleyebilir.
4. **Profesyonellik**: Konsolu kontrol eden son kullanıcılar veya müşteriler, birçok log ifadesi gördüğünde kodun kalitesini ve detaycılığını sorgulayabilir.
Kodunuzu üretime taşımadan önce tüm `console.log`ları kaldırdığınızdan emin olun.
## İsimlendirme
### Değişken Adlandırma
Değişken adları değişkenin amacını veya işlevini doğru bir şekilde tanımlamalıdır.
#### Genel adlar sorunu
Programlamada genel adlar ideal değildir çünkü kesinlik eksikliği nedeniyle belirsizlik ve kod okunabilirliğinin azalmasına neden olabilir. Bu tür adlar, değişkenin veya fonksiyonun amacını iletmede başarısız olur, geliştiricilerin kodun amacını daha derin bir araştırma yapmadan anlamalarını zorlaştırır. Bu, artan hata ayıklama süresi, hatalara eğilimli olma ve bakım ve işbirliği zorluklarına yol açabilir. Öte yandan, açıklayıcı adlandırma, kodu kendi kendini açıklayıcı hale getirir ve gezilebilirliği artırarak kod kalitesini ve geliştirici verimliliğini artırır.
```tsx
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
#### Değişken adlarında kaçınılması gereken bazı kelimeler
* kukla
### Olay işleyicileri
Olay işleyici adları `handle` ile başlamalı, bileşen props'larında olayları adlandırmak için `on` bir ön ek olarak kullanılmaktadır.
```tsx
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
```
## Opsiyonel Props
Opsiyonel bir prop için varsayılan değeri geçmekten kaçının.
**ÖRNEK**
Aşağıda tanımlanan `EmailField` bileşeni alın:
```tsx
type EmailFieldProps = {
value: string;
disabled?: boolean;
};
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
<TextInput value={value} disabled={disabled} fullWidth />
);
```
**Kullanım**
```tsx
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
```
```tsx
// ✅ Good, assumes the default value
const Form = () => <EmailField value="username@email.com" />;
```
## Bileşenleri props olarak geçirme
Mümkün olduğunca, bileşenleri kendi içinde istemsiz geçen bileşenler olarak gönderin, böylece çocuklar hangi props'ları geçmeleri gerektiğine kendileri karar verebilirler.
Bunun en yaygın örneği simge bileşenleridir:
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
return (
<div>
<MyIcon size={theme.icon.size.md}>
</div>
)
};
```
React'in bir bileşeni bileşen olarak anlaması için PascalCase kullanmalısınız, böylece daha sonra `<MyIcon>` ile örnekleyebilirsiniz.
## Prop Taşıma: Minimumda Tutun
React bağlamında prop taşıma, durum değişkenlerini ve ayarlayıcılarını, aracı bileşenler kullanmasalar bile birçok bileşen katmanı aracılığıyla aktarma uygulamasına atıfta bulunur. Bazen gerekli olmakla birlikte, aşırı prop taşıma şu durumlara yol açabilir:
1. **Azalan Okunabilirlik**: Bir prop'un nereden kaynaklandığını veya nerede kullanıldığını izlemek, derin bir şekilde iç içe geçmiş bir bileşen yapısında karışık hale gelebilir.
2. **Bakım Zorlukları**: Bir bileşenin prop yapısındaki değişiklikler, prop'u doğrudan kullanmasalar bile, birden fazla bileşende ayarlamalar yapılmasını gerektirebilir.
3. **Azalan Bileşen Yeniden Kullanılabilirliği**: Çok fazla props'u yalnızca aşağıya aktarma amacıyla alan bir bileşen, daha az genel amaçlı hale gelir ve farklı bağlamlarda yeniden kullanılması zorlaşır.
Aşırı prop taşıma kullanıyorsanız, [durum yönetimi en iyi uygulamalarına](#state-management) bakın.
## İçe Aktarımlar
İçe aktarırken, tüm veya göreli yolları belirtmek yerine belirlenen takma adları tercih edin.
**Takma Adlar**
```js
{
alias: {
"~": path.resolve(__dirname, "src"),
"@": path.resolve(__dirname, "src/modules"),
"@testing": path.resolve(__dirname, "src/testing"),
},
}
```
**Kullanım**
```tsx
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
import {
ComponentDecorator
} from '../../../../../testing/decorators/ComponentDecorator';
```
```tsx
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
## Şema Doğrulama
[Zod](https://github.com/colinhacks/zod), tiplenmemiş nesneler için bir şema doğrulayıcıdır:
```js
const validationSchema = z
.object({
exist: z.boolean(),
email: z
.string()
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
type Form = z.infer<typeof validationSchema>;
```
## Geriye Dönük Uyumsuz Değişiklikler
Testler henüz kapsamlı bir şekilde entegre edilmediği için, ilerlemeden önce yapılan değişikliklerin başka yerlerde bozulmalara neden olmadığından emin olmak için her zaman kapsamlı manuel testler yapın.

View File

@@ -0,0 +1,110 @@
---
title: Klasör Mimarisi
icon: folder-tree
info: Klasör mimarimize detaylı bir bakış
---
Bu kılavuzda, proje dizin yapısının ayrıntılarını ve bunun Twenty'nin düzenine ve bakım kolaylığına nasıl katkıda bulunduğunu inceleyeceksiniz.
Bu klasör mimarisi kuralını izleyerek, belirli özelliklerle ilgili dosyaları bulmak kolaylaşır ve uygulamanın ölçeklenebilir ve bakımı yapılabilir olması sağlanır.
```
front
└───modules
│ └───module1
│ │ └───submodule1
│ └───module2
│ └───ui
│ │ └───display
│ │ └───inputs
│ │ │ └───buttons
│ │ └───...
└───pages
└───...
```
## Sayfalar
Uygulama rotaları tarafından tanımlanan üst düzey bileşenleri içerir. Bunlar, daha alt düzey bileşenleri modules klasöründen içe aktarırlar (daha fazla ayrıntı aşağıda).
## Modüller
Her modül, kendine özgü bileşenlerini, durumlarını ve iş mantığını içeren bir özellik ya da özellik grubu temsil eder.
Hepsi aşağıdaki yapıyı takip etmelidir. Modüllerin içine modüller (alt modüller) yerleştirebilirsiniz ve aynı kurallar uygulanır.
```
module1
└───components
│ └───component1
│ └───component2
└───constants
└───contexts
└───graphql
│ └───fragments
│ └───queries
│ └───mutations
└───hooks
│ └───internal
└───states
│ └───selectors
└───types
└───utils
```
### Bağlamlar
Bağlam, verileri her seviyede prop'ları elle aktarmak zorunda kalmadan bileşen ağacından aşağıya iletmenin bir yoludur.
Daha fazla bilgi için [React Context](https://react.dev/reference/react#context-hooks) sayfasına bakın.
### GraphQL
Parçalar, sorgular ve mutasyonları içerir.
Daha fazla bilgi için [GraphQL](https://graphql.org/learn/) sayfasına bakın.
* Parçalar
Bir parça, farklı yerlerde kullanabileceğiniz, bir sorgunun yeniden kullanılabilir bir parçasıdır. Parçaları kullanarak, kodun kopyalanmasını önlemek daha kolaydır.
Daha fazla bilgi için [GraphQL Parçaları](https://graphql.org/learn/queries/#fragments) sayfasına bakın.
* Sorgular
Daha fazla bilgi için [GraphQL Sorgular](https://graphql.org/learn/queries/) sayfasına bakın.
* Mutasyonlar
Daha fazla bilgi için [GraphQL Mutasyonlar](https://graphql.org/learn/queries/#mutations) sayfasına bakın.
### Kancalar
Daha fazla bilgi için [Kancalar](https://react.dev/learn/reusing-logic-with-custom-hooks) sayfasına bakın.
### Durumlar
Durum yönetim mantığını içerir. [Jotai](https://jotai.org) bunu ele almaktadır.
* Seçiciler: Türetilmiş atomlar (`createAtomSelector` kullanılarak) diğer atomlardan değerleri hesaplar ve otomatik olarak önbelleğe alınır.
React'ın yerleşik durum yönetimi, bir bileşen içinde hala durumu ele alır.
### Yardımcılar
Sadece yeniden kullanılabilir saf fonksiyonları içermelidir. Aksi takdirde, `hooks` klasöründe özel kancalar oluşturun.
## Kullanıcı Arayüzü
Uygulamada kullanılan tüm tekrar kullanılabilir UI bileşenlerini içerir.
Bu klasör, belirli bileşen türleri için `data`, `display`, `feedback` ve `input` gibi alt klasörler içerebilir. Her bir bileşen, kendi içinde bağımsız ve yeniden kullanılabilir olmalı, böylece uygulamanın farklı kısımlarında kullanılabilir.
UI bileşenlerini `modules` klasöründeki diğer bileşenlerden ayırarak, tutarlı bir tasarımı korumak ve kullanıcı arayüzünde değişiklikleri kod tabanının diğer bölümlerini (iş mantığı) etkilemeden yapmak daha kolaydır.
## Arayüz ve bağımlılıklar
Diğer modüllerin kodunu, `ui` klasörü hariç, herhangi bir modülden içe aktarabilirsiniz. Bu, kodunun test edilmesini kolaylaştıracaktır.
### Dahili
Bir modülün her bir parçası (kancalar, durumlar, ...) yalnızca modül içinde kullanılan kısımları içeren bir `internal` klasöre sahip olabilir.

View File

@@ -0,0 +1,91 @@
---
title: Ön Yüz Komutları
icon: terminal
---
## Faydalı Komutlar
### Uygulamayı Başlatma
```bash
npx nx start twenty-front
```
### API graphql şemasına dayalı graphql şemasını yeniden oluştur
```bash
npx nx run twenty-front:graphql:generate --configuration=metadata
```
VEYA
```bash
npx nx run twenty-front:graphql:generate
```
### Kod Temizleme
```bash
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## Çeviriler
```bash
npx nx run twenty-front:lingui:extract
npx nx run twenty-front:lingui:compile
```
### Test
```bash
npx nx run twenty-front:test # run jest tests
npx nx run twenty-front:storybook:serve:dev # run storybook
npx nx run twenty-front:storybook:test # run tests # (needs yarn storybook:serve:dev to be running)
npx nx run twenty-front:storybook:coverage # (needs yarn storybook:serve:dev to be running)
```
## Teknoloji Yığını
Proje temiz ve basit bir yığına sahip, minimum şablon koduyla.
**Uygulama**
* [React](https://react.dev/)
* [Apollo](https://www.apollographql.com/docs/)
* [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
* [Jotai](https://jotai.org/)
* [TypeScript](https://www.typescriptlang.org/)
**Test**
* [Jest](https://jestjs.io/)
* [Storybook](https://storybook.js.org/)
**Araçlar**
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
## Mimari
### Rotalama
[React Router](https://reactrouter.com/) rotalamayı halleder.
Gereksiz [yeniden renderların](/l/tr/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders) önlenmesi için tüm rotalama mantığı `PageChangeEffect` içindeki bir `useEffect`'te yer alır.
### Durum Yönetimi
[Jotai](https://jotai.org/) durum yönetimini ele alır.
Durum yönetimi hakkında daha fazla bilgi için [en iyi uygulamalar](/l/tr/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) bölümüne bakın.
## Test
[Jest](https://jestjs.io/) birim testi aracı olarak kullanılırken [Storybook](https://storybook.js.org/) bileşen testleri için kullanılır.
Jest, esas olarak yardımcı programların test edilmesi için kullanılır ve bileşenlerin kendisi için değil.
Storybook, izole bileşenlerin davranışlarının test edilmesi ve tasarım sisteminin gösterilmesi için kullanılır.

View File

@@ -0,0 +1,178 @@
---
title: Kısayollar
---
## Giriş
Bir kısayolu dinlemeniz gerektiğinde, normalde `onKeyDown` olay dinleyicisini kullanırsınız.
Ancak `twenty-front` uygulamasında, aynı anda monte edilmiş farklı bileşenlerde kullanılan aynı kısayollar arasında çakışmalar olabilir.
Örneğin, Enter tuşunu dinleyen bir sayfanız ve Enter tuşunu dinleyen bir modalınız varsa, ve modalın içinde Enter tuşunu dinleyen bir Seçim bileşeni varsa, hepsi aynı anda monte edildiğinde bir çakışma yaşayabilirsiniz.
## `useScopedHotkeys` hook'u
Bu sorunu ele almak için, kısayolları çakışma olmadan dinlemeyi mümkün kılan özel bir kanca geliştirdik.
Bir bileşene yerleştirirsiniz ve sadece bileşen monte edildiğinde VE belirtilen **kısayol kapsamı** aktif olduğunda kısayolları dinleyecektir.
## Pratikte kısayolları nasıl dinleyebilirsiniz?
Kısayol dinleme kurulumu iki adımdan oluşur:
1. Kısayolları dinleyecek [kısayol kapsamını](#what-is-a-hotkey-scope-) ayarlayın
2. Kısayolları dinlemek için `useScopedHotkeys` hook'unu kullanın
Kısayol kapsamlarının kurulumu, sol menü veya komut menüsü gibi diğer kullanıcı arayüzü öğelerinin de kısayolları dinleyebileceği için, basit sayfalarda bile gereklidir.
## Kısayollar için kullanım durumları
Genel olarak, kısayollara ihtiyaç duyan iki kullanım durumunuz olacaktır:
1. Bir sayfada veya bir sayfada monte edilmiş bir bileşende
2. Bir kullanıcı eylemi nedeniyle dikkati çeken modal-tipi bir bileşende
İkinci kullanım durumu, örneğin bir modal içinde bir açılır menü gibi, yineleyici olabilir.
### Bir sayfada kısayolları dinlemek
Örnek:
```tsx
const PageListeningEnter = () => {
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
// 1. Set the hotkey scope in a useEffect
useEffect(() => {
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleEnterPage,
);
// Revert to the previous hotkey scope when the component is unmounted
return () => {
goBackToPreviousHotkeyScope();
};
}, [goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope]);
// 2. Use the useScopedHotkeys hook
useScopedHotkeys(
Key.Enter,
() => {
// Some logic executed on this page when the user presses Enter
// ...
},
ExampleHotkeyScopes.ExampleEnterPage,
);
return <div>My page that listens for Enter</div>;
};
```
### Modal-tipi bir bileşende kısayolları dinlemek
Bu örnekte, ebeveynine kapanmasını söylemek için Escape tuşunu dinleyen bir modal bileşeni kullanacağız.
Burada kullanıcı etkileşimi, kapsamı değiştirmektir.
```tsx
const ExamplePageWithModal = () => {
const [showModal, setShowModal] = useState(false);
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const handleOpenModalClick = () => {
// 1. Set the hotkey scope when user opens the modal
setShowModal(true);
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleModal,
);
};
const handleModalClose = () => {
// 1. Revert to the previous hotkey scope when the modal is closed
setShowModal(false);
goBackToPreviousHotkeyScope();
};
return <div>
<h1>My page with a modal</h1>
<button onClick={handleOpenModalClick}>Open modal</button>
{showModal && <MyModalComponent onClose={handleModalClose} />}
</div>;
};
```
Sonra modal bileşeninde:
```tsx
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
// 2. Use the useScopedHotkeys hook to listen for Escape.
// Note that escape is a common hotkey that could be used by many other components
// So it's important to use a hotkey scope to avoid conflicts
useScopedHotkeys(
Key.Escape,
() => {
onClose()
},
ExampleHotkeyScopes.ExampleModal,
);
return <div>My modal component</div>;
};
```
Bu modeli, mount/unmount ile yalnızca bir useEffect'e güvenmenin çakışmayı önlemek için yeterli olmayabileceği durumlarda kullanmak önemlidir.
Bu çakışmaların hata ayıklaması zor olabilir ve genellikle useEffect'lerle daha sık meydana gelebilir.
## Kısayol kapsamı nedir?
Kısayol kapsamı, kısayolların aktif olduğu bir bağlamı temsil eden bir dizedir. Genellikle bir enum olarak kodlanmıştır.
Kısayol kapsamını değiştirdiğinizde, bu kapsamı dinleyen kısayollar etkinleştirilecek ve diğer kapsamları dinleyen kısayollar devre dışı bırakılacaktır.
Aynı anda yalnızca bir kapsam ayarlayabilirsiniz.
Örneğin, her sayfa için kısayol kapsamları `PageHotkeyScope` enum'unda tanımlanmıştır:
```tsx
export enum PageHotkeyScope {
Settings = 'settings',
CreateWorkspace = 'create-workspace',
SignInUp = 'sign-in-up',
CreateProfile = 'create-profile',
PlanRequired = 'plan-required',
ShowPage = 'show-page',
PersonShowPage = 'person-show-page',
CompanyShowPage = 'company-show-page',
CompaniesPage = 'companies-page',
PeoplePage = 'people-page',
OpportunitiesPage = 'opportunities-page',
ProfilePage = 'profile-page',
WorkspaceMemberPage = 'workspace-member-page',
TaskPage = 'task-page',
}
```
Dahili olarak, şu anda seçili olan kapsam, uygulama genelinde paylaşılan bir Jotai atomunda saklanır:
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
key: 'currentHotkeyScopeState',
defaultValue: INITIAL_HOTKEYS_SCOPE,
});
```
Ancak bu atom asla el ile yönetilmemelidir! Bunu bir sonraki bölümde nasıl kullanacağımızı göreceğiz.
## İçsel olarak nasıl çalışıyor?
Gereksiz yeniden render etme işlemlerinden kaçınarak daha performanslı hale getiren [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) üzerine ince bir sarmalayıcı yaptık.
Ayrıca, kısayol kapsamı durumunu yönetmek ve uygulamanın her yerinde kullanılabilir hale getirmek için bir Jotai atomu oluşturuyoruz.

View File

@@ -0,0 +1,8 @@
---
title: Storybook
description: Twenty'nin UI bileşen kütüphanesine göz atın
---
Storybook'ta eksiksiz bileşen kütüphanemizi ve dokümantasyonumuzu görüntüleyin.
[Storybook'u Aç →](https://storybook.twenty.com)

View File

@@ -0,0 +1,291 @@
---
title: Stil Rehberi
icon: paintbrush
---
Bu belge, kod yazarken uyulması gereken kuralları içermektedir.
Buradaki amaç, okunması ve bakımı kolay tutarlı bir kod tabanı oluşturmaktır.
Bunun için, biraz daha ayrıntılı olmak, çok kısa olmaktan daha iyidir.
İnsanların kodu yazmaktan daha sık okuduğunu her zaman aklınızda bulundurun; özellikle herkesin katkıda bulunabildiği açık kaynak bir projede.
Burada tanımlanmayan, ancak linters tarafından otomatik olarak kontrol edilen birçok kural vardır.
## React
### Fonksiyonel bileşenler kullanın
Her zaman TSX fonksiyonel bileşenlerini kullanın.
Varsayılan `import`'u `const` ile kullanmayın, çünkü okunması daha zordur ve kod tamamlama ile içe aktarmak daha zordur.
```tsx
// ❌ Bad, harder to read, harder to import with code completion
const MyComponent = () => {
return <div>Hello World</div>;
};
export default MyComponent;
// ✅ Good, easy to read, easy to import with code completion
export function MyComponent() {
return <div>Hello World</div>;
};
```
### Özellikler
Prop'ların türünü oluşturun ve dışa aktarmaya gerek yoksa ona `(ComponentName)Props` adını verin.
Props destructuring kullanın.
```tsx
// ❌ Bad, no type
export const MyComponent = (props) => <div>Hello {props.name}</div>;
// ✅ Good, type
type MyComponentProps = {
name: string;
};
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
```
#### Prop türlerini tanımlamak için `React.FC` veya `React.FunctionComponent` kullanmaktan kaçının
```tsx
/* ❌ - Bad, defines the component type annotations with `FC`
* - With `React.FC`, the component implicitly accepts a `children` prop
* even if it's not defined in the prop type. This might not always be
* desirable, especially if the component doesn't intend to render
* children.
*/
const EmailField: React.FC<{
value: string;
}> = ({ value }) => <TextInput value={value} disabled fullWidth />;
```
```tsx
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
* component's props
* - This method doesn't automatically include the children prop. If
* you want to include it, you have to specify it in OwnProps.
*/
type EmailFieldProps = {
value: string;
};
const EmailField = ({ value }: EmailFieldProps) => (
<TextInput value={value} disabled fullWidth />
);
```
#### JSX Elemanlarında Tek Değişkenli Prop Yayılımından Kaçının
JSX elemanlarında tek değişkenli prop yayılımını, örneğin `{...props}` kullanmaktan kaçının. Bu uygulama, bileşenin hangi prop'ları aldığını belirsizleştirdiği için okunması zor ve bakımı güç kodlara yol açar.
```tsx
/* ❌ - Bad, spreads a single variable prop into the underlying component
*/
const MyComponent = (props: OwnProps) => {
return <OtherComponent {...props} />;
}
```
```tsx
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
};
```
Gerekçe:
* İlk bakışta, hangi prop'ların kod tarafından geçirildiği daha açıktır, bu da anlamayı ve bakımı kolaylaştırır.
* Prop'lar aracılığıyla bileşenler arasında sıkı bağlanmayı önlemeye yardımcı olur.
* Linting araçları, prop'ları açıkça listelediğinizde yanlış yazılmış veya kullanılmayan prop'ları tanımlamayı kolaylaştırır.
## JavaScript
### Nullish-birleştirme operatörü `??` kullanın
```tsx
// ❌ Bad, can return 'default' even if value is 0 or ''
const value = process.env.MY_VALUE || 'default';
// ✅ Good, will return 'default' only if value is null or undefined
const value = process.env.MY_VALUE ?? 'default';
```
### Opsiyonel zincirleme `?.` kullanın
```tsx
// ❌ Bad
onClick && onClick();
// ✅ Good
onClick?.();
```
## TypeScript
### `interface` yerine `type` kullanın
Neredeyse her zaman örtüştükleri ve `type` daha esnek olduğu için her zaman `interface` yerine `type` kullanın.
```tsx
// ❌ Bad
interface MyInterface {
name: string;
}
// ✅ Good
type MyType = {
name: string;
};
```
### enum'lar yerine string literal'leri kullanın
[String literalleri](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types), TypeScript'te enum benzeri değerleri yönetmek için en iyi yöntemdir. Pick ve Omit ile genişletilmeleri daha kolaydır ve özellikle kod tamamlama ile daha iyi bir geliştirici deneyimi sunarlar.
TypeScript, enum'ların neden kaçınılması gereken bir seçenek olduğunu [burada](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums) açıklamaktadır.
```tsx
// ❌ Bad, utilizes an enum
enum Color {
Red = "red",
Green = "green",
Blue = "blue",
}
let color = Color.Red;
```
```tsx
// ✅ Good, utilizes a string literal
let color: "red" | "green" | "blue" = "red";
```
#### GraphQL ve iç kütüphaneler
GraphQL codegen tarafından üretilen enum'ları kullanmalısınız.
Bir iç kütüphane kullanırken de bir enum kullanmak daha iyidir, böylece iç kütüphane, iç API ile ilgili olmayan bir string literal türü açmak zorunda kalmaz.
Örnek:
```TSX
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
```
## Şekil Verme
### StyledComponents kullanın
Bileşenleri [Linaria styled](https://github.com/callstack/linaria) ile stillendirin.
```tsx
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
Styled bileşenleri, bunları "gerçek" bileşenlerden ayırt etmek için "Styled" önekiyle belirtin.
```tsx
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
### Temalandırma
Çoğu bileşen şekillendirmesi için temayı kullanmak tercih edilen bir yaklaşımdır.
#### Ölçü birimleri
Styled bileşenler içinde doğrudan `px` veya `rem` değerlerini kullanmaktan kaçının. Gerekli değerler genellikle temada tanımlanmıştır, bu nedenle bu amaçlar için temayı kullanmak önerilir.
#### Renkler
Yeni renkler eklemekten kaçının; Bunun yerine temadaki mevcut paleti kullanın. Palet uyum sağlamıyorsa, lütfen ekibin düzeltmesi için bir yorum bırakın.
```tsx
// ❌ Bad, directly specifies style values without utilizing the theme
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
font-weight: 400;
margin-left: 4px;
border-radius: 50px;
`;
```
```tsx
// ✅ Good, utilizes the theme
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.regular};
margin-left: ${({ theme }) => theme.spacing(1)};
border-radius: ${({ theme }) => theme.border.rounded};
`;
```
## Tip içermeyen importları zorunlu kılma
Tip ithalatlarından kaçının. Bu standardı uygulamak için bir Oxlint kuralı, herhangi bir tür içe aktarmayı denetler ve raporlar. Bu, TypeScript kodunda tutarlılık ve okunabilirliği sağlamaya yardımcı olur.
```tsx
// ❌ Bad
import { type Meta, type StoryObj } from '@storybook/react';
// ❌ Bad
import type { Meta, StoryObj } from '@storybook/react';
// ✅ Good
import { Meta, StoryObj } from '@storybook/react';
```
### Neden Tip İthalatları Yok
* **Tutarlılık**: Hem tip hem de değer ithalatları için tek bir yaklaşım kullanarak, kod tabanı modül import stilinde tutarlı kalır.
* **Okunabilirlik**: İthalat türü olmadığında, değer veya tip ithal ettiğiniz zaman daha anlaşılır olur böylece kod okunabilirliği artırılır. Bu belirsizliği azaltır ve ithal edilen sembollerinin amacını anlamayı kolaylaştırır.
* **Bakım Kolaylığı**: Kod tabanının bakımını kolaylaştırır çünkü geliştiriciler kodu incelerken veya değiştirirken yalnızca tip ithalatlarını tanımlayabilir ve bulabilirler.
### Oxlint Kuralı
Bir Oxlint kuralı, `typescript/consistent-type-imports`, tür içe aktarmalarının yasaklanması standardını uygular. Bu kural, herhangi bir tip ithalat ihlali için hata veya uyarı üretir.
Lütfen unutmayın ki bu kural, istemeden yapılan tip ithalatlarının gerçekleştiği nadir durumları özellikle ele alır. TypeScript'in kendisi, [TypeScript 3.8 sürüm notlarında](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html) belirtildiği gibi, bu uygulamayı caydırmaktadır. Çoğu durumda, yalnızca tip ithalatları kullanmanıza gerek yoktur.
Kodunuzun bu kurala uyduğundan emin olmak için, geliştirme iş akışınızın bir parçası olarak Oxlint'i çalıştırdığınızdan emin olun.

View File

@@ -0,0 +1,60 @@
---
title: Figma ile Çalışma
info: Twenty'nin Figma'sında nasıl işbirliği yapabileceğinizi öğrenin
---
Figma, tasarımcılar ve geliştiriciler arasındaki iletişim engelini aşmaya yardımcı olan işbirliği odaklı bir arayüz tasarım aracıdır.
Bu kılavuz, Figma ile nasıl işbirliği yapacağınızı açıklar.
## Erişim
1. **Paylaşılan bağlantıya erişin:** Projenin Figma dosyasına [buradan](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty) erişebilirsiniz.
2. **Oturum açın:** Henüz oturum açmadıysanız, Figma sizden oturum açmanızı isteyecektir.
Geliştirici modu ve ayrılmış bir çerçeveyi seçme yeteneği gibi ana özellikler yalnızca oturum açmış kullanıcılara sunulur.
<Warning>
Bir hesabınız olmadan etkili bir şekilde işbirliği yapamazsınız.
</Warning>
## Figma yapısı
Sol kenar çubuğundan Twenty'nin Figma'sındaki farklı sayfalara erişebilirsiniz. Bu şekilde organize edilmişlerdir:
* **Bileşenler sayfası:** Bu, ilk sayfadır. Tasarımcı, tasarım dosyası boyunca kullanılan yeniden kullanılabilir tasarım öğelerini oluşturmak ve organize etmek için kullanır. Örneğin, düğmeler, simgeler, semboller veya diğer herhangi bir yeniden kullanılabilir bileşen. Bu, tasarımda tutarlılığı sağlamak amacıyla hizmet eder.
* **Ana sayfa:** İkinci sayfa ana sayfadır ve projenin tam kullanıcı arayüzünü gösterir. You can press ***Play*** to use the full app prototype.
* **Özellikler sayfaları:** Diğer sayfalar genellikle devam eden özelliklere adanmıştır. Uygulama veya web sitesinin belirli özelliklerinin veya modüllerinin tasarımlarını içerir. Genellikle hala devam etmektedirler.
## Kullanışlı İpuçları
Yalnızca-okuma erişimiyle tasarımı düzenleyemezsiniz, ancak tasarımları koda dönüştürmek için kullanışlı olan tüm özelliklere erişebilirsiniz.
### Geliştirici modunu kullan
Figma'nın Geliştirici Modu, tasarım ve geliştirme arasındaki boşluğu doldurarak, kolay tasarım gezintisi, etkili varlık yönetimi, verimli iletişim araçları, araç kutusu entegrasyonları, hızlı kod parçacıkları ve önemli katman bilgileri ile geliştiricilerin üretkenliğini arttırır. Geliştirici Modu hakkında daha fazla bilgiyi [buradan](https://www.figma.com/dev-mode/) öğrenebilirsiniz.
Tasarım özelliklerini görmek, CSS kopyalamak ve varlıklara erişmek için araç çubuğunun sağ kısmında, "Geliştirici" moduna geçiş yapın.
### Prototipi kullan
Prototip görünümüne erişmek için tuvaldeki herhangi bir öğeye tıklayın ve arayüzün sağ üst köşesindeki “Play” düğmesine basın. Prototip modu, tasarımla nihai ürünmüş gibi etkileşim kurmanızı sağlar. Ekranlar arasındaki akışı gösterir ve düğmeler, bağlantılar veya menüler gibi arayüz öğelerinin etkileşim sırasında nasıl davrandığını gösterir.
1. **Geçişler ve animasyonları anlama:** Prototip modunda, bir tasarımcı tarafından ekranlar veya UI öğeleri arasında eklenen herhangi bir geçişi veya animasyonu görebilir, geliştiricilere hedeflenen davranış ve stille ilgili net görsel talimatlar verir.
2. **Uygulama açıklaması:** Bir prototip aynı zamanda belirsizlikleri azaltmaya da yardımcı olabilir. Geliştiriciler, belirli öğelerin işlevselliği veya görünümü hakkında daha iyi bir anlayış kazanmak adına onunla etkileşime geçebilirler.
Figma platformunu öğrenmek ile ilgili daha kapsamlı detaylar ve rehberlik için resmi [Figma Dokümantasyonu](https://help.figma.com/hc/en-us) sayfasını ziyaret edebilirsiniz.
### Mesafeleri ölç
Bir öğe seçin, (Mac) `Option` tuşunu veya (Windows) `Alt` tuşunu basılı tutun, ardından aralarındaki mesafeyi görmek için başka bir öğenin üzerine gelin.
### VSCode için Figma uzantısı (Önerilir)
[Figma for VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension) ile tasarım dosyalarını gezebilir ve inceleyebilir, tasarımcılarla işbirliği yapabilir, değişiklikleri takip edebilir ve uygulama hızını artırabilirsiniz - tüm bunları metin düzenleyicinizden ayrılmadan yapabilirsiniz.
Önerilen uzantılarımızın bir parçasıdır.
## İşbirliği
1. **Yorumları Kullanarak:** Araç çubuğunun sol kısmındaki balon simgesine tıklayarak yorum yapma özelliğini kullanabilirsiniz.
2. **İmleç sohbeti:** Figma'nın güzel bir özelliği İmleç sohbetidir. Eğer bir başkasının sizinle aynı anda Figma'yı kullandığını görürseniz, bir mesaj göndermek için Mac'te `;` tuşuna, Windows'ta ise `/` tuşuna basın.

View File

@@ -0,0 +1,317 @@
---
title: Yerel Kurulum
icon: laptop-code
description: Twenty'i yerel olarak çalıştırmak isteyen katkıda bulunanlar (veya meraklı geliştiriciler) için kılavuz.
---
## Ön Gereksinimler
<Tabs>
<Tab title="Linux ve macOS">
Twenty'i yüklemeden ve kullanmadan önce bilgisayarınıza aşağıdakileri yüklediğinizden emin olun:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node v24.5.0](https://nodejs.org/en/download)
* [yarn v4](https://yarnpkg.com/getting-started/install)
* [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md)
<Warning>
`npm` çalışmaz, bunun yerine `yarn` kullanmalısınız. Yarn artık Node.js ile birlikte geliyor, bu yüzden ayrı bir yüklemeye gerek yoktur.
Eğer henüz yapmadıysanız Yarn'ı etkinleştirmek için sadece `corepack enable` komutunu çalıştırmanız gerekiyor.
</Warning>
</Tab>
<Tab title="Windows (WSL)">
1. WSL Kurun
PowerShell'i Yönetici olarak açın ve çalıştırın:
```powershell
wsl --install
```
Şimdi bilgisayarınızı yeniden başlatmanız gerektiğine dair bir uyarı göreceksiniz. Eğer görmüyorsanız, manuel olarak yeniden başlatın.
Yeniden başlatıldığında bir PowerShell penceresi açılacak ve Ubuntu yüklenecek. Bu biraz zaman alabilir.
Ubuntu kurulumunuz için bir kullanıcı adı ve şifre oluşturmanız gerektiğine dair bir uyarı göreceksiniz.
2. git'i Yükleyin ve Yapılandırın
```bash
sudo apt-get install git
git config --global user.name "Your Name"
git config --global user.email "youremail@domain.com"
```
3. nvm, node.js ve yarn'ı Yükleyin
<Warning>
Doğru `node` versiyonunu yüklemek için `nvm` kullanın. `.nvmrc` tüm katkıda bulunanların aynı versiyonu kullanmasını sağlar.
</Warning>
```bash
sudo apt-get install curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
```
nvm'i kullanmak için terminalinizi kapatıp yeniden açın. Sonra aşağıdaki komutları çalıştırın.
```bash
nvm install # installs recommended node version
nvm use # use recommended node version
corepack enable
```
</Tab>
</Tabs>
---
## Adım 1: Git Clone
Terminalinizde aşağıdaki komutu çalıştırın.
<Tabs>
<Tab title="SSH (Önerilen)">
SSH anahtarlarını henüz kurmadıysanız, bunu nasıl yapacağınızı [buradan](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh) öğrenebilirsiniz.
```bash
git clone git@github.com:twentyhq/twenty.git
```
</Tab>
<Tab title="HTTPS">
```bash
git clone https://github.com/twentyhq/twenty.git
```
</Tab>
</Tabs>
## Adım 2: Kök Dizine Konuçlanın
```bash
cd twenty
```
Sonraki adımlardaki tüm komutları projenin kök dizininden çalıştırmalısınız.
## Adım 3: Bir PostgreSQL Veritabanı Kurun
<Tabs>
<Tab title="Linux">
**Seçenek 1 (tercih edilen):** Veritabanınızı yerel olarak hazırlamak için:
Linux makinenize PostgreSQL yüklemek için aşağıdaki bağlantıyı kullanın: [PostgreSQL Kurulumu](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Not: İzin hatalarından kaçınmak için `psql` komutundan önce `sudo -u postgres` eklemeniz gerekebilir.
**Seçenek 2:** Eğer docker yüklüyse:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="Mac OS">
**Seçenek 1 (tercih edilen):** Veritabanınızı `brew` ile yerel olarak kurmak için:
```bash
brew install postgresql@16
export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"
brew services start postgresql@16
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
PostgreSQL sunucusunun çalışıp çalışmadığını kontrol etmek için şu komutu çalıştırabilirsiniz:
```bash
brew services list
```
Yükleyici, macOS'ta Homebrew aracılığıyla yüklenirken
varsayılan olarak `postgres` kullanıcısını oluşturmayabilir. Bunun yerine, macOS kullanıcı adınıza (ör. "john") uygun bir PostgreSQL rolü oluşturur.
Gerekiyorsa `postgres` kullanıcısını kontrol etmek ve oluşturtmak için şu adımları izleyin:
```bash
# Connect to PostgreSQL
psql postgres
or
psql -U $(whoami) -d postgres
```
psql isteminde (postgres=#) şu komutu çalıştırın:
```bash
# List existing PostgreSQL roles
\du
```
Şu benzer bir çıktı göreceksiniz:
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
john | Superuser | {}
```
Eğer listede bir `postgres` rolü görmüyorsanız, bir sonraki adıma geçin.
`postgres` rolünü manuel olarak oluşturun:
```bash
CREATE ROLE postgres WITH SUPERUSER LOGIN;
```
Bu, `postgres` adıyla giriş erişimi olan bir süper kullanıcı rolü oluşturur.
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | Superuser | {}
john | Superuser | {}
```
**Seçenek 2:** Eğer docker yüklüyse:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="Windows (WSL)">
Aşağıdaki tüm adımlar WSL terminalinde (sanallaştırma makineniz içinde) çalıştırılmalıdır.
**Seçenek 1:** PostgreSQL'i yerel olarak hazırlamak için:
Linux sanal makinenize PostgreSQL yüklemek için aşağıdaki bağlantıyı kullanın: [PostgreSQL Kurulumu](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Not: İzin hatalarından kaçınmak için `psql` komutundan önce `sudo -u postgres` eklemeniz gerekebilir.
**Seçenek 2:** Eğer docker yüklüyse:
WSL üzerinde Docker çalıştırmak ek bir karmaşıklık katmanı ekler.
Yalnızca ek adımlar dahil olmak üzere extra karmaşıklık adımlarına aşinaysanız kullanın. [Docker Desktop WSL2](https://docs.docker.com/desktop/wsl) etkinleştirmenizi içeren.
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
</Tabs>
Artık veritabanına `localhost:5432` üzerinden erişebilirsiniz.
Yukarıdaki Docker seçeneğini kullandıysanız, varsayılan kimlik bilgileri kullanıcı adı `postgres` ve parola `postgres` şeklindedir. Yerel PostgreSQL kurulumları için, makinenizde yapılandırılmış kimlik bilgilerini ve rolleri kullanın.
## Adım 4: Redis Veritabanı (önbellek) Kurun
Twenty, en iyi performansı sağlamak için bir Redis önbelleği gerektirir.
<Tabs>
<Tab title="Linux">
**Seçenek 1:** Redis'i yerel olarak sağlamak için:
Linux makinenize Redis yüklemek için şu bağlantıyı kullanın: [Redis Kurulumu](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**Seçenek 2:** Eğer docker yüklüyse:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Mac OS">
**Seçenek 1 (tercih edilen):** Redis'i `brew` ile yerel olarak sağlamak için:
```bash
brew install redis
```
Redis sunucunuzu başlatın:
```bash
brew services start redis
```
**Seçenek 2:** Eğer docker yüklüyse:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Windows (WSL)">
**Seçenek 1:** Redis'i yerel olarak sağlamak için:
Linux sanal makinenize Redis yüklemek için şu bağlantıyı kullanın: [Redis Kurulumu](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**Seçenek 2:** Eğer docker yüklüyse:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
</Tabs>
Bir istemci GUI'ye ihtiyacınız varsa, [Redis Insight](https://redis.io/insight/) (ücretsiz sürüm mevcut) öneriyoruz.
## Adım 5: Ortam değişkenlerini ayarlayın
Projenizi yapılandırmak için çevresel değişkenler veya `.env` dosyaları kullanın. Daha fazla bilgi [burada](/l/tr/developers/self-host/capabilities/setup).
`.env.example` dosyalarını `/front` ve `/server` içine kopyalayın:
```bash
cp ./packages/twenty-front/.env.example ./packages/twenty-front/.env
cp ./packages/twenty-server/.env.example ./packages/twenty-server/.env
```
<Info>
**Çoklu Çalışma Alanı Modu:** Varsayılan olarak, Twenty yalnızca bir çalışma alanının oluşturulabildiği tek çalışma alanı modunda çalışır. Çoklu çalışma alanı desteğini etkinleştirmek için (alt alan adına dayalı özellikleri test etmek açısından kullanışlıdır), sunucunuzdaki `.env` dosyanızda `IS_MULTIWORKSPACE_ENABLED=true` değerini ayarlayın. Ayrıntılar için [Çoklu Çalışma Alanı Modu](/l/tr/developers/self-host/capabilities/setup#multi-workspace-mode) bölümüne bakın.
</Info>
## Adım 6: Bağımlılıkların yüklenmesi
Twenty server'ı oluşturup veritabanınıza bazı veriler yerleştirmek için aşağıdaki komutu çalıştırın:
```bash
yarn
```
`npm` veya `pnpm` çalışmaz
## Adım 7: Projeyi çalıştırma
<Tabs>
<Tab title="Linux">
Dağıtımınıza bağlı olarak, Redis sunucusu otomatik olarak başlatılabilir.
Değilse, dağıtımınız için [Redis Kurulum Kılavuzu](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) üzerinden kontrol edin.
</Tab>
<Tab title="Mac OS">
Redis zaten çalışıyor olmalıdır. Değilse, şu komutu çalıştırın:
```bash
brew services start redis
```
</Tab>
<Tab title="Windows (WSL)">
Dağıtımınıza bağlı olarak, Redis sunucusu otomatik olarak başlatılabilir.
Değilse, dağıtımınız için [Redis Kurulum Kılavuzu](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) üzerinden kontrol edin.
</Tab>
</Tabs>
Veritabanınızı aşağıdaki komutla kurun:
```bash
npx nx database:reset twenty-server
```
Sunucuyu, çalışanı ve ön uç hizmetlerini başlatın:
```bash
npx nx start twenty-server
npx nx worker twenty-server
npx nx start twenty-front
```
Alternatif olarak, tüm hizmetleri aynı anda başlatabilirsiniz:
```bash
npx nx start
```
## Adım 8: Twenty'yi kullanın
**Ön Uç**
Twenty'nin ön ucu [http://localhost:3001](http://localhost:3001) adresinde çalışıyor olacak.
Varsayılan demo hesabıyla giriş yapabilirsiniz: `tim@apple.dev` (şifre: `tim@apple.dev`)
**Arka Uç**
* Twenty'nin sunucusu [http://localhost:3000](http://localhost:3000) adresinde çalışır durumda olacak
* GraphQL API'sine [http://localhost:3000/graphql](http://localhost:3000/graphql) adresinden erişebilirsiniz.
* REST API'sine [http://localhost:3000/rest](http://localhost:3000/rest) adresinden ulaşabilirsiniz.
## Sorun Giderme
Herhangi bir sorunla karşılaşırsanız, çözümler için [Sorun Giderme](/l/tr/developers/self-host/capabilities/troubleshooting) sayfasına bakın.

View File

@@ -0,0 +1,77 @@
---
title: Komutlar
icon: terminal
description: Twenty geliştirmek için yararlı komutlar.
---
Komutlar, deponun kök dizininden `npx nx` kullanılarak çalıştırılabilir. Açık hedefleme için `npx nx run {project}:{command}` kullanın.
## Uygulamayı Başlatma
```bash
npx nx start twenty-front # Frontend dev server (http://localhost:3001)
npx nx start twenty-server # Backend server (http://localhost:3000)
npx nx run twenty-server:worker # Background worker
```
## Veritabanı
```bash
npx nx database:reset twenty-server # Reset and seed database
npx nx run twenty-server:database:migrate:prod # Run migrations
npx nx run twenty-server:database:migrate:generate --name <name> --type <fast|slow> # Generate a migration
```
## Lint denetimi
```bash
npx nx lint:diff-with-main twenty-front # Lint changed files (fastest)
npx nx lint:diff-with-main twenty-server
npx nx lint twenty-front --configuration=fix # Auto-fix
```
## Tip denetimi
```bash
npx nx typecheck twenty-front
npx nx typecheck twenty-server
```
## Test
```bash
# Frontend
npx nx test twenty-front # Jest unit tests
npx nx storybook:build twenty-front # Build Storybook
npx nx storybook:test twenty-front # Storybook tests
# Backend
npx nx run twenty-server:test:unit # Unit tests
npx nx run twenty-server:test:integration # Integration tests
npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset
# Single file (fastest)
npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs
```
## GraphQL
```bash
npx nx run twenty-front:graphql:generate # Regenerate types
npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema
```
## Çeviriler
```bash
npx nx run twenty-front:lingui:extract # Extract strings
npx nx run twenty-front:lingui:compile # Compile translations
```
## Derleme
```bash
npx nx build twenty-shared # Must be built first
npx nx build twenty-front
npx nx build twenty-server
```

View File

@@ -0,0 +1,31 @@
---
title: Katkıda Bulunun
description: Twenty'nin açık kaynak geliştirmesine katkıda bulunun.
---
<Frame>
<img src="/images/user-guide/github/github-header.png" alt="AI" />
</Frame>
## Genel Bakış
Twenty açık kaynaktır ve topluluktan gelen katkıları memnuniyetle karşılar. İster hataları düzeltiyor, ister özellikler ekliyor ya da belgeleri iyileştiriyor olun, katkılarınız Twenty'yi herkes için daha iyi hâle getirmeye yardımcı olur.
## Katkıda Bulunma Yolları
* **Hataları bildirin**: Sorunları belirlemeye ve belgelemeye yardımcı olun
* **Özellik önerin**: Yeni işlevler önerip uygulayın
* **Belgeleri iyileştirin**: Belgelerimizi daha anlaşılır ve daha faydalı hâle getirin
* **Ön uç geliştirme**: React tabanlı kullanıcı arayüzü üzerinde çalışın
* **Arka uç geliştirme**: NestJS sunucusuna katkıda bulunun
## Başlarken
<CardGroup cols={2}>
<Card title="Hata Raporları ve İstekler" icon="bug" href="/l/tr/developers/contribute/capabilities/bug-and-requests">
Sorunları bildirin veya özellik talep edin
</Card>
<Card title="Frontend Geliştirme" icon="browser" href="/l/tr/developers/contribute/capabilities/frontend-development/folder-architecture-front">
Kullanıcı arayüzüne katkıda bulunun
</Card>
</CardGroup>

View File

@@ -0,0 +1,176 @@
---
title: Stil Rehberi
icon: paintbrush
description: Twenty'ye katkıda bulunmak için kod kuralları ve en iyi uygulamalar.
---
## React
### Yalnızca fonksiyonel bileşenler
Her zaman named export'larla TSX fonksiyonel bileşenlerini kullanın.
```tsx
// ❌ Bad
const MyComponent = () => {
return <div>Hello World</div>;
};
export default MyComponent;
// ✅ Good
export function MyComponent() {
return <div>Hello World</div>;
};
```
### Özellikler
`{ComponentName}Props` adında bir type oluşturun. Destructuring kullanın. `React.FC` kullanmayın.
```tsx
type MyComponentProps = {
name: string;
};
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
```
### Tek değişken için prop spread kullanmayın
```tsx
// ❌ Bad
const MyComponent = (props: MyComponentProps) => <Other {...props} />;
// ✅ Good
const MyComponent = ({ prop1, prop2 }: MyComponentProps) => <Other {...{ prop1, prop2 }} />;
```
## Durum Yönetimi
### Global durum için Jotai atomları
```tsx
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
export const myAtomState = createAtomState<string>({
key: 'myAtomState',
defaultValue: 'default value',
});
```
* Prop drilling yerine atomları tercih edin
* Durum için `useRef` kullanmayın — `useState` veya atomları kullanın
* Listeler için atom ailelerini ve seçicileri kullanın
### Gereksiz yeniden render'ları önleyin
* `useEffect` ve veri çekmeyi kardeş sidecar bileşenlere ayırın
* `useEffect` yerine olay işleyicilerini (`handleClick`, `handleChange`) tercih edin
* `React.memo()` kullanmayın — bunun yerine kök nedeni düzeltin
* `useCallback` / `useMemo` kullanımını sınırlayın
```tsx
// ❌ Bad — useEffect in the same component causes re-renders
export const Page = () => {
const [data, setData] = useAtomState(dataState);
const [dep] = useAtomState(depState);
useEffect(() => { setData(dep); }, [dep]);
return <div>{data}</div>;
};
// ✅ Good — extract into sibling
export const PageData = () => {
const [data, setData] = useAtomState(dataState);
const [dep] = useAtomState(depState);
useEffect(() => { setData(dep); }, [dep]);
return <></>;
};
export const Page = () => {
const [data] = useAtomState(dataState);
return <div>{data}</div>;
};
```
## TypeScript
* **`interface` yerine `type`** — daha esnek, birleştirmesi daha kolay
* **enum yerine string literal'lar** — GraphQL codegen enum'ları ve dahili kütüphane API'leri hariç
* **`any` yok** — katı TypeScript zorunludur
* **Type import'ları yok** — normal import'lar kullanın (Oxlint `typescript/consistent-type-imports` tarafından uygulanır)
* **[Zod](https://github.com/colinhacks/zod) kullanın** tiplenmemiş nesnelerin çalışma zamanı doğrulaması için
## JavaScript
```tsx
// Use nullish-coalescing (??) instead of ||
const value = process.env.MY_VALUE ?? 'default';
// Use optional chaining
onClick?.();
```
## İsimlendirme
* **Değişkenler**: camelCase, açıklayıcı (`email` değil `value`, `fieldMetadata` değil `fm`)
* **Sabitler**: SCREAMING_SNAKE_CASE
* **Tipler/Sınıflar**: PascalCase
* **Dosyalar/dizinler**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`)
* **Olay işleyicileri**: `handleClick` (işleyici fonksiyon için `onClick` değil)
* **Bileşen prop'ları**: önek olarak bileşen adını kullanın (`ButtonProps`)
* **Styled bileşenler**: `Styled` öneğini kullanın (`StyledTitle`)
## Stil
[Linaria](https://github.com/callstack/linaria) ile stillendirilmiş bileşenleri kullanın. Tema değerlerini kullanın — kodda sabit tanımlanmış `px`, `rem` veya renklerden kaçının.
```tsx
// ❌ Bad
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
margin-left: 4px;
`;
// ✅ Good
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
margin-left: ${({ theme }) => theme.spacing(1)};
`;
```
## İçe Aktarımlar
Göreli yollar yerine alias kullanın:
```tsx
// ❌ Bad
import { Foo } from '../../../../../testing/decorators/Foo';
// ✅ Good
import { Foo } from '~/testing/decorators/Foo';
import { Bar } from '@/modules/bar/components/Bar';
```
## Klasör Yapısı
```
front
└── modules/ # Feature modules
│ └── module1/
│ ├── components/
│ ├── constants/
│ ├── contexts/
│ ├── graphql/ (fragments, queries, mutations)
│ ├── hooks/
│ ├── states/ (atoms, selectors)
│ ├── types/
│ └── utils/
└── pages/ # Route-level components
└── ui/ # Reusable UI components (display, input, feedback, ...)
```
* Modüller diğer modüllerden import edebilir, ancak `ui/` bağımlılıksız kalmalıdır
* `internal/` alt klasörlerini modüle özel kod için kullanın
* Bileşenler 300 satırın altında, servisler 500 satırın altında

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).

View File

@@ -0,0 +1,28 @@
---
title: Geliştiriciler
description: Uygulamalar geliştirin, API'yi kullanın, kendi altyapınızda barındırın ya da kod tabanına katkıda bulunun.
---
import { CardTitle } from "/snippets/card-title.mdx"
<CardGroup cols={3}>
<Card href="/l/tr/developers/extend/apps/getting-started/quick-start" img="/images/user-guide/halftone/dev-apps.png">
<CardTitle>Uygulamalar</CardTitle>
Özel nesneler, sunucu tarafı mantık, UI bileşenleri ve yapay zeka ajanlarıyla Twenty'yi genişletin — hepsi TypeScript paketleri olarak.
</Card>
<Card href="/l/tr/developers/extend/api" img="/images/user-guide/halftone/dev-api.png">
<CardTitle>API</CardTitle>
REST ve GraphQL API'leri, webhook'lar ve OAuth.
</Card>
<Card href="/l/tr/developers/self-host/capabilities/docker-compose" img="/images/user-guide/halftone/dev-self-host.png">
<CardTitle>Self-Host</CardTitle>
Twenty'yi kendi altyapınızda çalıştırın.
</Card>
<Card href="/l/tr/developers/contribute/capabilities/local-setup" img="/images/user-guide/halftone/dev-contribute.png">
<CardTitle>Katkıda Bulunun</CardTitle>
Monorepo'yu yerel olarak kurun ve PR'ler gönderin.
</Card>
</CardGroup>

View File

@@ -0,0 +1,262 @@
---
title: Docker Compose
icon: docker
---
<Warning>
Docker kapsayıcıları, üretim ortamında barındırma veya kendi kendine barındırma içindir. Katkıda bulunmak için lütfen [Yerel Kurulum](/l/tr/developers/contribute/capabilities/local-setup) bölümüne bakın.
</Warning>
## Genel Bakış
Bu kılavuz, Docker Compose kullanarak Twenty uygulamasını kurmak ve yapılandırmak için adım adım yönergeler sunar. Amaç, süreci sadeleştirmek ve kurulumunuzu bozabilecek yaygın sorunları önlemektir.
**Önemli:** Yalnızca bu kılavuzda açıkça belirtilen ayarları değiştirin. Diğer yapılandırmaları değiştirmek sorunlara yol açabilir.
İleri düzey yapılandırma için [Ortam Değişkenlerini Ayarlama](/l/tr/developers/self-host/capabilities/setup) bölümüne bakın. Tüm ortam değişkenleri, değişkene bağlı olarak sunucu ve/veya işçi düzeyinde `docker-compose.yml` dosyasında tanımlanmalıdır.
## Sistem Gereksinimleri
* RAM: Ortamınızda en az 2GB RAM bulunduğundan emin olun. Yetersiz bellek, işlemlerin çökmesine neden olabilir.
* Docker & Docker Compose: Her ikisinin de yüklendiğinden ve güncel olduğundan emin olun.
## Seçenek 1: Tek satırlık komut dosyası
Tek bir komutla en son kararlı Twenty sürümünü yükleyin:
```bash
bash <(curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/scripts/install.sh)
```
Belirli bir sürüm veya dal yüklemek için:
```bash
VERSION=vx.y.z BRANCH=branch-name bash <(curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/scripts/install.sh)
```
* x.y.z'yi istediğiniz sürüm numarasıyla değiştirin.
* dal-adını yüklemek istediğiniz dal adıyla değiştirin.
## Seçenek 2: Manuel adımlar
Manuel kurulum için bu adımları uygulayın.
### Adım 1: Çevre Dosyasını Kurun
1. **.env Dosyasını Oluşturun**
Örnek çevre dosyasını, çalışma dizininizde yeni bir .env dosyasına kopyalayın:
```bash
curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example
```
2. **Bir Şifreleme Anahtarı Oluşturun**
Benzersiz bir rastgele karakter dizisi oluşturmak için aşağıdaki komutu çalıştırın:
```bash
openssl rand -base64 32
```
**Önemli:** Bu değeri gizli tutun / paylaşmayın. `ENCRYPTION_KEY` kaybedilirse, veritabanında depolanan her gizli veriye (OAuth belirteçleri, uygulama değişkenleri, TOTP gizli anahtarları vb.) erişim de kaybedilir.
3. **`.env` Güncelleyin**
.env dosyanızdaki yer tutucu değeri oluşturulan jetonla değiştirin:
```ini
ENCRYPTION_KEY=random_string
```
Kesinti olmadan anahtarı döndürmeye ilişkin talimatlar için [Anahtar döndürme kılavuzuna](/l/tr/developers/self-host/capabilities/key-rotation) bakın.
4. **Postgres Şifresini Ayarlayın**
.env dosyasındaki `PG_DATABASE_PASSWORD` değerini özel karakter içermeyen güçlü bir şifre ile güncelleyin.
```ini
PG_DATABASE_PASSWORD=my_strong_password
```
### Adım 2: Docker Compose Dosyasını Edinin
`docker-compose.yml` dosyasını çalışma dizininize indirin:
```bash
curl -o docker-compose.yml https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/docker-compose.yml
```
### Adım 3: Uygulamayı Başlatın
Docker konteynerlerini başlatın:
```bash
docker compose up -d
```
### Adım 4: Uygulamaya Erişin
Eğer twentyCRM'i kendi bilgisayarınızda barındırıyorsanız, tarayıcınızı açın ve [http://localhost:3000](http://localhost:3000) adresine gidin.
Bir sunucuda barındırıyorsanız, sunucunun çalıştığını ve her şeyin yolunda olduğunu kontrol edin
```bash
curl http://localhost:3000
```
## Yapılandırma
### Twenty'yi Dış Erişime Açma
Varsayılan olarak, Twenty `localhost` üzerinde `3000` portunda çalışır. UI, API'ye tarayıcının kendi origin'inden erişir; bu nedenle konteynere ulaşan herhangi bir ana bilgisayar adından yüklenir — ancak yine de `SERVER_URL` değerini herkese açık URL'nize ayarlayın: sunucu bunu oluşturulan bağlantılar (e-postalar, OAuth geri çağrıları) ve güvenli çerezler için HTTPS'i saptamak amacıyla kullanır.
#### `SERVER_URL`'a Genel Bakış
* **Protokol:** Yapılandırmanıza bağlı olarak `http` veya `https` kullanın.
* SSL ayarlamadıysanız `http` kullanın.
* SSL yapılandırıldıysa `https` kullanın.
* **Alan Adı/IP:** Uygulamanızın erişilebilir olduğu alan adı veya IP adresi.
* **Port:** Varsayılan portları kullanmıyorsanız, port numarasını ekleyin (`80` `http` için, `443` `https` için).
### SSL Gereksinimleri
Belirli tarayıcı özelliklerinin düzgün çalışabilmesi için SSL (HTTPS) gereklidir. Bu özellikler yerel geliştirme sırasında çalışabilirken (tarayıcılar localhost'u farklı şekilde ele alır), Twenty'yi düzenli bir alanda barındırırken uygun bir SSL kurulumu gereklidir.
Örneğin, panoya kopyalama API'si güvenli bir bağlam gerektirebilir - uygulama boyunca kopyalama düğmeleri gibi bazı özellikler HTTPS etkinleştirilmediğinde çalışmayabilir.
Optimum güvenlik ve işlevsellik için Twenty'nin SSL sonlandırma ile bir ters proxy arkasında ayarlanmasını şiddetle öneririz.
#### `SERVER_URL`'ı Yapılandırma
1. **Erişim URL'nizi Belirleyin**
* **Ters Proxy Olmadan (Doğrudan Erişim):**
Uygulamaya doğrudan bir ters proxy olmadan erişiyorsanız:
```ini
SERVER_URL=http://your-domain-or-ip:3000
```
* **Ters Proxy ile (Standart Portlar):**
Nginx veya Traefik gibi bir ters proxy kullanıyorsanız ve SSL yapılandırıldıysa:
```ini
SERVER_URL=https://your-domain-or-ip
```
* **Ters Proxy ile (Özel Portlar):**
Standart olmayan portlar kullanıyorsanız:
```ini
SERVER_URL=https://your-domain-or-ip:custom-port
```
2. **`.env` Dosyasını Güncelleyin**
`.env` dosyanızı açın ve `SERVER_URL`'i güncelleyin:
```ini
SERVER_URL=http(s)://your-domain-or-ip:your-port
```
**Örnekler:**
* SSL olmadan doğrudan erişim:
```ini
SERVER_URL=http://123.45.67.89:3000
```
* SSL ile alan adı aracılığıyla erişim:
```ini
SERVER_URL=https://mytwentyapp.com
```
3. **Uygulamayı Yeniden Başlatın**
Değişikliklerin etkili olması için Docker konteynerlerini yeniden başlatın:
```bash
docker compose down
docker compose up -d
```
#### Dikkat Edilmesi Gerekenler
* **Ters Proxy Yapılandırması:**
Ters proxy'nin istekleri doğru iç bağlantı noktasına ilettiğinden emin olun (`3000` varsayılan olarak). SSL sonlandırmasını ve gerekli tüm başlıkları yapılandırın.
* **Güvenlik Duvarı Ayarları:**
Dış erişime izin vermek için güvenlik duvarınızdaki gerekli portları açın.
* **Tutarlılık:**
`SERVER_URL`, kullanıcıların uygulamanıza tarayıcıları ile nasıl eriştiği ile eşleşmelidir.
#### Süreklilik
* **Veri Hacimleri:**
Docker Compose yapılandırması, veritabanı ve sunucu depolama için verileri kalıcı kılmak adına hacimler kullanmaktadır.
* **Durumsuz Ortamlar:**
Eğer durumsuz bir ortama (örneğin, bazı bulut hizmetleri) dağıtım yapıyorsanız, verilerin kalıcılığını sağlamak için harici depolama yapılandırın.
## Yedekleme ve Geri Yükleme
Düzenli yedeklemeler CRM verilerinizi kayıptan korur.
### Veritabanı Yedeği Oluşturun
```bash
docker exec twenty-postgres pg_dump -U postgres twenty > backup_$(date +%Y%m%d).sql
```
### Günlük Yedeklemeleri Otomatikleştirin
crontab'ınıza ekleyin (`crontab -e`):
```bash
0 2 * * * docker exec twenty-postgres pg_dump -U postgres twenty > /backups/twenty_$(date +\%Y\%m\%d).sql
```
### Yedekten Geri Yükleyin
1. Uygulamayı durdurun:
```bash
docker compose stop server worker
```
2. Veritabanını geri yükleyin:
```bash
docker exec -i twenty-postgres psql -U postgres twenty < backup_20240115.sql
```
3. Servisleri yeniden başlatın:
```bash
docker compose up -d
```
### Yedekleme En İyi Uygulamaları
* **Geri yüklemeleri düzenli olarak test edin** — yedeklerin gerçekten çalıştığını doğrulayın
* **Yedekleri farklı bir konumda saklayın** — bulut depolama kullanın (S3, GCS, vb.)
* **Hassas verileri şifreleyin** — yedekleri şifreleme ile koruyun
* **Birden fazla kopya saklayın** — günlük, haftalık ve aylık yedekleri tutun
## Sorun Giderme
Herhangi bir sorunla karşılaşırsanız, çözümler için [Sorun Giderme](/l/tr/developers/self-host/capabilities/troubleshooting) sayfasına bakın.
## Yönetilen Barındırma
<Tip>
Twenty'yi kendiniz çalıştırıp bakımını yapmak istemiyor musunuz? [Sertifikalı bir Twenty barındırma iş ortağı bulun](https://twenty.com/partners/list?categories=HOSTING\&ref=docs-docker-compose); kurulumunuzun dağıtımını yapması, barındırması ve uçtan uca yönetmesi için. *(Veya ekibimize ulaşın: [contact@twenty.com](mailto:contact@twenty.com).)*
</Tip>

View File

@@ -0,0 +1,60 @@
---
title: Anahtar döndürme
icon: rotate
---
Twenty'nin iki bağımsız anahtar ailesi vardır:
* **JWT imzalama anahtarları** — asimetrik ES256 anahtar çiftleri (`kid` etiketli) `core."signingKey"` içinde saklanır ve erişim/yenileme belirteçlerini imzalamak ve doğrulamak için kullanılır.
* **Bekleme hâlindeki şifreleme anahtarı** — OAuth belirteçlerini, uygulama değişkenlerini, imzalama anahtarlarının gizli anahtarlarını, hassas yapılandırma değerlerini ve `enc:v2:` zarfı içindeki TOTP gizli anahtarlarını şifrelemek için kullanılan `ENCRYPTION_KEY`.
`APP_SECRET`, geriye dönük uyumluluk için tutulan eski bir gizlidir: `ENCRYPTION_KEY` ayarlanmamış olduğunda bekleme hâlindeki şifreleme / oturum çerezi geri dönüşü olarak davranır ve hâlâ önceden var olan HS256 erişim belirteçlerini doğrular. Kullanımdan kaldırılacaktır.
## JWT imzalama anahtarları
Her anahtar bir `publicKey` (önceden verilmiş belirteçleri doğrulayabilmesi için süresiz olarak saklanır), şifrelenmiş bir `privateKey` (yalnızca anahtar geçerli iken kullanılır), bir `isCurrent` bayrağı (aynı anda tam olarak bir satır) ve isteğe bağlı bir `revokedAt` taşır.
### Geçerli anahtarı döndür
Etkinleştirmek için `SIGNING_KEY_ROTATION_DAYS` değerini ayarlayın: günlük bir cron görevi, mevcut anahtar bu eşikten daha eski olduğunda yeni bir güncel anahtar oluşturur. Önceki anahtarlar *iptal edilmez*, bu nedenle onlarla imzalanmış belirteçler doğrulanmaya devam eder. Otomatik döndürmeyi devre dışı bırakmak için değişkeni ayarlamadan bırakın.
<Note>Otomatik döndürme v2.6+ ile birlikte gelir.</Note>
### Bir anahtarı iptal et (yalnızca sızıntı / acil durum için)
Geçerli olmayan bir satırda **Settings → Admin Panel → Signing keys → Revoke**. Şifrelenmiş özel materyali siler, `revokedAt` değerini ayarlar ve o `kid` ile imzalanmış mevcut tüm belirteçleri reddeder.
## `ENCRYPTION_KEY` anahtarını döndür
<Note>Aşağıda açıklanan `secret-encryption:rotate` komutu v2.6+ ile birlikte gelir.</Note>
Her şifrelenmiş değer `enc:v2:\<keyId>:\<payload>` olarak sarılır; burada `\<keyId>`, ham anahtardan türetilen 8 onaltılık önekidir. Döndürme çevrimiçi ve kaldığı yerden devam ettirilebilir.
1. **Yeni bir anahtar oluşturun**: `openssl rand -base64 32`.
2. `.env` içinde **her iki anahtarı yan yana yapılandırın**, ardından yeniden başlatın:
```ini
ENCRYPTION_KEY=NEW_VALUE
FALLBACK_ENCRYPTION_KEY=OLD_VALUE
```
Yeni yazmalar yeni anahtarı kullanır, mevcut satırlar hâlâ geri dönüş anahtarıyla şifresi çözülerek okunur.
3. **Mevcut satırları yeniden şifreleyin**:
```bash
docker exec -it {server_container} yarn command:prod secret-encryption:rotate
```
Komut altı siteyi dolaşır (`connected-account-tokens`, `application-variable`, `application-registration-variable`, `signing-key-private-keys`, `sensitive-config-storage`, `totp-secrets`). Bir SQL filtresi, zaten yeni `\<keyId>` üzerinde olan satırları atlar, bu nedenle komut idempotenttir: gerektiğinde yarıda kesip yeniden çalıştırın. Herhangi bir satır başarısız olursa sıfır olmayan kodla çıkar — yeniden denemek için tekrar çalıştırın.
| Bayrak | Açıklama |
| ---------------------------------------- | ------------------------------------------------------------------ |
| `-s, --site \<site>` | Yalnızca tek bir siteyle sınırla. |
| `-b, --batch-size \<n>` | Her yığın başına satır sayısı (varsayılan `200`, en fazla `5000`). |
| `-d, --dry-run` | Şifresini çöz + bellekte yeniden şifrele, `UPDATE` işlemini atla. |
4. `--dry-run` sıfır kalan satır gösterdiğinde **geri dönüş anahtarını bırakın**: `FALLBACK_ENCRYPTION_KEY` değerini kaldırın ve yeniden başlatın.
## Eski `APP_SECRET` desteği
Hiç `ENCRYPTION_KEY` ayarlamamış eski örnekler, `APP_SECRET` değerini bekleme hâlindeki şifreleme anahtarı (ve ondan türetilen oturum çerezi gizli anahtarı) olarak kullanır. Bu yol, geriye dönük uyumluluk için korunmuştur ancak **kullanımdan kaldırılmıştır** — özel bir `ENCRYPTION_KEY` ayarlayın ve ondan çıkmak için yukarıdaki döndürme prosedürünü izleyin. `APP_SECRET`in kendisi eski HS256 erişim belirteçlerini doğrulamak için kullanılmaya devam eder.

View File

@@ -0,0 +1,413 @@
---
title: Kurulum
icon: gear
---
# Konfigürasyon Yönetimi
<Warning>
**İlk kez mi kuruyorsunuz?** Twenty'yi çalıştırmak için [Docker Compose kurulum kılavuzunu](/l/tr/developers/self-host/capabilities/docker-compose) izleyin, ardından yapılandırma için buraya dönün.
</Warning>
Twenty, farklı dağıtım ihtiyaçlarına uygun **iki yapılandırma modu** sunar:
**Yönetici paneli erişimi:** Yalnızca yönetici ayrıcalıklarına sahip kullanıcılar (`canAccessFullAdminPanel: true`) konfigürasyon arayüzüne erişebilir.
## 1. Yönetici Paneli Konfigürasyonu (Varsayılan)
```bash
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # default
```
**Çoğu konfigürasyon, kurulumdan sonra arayüz üzerinden gerçekleştirilir**:
1. Twenty örneğinize erişin (genellikle `http://localhost:3000`)
2. **Ayarlar / Yönetici Paneli / Konfigürasyon Değişkenleri** yoluna gidin
3. Entegrasyonları, e-postayı, depolamayı ve daha fazlasını yapılandırın
4. Değişiklikler hemen yürürlüğe girer (çok konteynerli dağıtımlarda 15 saniye içinde)
<Warning>
**Çok Konteynerli Dağıtımlar:** Veritabanı yapılandırması (`IS_CONFIG_VARIABLES_IN_DB_ENABLED=true`) kullanıldığında, hem sunucu hem de worker konteynerleri aynı veritabanından okur. Yönetici panelindeki değişiklikler her ikisini de otomatik olarak etkiler ve konteynerler arasında ortam değişkenlerini çoğaltma ihtiyacını ortadan kaldırır (altyapı değişkenleri hariç).
</Warning>
**Yönetici paneli aracılığıyla yapılandırabileceğiniz:**
* **Kimlik Doğrulama** - Google/Microsoft OAuth, şifre ayarları
* **E-posta** - SMTP ayarları, şablonlar, doğrulama
* **Depolama** - S3 yapılandırması, yerel depolama yolları
* **Entegrasyonlar** - Gmail, Google Calendar, Microsoft hizmetleri
* **İş Akışı ve Hız Limiti** - Yürütme sınırları, API daraltma
* **Ve daha fazlası...**
![Yönetici Paneli Konfigürasyon Değişkenleri](/images/user-guide/setup/admin-panel-config-variables.png)
<Warning>
Her bir değişken, yönetici panelinizde **Ayarlar → Yönetici Paneli → Konfigürasyon Değişkenleri** altında açıklamalarla belgelenir.
Veritabanı bağlantıları (`PG_DATABASE_URL`), sunucu URL'leri (`SERVER_URL`) ve gizli anahtarlar (`ENCRYPTION_KEY`, `FALLBACK_ENCRYPTION_KEY`) gibi bazı altyapı ayarları yalnızca `.env` dosyası aracılığıyla yapılandırılabilir.
[Tam teknik referans →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
</Warning>
## Şifreleme anahtarları
Twenty yalnızca ortam değişkenleriyle yapılandırılan iki şifreleme anahtarı kullanır:
| Değişken | Amaç | Zorunlu |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `ENCRYPTION_KEY` | Dinlenik durumdaki gizli verileri şifrelemek için kullanılan birincil anahtar (OAuth belirteçleri, uygulama değişkenleri, imzalama anahtarlarının özel anahtarları, TOTP sırları, hassas yapılandırma değerleri). | Yeni kurulumlar için evet (eski kurulumlar bunun yerine aşağıda açıklandığı gibi `APP_SECRET`'e dayanıyor olabilir) |
| `FALLBACK_ENCRYPTION_KEY` | Yalnızca doğrulama için kullanılan anahtar. Döndürme sırasında, mevcut satırların şifresinin çözülebilir kalması için *önceki* `ENCRYPTION_KEY` değerine ayarlanır. | Yalnızca döndürme sırasında |
Geriye dönük uyumluluk için, `ENCRYPTION_KEY` ayarlanmadıysa, Twenty dinlenik durumdaki şifreleme için `APP_SECRET`'e geri döner — bu, daha eski dağıtımlardaki eski davranışla eşleşir. Yeni kurulumlarda her zaman özel bir `ENCRYPTION_KEY` ayarlanmalıdır.
Değerleri `openssl rand -base64 32` ile oluşturun ve onları güvenli bir yerde saklayın (bir gizli yönetim sistemi, mühürlü yapılandırma vb.). `ENCRYPTION_KEY`'in kaybedilmesi, veritabanında saklanan tüm gizli bilgilere erişimin kaybedilmesi anlamına gelir.
`ENCRYPTION_KEY`'i kesinti olmadan döndürmek için [Anahtar döndürme rehberi](/l/tr/developers/self-host/capabilities/key-rotation) bölümüne bakın.
## 2. Yalnızca Ortam Değişkenleriyle Yapılandırma
```bash
IS_CONFIG_VARIABLES_IN_DB_ENABLED=false
```
**Tüm konfigürasyon `.env` dosyaları aracılığıyla yönetilir:**
1. `.env` dosyanızda `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` değerini ayarlayın
2. Tüm konfigürasyon değişkenlerini `.env` dosyanıza ekleyin
3. Değişikliklerin etkin olması için kapları yeniden başlatın
4. Yönetici paneli mevcut değerleri gösterir ancak bunları değiştiremez
## Çoklu Çalışma Alanı Modu
Varsayılan olarak, Twenty **tek çalışma alanı modunda** çalışır — kuruluşunuz için tek bir CRM örneğine ihtiyaç duyulan çoğu kendi barındırmalı dağıtım için idealdir.
### Tek Çalışma Alanı Modu (Varsayılan)
```bash
IS_MULTIWORKSPACE_ENABLED=false # default
```
* Her Twenty örneği başına bir çalışma alanı
* İlk kullanıcı otomatik olarak tam ayrıcalıklara sahip yönetici olur (`canImpersonate` ve `canAccessFullAdminPanel`)
* İlk çalışma alanı oluşturulduktan sonra yeni kayıtlar devre dışı bırakılır
* Basit URL yapısı: `https://your-domain.com`
### Çoklu Çalışma Alanı Modunu Etkinleştirme
```bash
IS_MULTIWORKSPACE_ENABLED=true
DEFAULT_SUBDOMAIN=app # default value
```
Aynı Twenty örneği üzerinde birden fazla bağımsız ekibin kendi çalışma alanlarına ihtiyaç duyduğu SaaS benzeri dağıtımlar için çoklu çalışma alanı modunu etkinleştirin.
**Tek çalışma alanı modundan temel farklar:**
* Aynı örnek üzerinde birden fazla çalışma alanı oluşturulabilir
* Her çalışma alanı kendi alt alan adını alır (ör. `sales.your-domain.com`, `marketing.your-domain.com`)
* Kullanıcılar `{DEFAULT_SUBDOMAIN}.your-domain.com` adresinde kaydolur ve oturum açar (ör. `app.your-domain.com`)
* Otomatik yönetici ayrıcalıkları yoktur — her çalışma alanındaki ilk kullanıcı normal bir kullanıcıdır
* Alt alan adı ve özel alan adı gibi çalışma alanına özgü ayarlar, çalışma alanı ayarlarında kullanılabilir hale gelir
<Warning>
**Yalnızca ortam üzerinden ayar:** `IS_MULTIWORKSPACE_ENABLED` yalnızca `.env` dosyası üzerinden yapılandırılabilir ve yeniden başlatma gerektirir. Yönetici paneli üzerinden değiştirilemez.
</Warning>
### Çoklu Çalışma Alanı için DNS Yapılandırması
Çoklu çalışma alanı modu kullanılırken, dinamik alt alan adı oluşturmayı sağlamak için DNS'inizi bir wildcard kaydıyla yapılandırın:
```
*.your-domain.com -> your-server-ip
```
Bu, manuel DNS yapılandırması olmadan yeni çalışma alanları için otomatik alt alan yönlendirmesini etkinleştirir.
### Çalışma Alanı Oluşturmayı Kısıtlama
Çoklu çalışma alanı modunda, yeni çalışma alanı oluşturabilecek kişileri sınırlamak isteyebilirsiniz:
```bash
IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS=true
```
Etkinleştirildiğinde, yalnızca `canAccessFullAdminPanel` yetkisine sahip kullanıcılar ek çalışma alanları oluşturabilir. Kullanıcılar yine de ilk kayıt sırasında ilk çalışma alanlarını oluşturabilir.
## Gmail ve Google Takvim Entegrasyonu
### Google Cloud Projesi Oluştur
1. [Google Cloud Konsolu](https://console.cloud.google.com/) adresine gidin
2. Yeni bir proje oluşturun veya mevcut bir projeyi seçin
3. Bu API'leri etkinleştirin:
* [Gmail API](https://console.cloud.google.com/apis/library/gmail.googleapis.com)
* [Google Calendar API](https://console.cloud.google.com/apis/library/calendar-json.googleapis.com)
* [Kişiler API](https://console.cloud.google.com/apis/library/people.googleapis.com)
### OAuth'u Yapılandır
1. [Kimlik Bilgileri](https://console.cloud.google.com/apis/credentials) sayfasına gidin
2. OAuth 2.0 İstemci Kimliği Oluştur
3. Bu yönlendirme URI'lerini ekleyin:
* `https://{your-domain}/auth/google/redirect` (SSO için)
* `https://{your-domain}/auth/google-apis/get-access-token` (entegrasyonlar için)
### Twenty'de Yapılandır
1. **Ayarlar → Yönetici Paneli → Konfigürasyon Değişkenleri** bölümüne gidin
2. **Google Auth** bölümünü bulun
3. Bu değişkenleri ayarlayın:
* `MESSAGING_PROVIDER_GMAIL_ENABLED=true`
* `CALENDAR_PROVIDER_GOOGLE_ENABLED=true`
* `AUTH_GOOGLE_CLIENT_ID={client-id}`
* `AUTH_GOOGLE_CLIENT_SECRET={client-secret}`
* `AUTH_GOOGLE_CALLBACK_URL=https://{your-domain}/auth/google/redirect`
* `AUTH_GOOGLE_APIS_CALLBACK_URL=https://{your-domain}/auth/google-apis/get-access-token`
<Warning>
**Çevre-yalnızca modu:** `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` ayarlarsanız, bu değişkenleri `.env` dosyanıza ekleyin.
</Warning>
**Gerekli kapsamlar** (otomatik yapılandırılmış):
[İlgili kaynak kodunu görün](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes.ts#L4-L10)
* `https://www.googleapis.com/auth/calendar.events`
* `https://www.googleapis.com/auth/gmail.readonly`
* `https://www.googleapis.com/auth/profile.emails.read`
### Uygulamanız test modunda ise
Uygulamanız test modunda ise, projenize test kullanıcıları eklemeniz gerekecek.
[OAuth onay ekranı](https://console.cloud.google.com/apis/credentials/consent) altında "Test kullanıcılar" bölümüne test kullanıcılarınızı ekleyin.
## Microsoft 365 Entegrasyonu
<Warning>
Kullanıcıların Takvim ve Mesajlaşma API'sini kullanabilmesi için bir [Microsoft 365 Lisansı](https://admin.microsoft.com/Adminportal/Home) olması gerekir. Biri olmadan hesaplarını Twenty'de eşitleyemeyecekler.
</Warning>
### Microsoft Azure'da bir proje oluştur
[Microsoft Azure](https://portal.azure.com/#view/Microsoft_AAD_IAM/AppGalleryBladeV2) platformunda bir proje oluşturmanız ve kimlik bilgilerini almanız gerekecek.
### API'leri etkinleştir
Microsoft Azure Konsolunda "İzinler" altında aşağıdaki API'leri etkinleştirin:
* Microsoft Graph: Mail.ReadWrite
* Microsoft Graph: Mail.Send
* Microsoft Graph: Calendars.Read
* Microsoft Graph: User.Read
* Microsoft Graph: openid
* Microsoft Graph: email
* Microsoft Graph: profil
* Microsoft Graph: offline_access
<Note>
"Mail.ReadWrite" ve "Mail.Send", yalnızca iş akışı eylemlerimizi kullanarak e-posta göndermek istiyorsanız zorunludur. Yalnızca e-posta almak istiyorsanız "Mail.Read" kullanabilirsiniz.
</Note>
<Note>
İş akışı eylemlerimizi kullanarak takvim etkinlikleri oluşturmak istiyorsanız "Calendars.ReadWrite" zorunludur. Yalnızca takvim etkinliklerini eşzamanlamak istiyorsanız "Calendars.Read" kullanın.
</Note>
### Yetkili yönlendirme URI'leri
Projenize aşağıdaki yönlendirme URI'lerini eklemeniz gerekir:
* `https://{your-domain}/auth/microsoft/redirect` Microsoft SSO kullanmak istiyorsanız
* `https://{your-domain}/auth/microsoft-apis/get-access-token`
### Twenty'de Yapılandır
1. **Ayarlar → Yönetici Paneli → Konfigürasyon Değişkenleri** bölümüne gidin
2. **Microsoft Auth** bölümünü bulun
3. Bu değişkenleri ayarlayın:
* `MESSAGING_PROVIDER_MICROSOFT_ENABLED=true`
* `CALENDAR_PROVIDER_MICROSOFT_ENABLED=true`
* `AUTH_MICROSOFT_ENABLED=true`
* `AUTH_MICROSOFT_CLIENT_ID={client-id}`
* `AUTH_MICROSOFT_CLIENT_SECRET={client-secret}`
* `AUTH_MICROSOFT_CALLBACK_URL=https://{your-domain}/auth/microsoft/redirect`
* `AUTH_MICROSOFT_APIS_CALLBACK_URL=https://{your-domain}/auth/microsoft-apis/get-access-token`
<Warning>
**Çevre-yalnızca modu:** `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` ayarlarsanız, bu değişkenleri `.env` dosyanıza ekleyin.
</Warning>
### Kapsamları yapılandır
[İlgili kaynak kodunu görün](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes.ts#L2-L9)
* 'openid'
* 'e-posta'
* 'profil'
* 'offline_access'
* 'Mail.ReadWrite'
* 'Mail.Send'
* 'Calendars.Read'
### Uygulamanız test modunda ise
Uygulamanız test modunda ise, projenize test kullanıcıları eklemeniz gerekecek.
Test kullanıcılarınızı "Kullanıcılar ve gruplar" bölümüne ekleyin.
## Takvim ve Mesajlaşma için Arka Plan İşleri
Gmail, Google Takvim veya Microsoft 365 entegrasyonlarını yapılandırdıktan sonra, verileri senkronize edecek arka plan işlerini başlatmanız gerekir.
İşçi kabınızdaki aşağıdaki tekrarlanan işleri kaydedin:
```bash
# from your worker container
yarn command:prod cron:messaging:messages-import
yarn command:prod cron:messaging:message-list-fetch
yarn command:prod cron:calendar:calendar-event-list-fetch
yarn command:prod cron:calendar:calendar-events-import
yarn command:prod cron:messaging:ongoing-stale
yarn command:prod cron:calendar:ongoing-stale
yarn command:prod cron:workflow:automated-cron-trigger
```
## E-posta Yapılandırması
1. **Ayarlar → Yönetici Paneli → Konfigürasyon Değişkenleri** bölümüne gidin
2. **E-posta** bölümünü bulun
3. SMTP ayarlarınızı yapılandırın:
<Tabs>
<Tab title="Gmail">
[Uygulama Parolası](https://support.google.com/accounts/answer/185833) sağlamanız gerekecek.
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=smtp.gmail.com
* EMAIL_SMTP_PORT=465
* EMAIL_SMTP_USER=gmail_e-posta_adresi
* EMAIL_SMTP_PASSWORD='gmail_uygulama_parolası'
</Tab>
<Tab title="Office365">
2FA etkinleştirilmişse, [Uygulama Parolası](https://support.microsoft.com/en-us/account-billing/manage-app-passwords-for-two-step-verification-d6dc8c6d-4bf7-4851-ad95-6d07799387e9) sağlamanız gerekeceğini unutmayın.
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=smtp.office365.com
* EMAIL_SMTP_PORT=587
* EMAIL_SMTP_USER=office365_e-posta_adresi
* EMAIL_SMTP_PASSWORD='office365_parola'
</Tab>
<Tab title="smtp4dev">
**smtp4dev**, geliştirme ve test için sahte bir SMTP e-posta sunucusudur.
* smtp4dev imajını çalıştırın: `docker run --rm -it -p 8090:80 -p 2525:25 rnwood/smtp4dev`
* smtp4dev kullanıcı arayüzüne şu adresten erişin: [http://localhost:8090](http://localhost:8090)
* Aşağıdaki değişkenleri ayarlayın:
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=localhost
* EMAIL_SMTP_PORT=2525
</Tab>
</Tabs>
<Warning>
**Çevre-yalnızca modu:** `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` ayarlarsanız, bu değişkenleri `.env` dosyanıza ekleyin.
</Warning>
## S3 Depolama
<Warning>
Varsayılan olarak, Twenty yüklenen dosyaları yerel dosya sisteminde depolar. Üretim dağıtımları için S3 veya S3 uyumlu bir hizmet (MinIO, DigitalOcean Spaces vb.) kullanın. dosyaların kapsayıcı yeniden başlatmaları arasında kalıcı olmasını ve birden çok sunucu örneği arasında ölçeklenmesini sağlamak için.
</Warning>
`STORAGE_TYPE=S_3` değerini ayarlayın ve `STORAGE_S3_*` değişkenlerini yönetici paneli veya `.env` üzerinden yapılandırın. S3 değişkenlerinin tam listesi için [config-variables.ts referansına](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts) bakın.
S3'ü CORS'a bağlı özelliklerle kullanırken (örn. tarayıcı içi dosya indirmeleri), bucket'ınızın CORS yapılandırmasında Twenty frontend origin'inize izin verdiğinden emin olun.
## Mantıksal İşlevler ve Kod Yorumlayıcısı
Twenty, iş akışları için mantıksal işlevleri ve yapay zekâ veri analizi için kod yorumlayıcısını destekler. Her ikisi de kullanıcı tarafından sağlanan kodu çalıştırır ve güvenlik için açık bir yapılandırma gerektirir.
### Güvenlik Varsayılanları
**Üretimde (NODE_ENV=production):** Hem mantıksal işlevler hem de kod yorumlayıcı varsayılan olarak **Devre Dışı**dır. Bu özelliklere ihtiyacınız varsa, onları `LOGIC_FUNCTION_TYPE` ve `CODE_INTERPRETER_TYPE` ile açıkça etkinleştirmeniz gerekir.
**Geliştirmede (NODE_ENV=development):** Yerel olarak çalıştırırken kolaylık olması için her ikisinin varsayılanı **LOCAL**dır.
<Warning>
**Güvenlik Notu:** Yerel sürücü (`LOGIC_FUNCTION_TYPE=LOCAL` veya `CODE_INTERPRETER_TYPE=LOCAL`), herhangi bir sandbox olmadan kodu ana makinede doğrudan bir Node.js sürecinde çalıştırır. Yalnızca geliştirirken güvenilen kod için kullanılmalıdır. Üretim dağıtımlarında güvenilmeyen kodu işlerken, `LOGIC_FUNCTION_TYPE=LAMBDA` veya `CODE_INTERPRETER_TYPE=E2B` (korumalı alanla) kullanın ya da bunları devre dışı bırakın.
</Warning>
### Mantıksal İşlevler - Kullanılabilir Sürücüler
| Sürücü | Ortam Değişkeni | Kullanım alanı | Güvenlik Düzeyi |
| ---------- | ------------------------------ | ---------------------------------------------- | ------------------------------------ |
| Devre dışı | `LOGIC_FUNCTION_TYPE=DISABLED` | Mantıksal işlevleri tamamen devre dışı bırakın | Uygulanamaz |
| Yerel | `LOGIC_FUNCTION_TYPE=LOCAL` | Geliştirme ve güvenilir ortamlar | Düşük (sandbox yok) |
| Lambda | `LOGIC_FUNCTION_TYPE=LAMBDA` | Güvenilmeyen kodla üretim | Yüksek (donanım düzeyinde izolasyon) |
### Mantıksal İşlevler - Önerilen Yapılandırma
**Geliştirme için:**
```bash
LOGIC_FUNCTION_TYPE=LOCAL # default when NODE_ENV=development
```
**Üretim için (AWS):**
```bash
LOGIC_FUNCTION_TYPE=LAMBDA
LOGIC_FUNCTION_LAMBDA_REGION=us-east-1
LOGIC_FUNCTION_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID=your-access-key
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
```
**Mantıksal işlevleri devre dışı bırakmak için:**
```bash
LOGIC_FUNCTION_TYPE=DISABLED # default when NODE_ENV=production
```
### Kod Yorumlayıcısı - Kullanılabilir Sürücüler
| Sürücü | Ortam Değişkeni | Kullanım Senaryosu | Güvenlik Düzeyi |
| ---------- | -------------------------------- | --------------------------------------------- | --------------------------------- |
| Devre dışı | `CODE_INTERPRETER_TYPE=DISABLED` | Yapay zekâ kod yürütmesini devre dışı bırakın | Uygulanamaz |
| Yerel | `CODE_INTERPRETER_TYPE=LOCAL` | Yalnızca geliştirme için | Düşük (sandbox yok) |
| E2B | `CODE_INTERPRETER_TYPE=E_2_B` | Korumalı alanlı yürütmeyle üretim | Yüksek (yalıtılmış korumalı alan) |
<Note>
`LOGIC_FUNCTION_TYPE=DISABLED` veya `CODE_INTERPRETER_TYPE=DISABLED` kullanıldığında, herhangi bir yürütme girişimi bir hata döndürür. Bu, Twenty'yi bu yetenekler olmadan çalıştırmak istiyorsanız kullanışlıdır.
</Note>
## API ve ön uç ayrı ana makinelerde
Yalnızca arka ucun (back-end) ön ucu (front-end) sunmasına izin vermiyorsanız geçerlidir. Aynı kökene (same-origin) sahip dağıtımlar için burada hiçbir şeye gerek yoktur.
API ile iletişim kuran her tarayıcı kökenini (origin) virgülle ayırarak belirtin. `SERVER_URL`
ve `FRONTEND_URL` zaten güvenilirdir. Bir tarayıcı bildirilmeyen bir kökenden
çağrı yaptığında sunucu bir uyarı kaydeder.
```bash
AUTH_COOKIE_ALLOWED_ORIGINS=https://app.example.com
```
<Warning>
Ön uç her istekte kimlik bilgileri gönderir ve tarayıcılar, bildirilmemiş bir kökenden gelen kimlik bilgileri içeren
yanıtı reddeder, bu nedenle uygulama,
bunu ayarlayana kadar yüklenemez.
</Warning>
İki ana makine farklı kayıt edilebilir etki alanlarındaysa (sadece farklı
alt etki alanları değilse), ayrıca HTTPS gerektiren `AUTH_COOKIE_SAME_SITE=none` değişkenini de ayarlayın.
Çoklu çalışma alanı etkinleştirildiğinde, burada her çalışma alanının alt alan adını listelemeyin.
Bunun yerine, API yollarını (`/graphql`, `/metadata`, `/rest`, `/auth/`,
`/client-config`, `/file`) her çalışma alanı ana makine adında arka uca yönlendirin; böylece her çalışma alanı aynı kökende kalır — Twenty Cloud tam olarak böyle çalışır.

View File

@@ -0,0 +1,254 @@
---
title: Sorun Giderme
icon: wrench
---
## Sorun Giderme
Geliştirme ortamını kurarken, sürüm yükseltirken veya kendi sunucunuzda barındırırken herhangi bir sorunla karşılaşırsanız, işte yaygın sorunlar için bazı çözümler.
### Kendi Kendine Barındırma
#### İlk kurulum, `password authentication failed for user "postgres"` hatasıyla sonuçlanıyor.
🚨 **ÖNEMLİ: Bu çözüm SADECE yeni kurulumlar için GEÇERLİDİR** 🚨
Mevcut bir Twenty örneğiniz varsa ve üretim verileri varsa, bu adımları **TAKİP ETMEYİN**, çünkü bu veritabanınızı kalıcı olarak silecektir!
Twenty'yi ilk kez kurarken, varsayılan veritabanı parolasını değiştirmek isteyebilirsiniz.
İlk kurulum sırasında belirlediğiniz parola, veritabanı hacminde kalıcı olarak depolanır. Daha sonra bu parolayı yapılandırmanızda, eski hacmi kaldırmadan değiştirmeye çalışırsanız, kimlik doğrulama hataları alırsınız çünkü veritabanı hala orijinal parolayı kullanmaktadır.
⚠️ UYARI: Aşağıdaki adımlar veritabanındaki tüm verileri KALICI OLARAK SİLECEKTİR! ⚠️
Bu yalnızca önemli veri içermeyen yeni kurulumlar için geçerlidir.
`PG_DATABASE_PASSWORD` değerini güncellemek için şunları yapmanız gerekir:
```sh
# Update the PG_DATABASE_PASSWORD in .env
docker compose down --volumes
docker compose up -d
```
#### CR satır sonları bulundu [Windows]
Bu, Windows satır sonu karakterleri ile Git yapılandırmanızın birleşimi nedeniyle olur. Deneyin:
```
git config --global core.autocrlf false
```
Ardından depoyu silin ve tekrar klonlayın.
#### Eksik meta veri şeması
Twenty kurulumu sırasında, postgres veritabanınızı doğru şemalar, uzantılar ve kullanıcılarla hazırlamanız gerekir.
Bu hazırlığı başarıyla tamamlarsanız, veritabanınızda `varsayılan` ve `meta veri` şemalarına sahip olmalısınız.
Eğer yoksa, bilgisayarınızda birden fazla PostgreSQL örneği çalışmadığından emin olun.
#### Modül 'twenty-emails' veya buna karşılık gelen tür açıklamaları bulunamıyor.
Veritabanı başlatma işlemini çalıştırmadan önce `twenty-emails` paketini (`npx nx run twenty-emails:build`) derlemeniz gerekir.
#### Eksik twenty-x paketi
Kök dizinde yarn çalıştırdığınızdan ve ardından `npx nx server:dev twenty-server` çalıştırdığınızdan emin olun. Bu hala çalışmıyorsa eksik paketi manuel olarak derlemeyi deneyin.
#### Kaydettiğinde lint çalışmıyor
Bu, kurulu Oxc uzantısıyla (`oxc.oxc-vscode`) kutudan çıktığı anda çalışmalıdır. Bu işe yaramazsa, VS Code ayarlarınıza (geliştirme konteyner kapsamında) bunu eklemeyi deneyin:
```
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
}
```
#### `npx nx start` veya `npx nx start twenty-front` çalıştırılırken, yetersiz bellek hatası oluşuyor
`npx nx start` komutu yerine yalnızca ihtiyaç duyduğunuz servisleri çalıştırın. Örneğin, sunucuda çalışıyorsanız yalnızca `npx nx worker twenty-server` çalıştırın.
**Bu işe yaramazsa:**
WSL'de yalnızca `npx nx run twenty-server:start` çalıştırmayı denediyseniz ve aşağıdaki bellek hatasıyla karşılaşıyorsanız:
`FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory`
Geçici bir çözüm olarak, aşağıdaki komutu terminalinizde çalıştırabilir veya otomatik olarak uygulanması için `.bashrc` profilinize ekleyebilirsiniz:
`export NODE_OPTIONS="--max-old-space-size=8192"`
\--max-old-space-size=8192 bayrağı, Node.js yığını için 8GB'lık üst sınır ayarlar; kullanım uygulama talebiyle ölçeklenir.
Referans: https://stackoverflow.com/questions/56982005/where-do-i-set-node-options-max-old-space-size-2048
**Bu işe yaramazsa:**
Makinenizin RAM'inin çoğunu hangi işlemlerin kullandığını araştırın. Twenty'de bazı VS Code uzantılarının çok fazla RAM kullandığını fark ettik, bu yüzden onları geçici olarak devre dışı bıraktık.
**Bu işe yaramazsa:**
Makinenizi yeniden başlatmak, hayalet işlemleri temizlemeye yardımcı olur.
#### `npx nx start` çalıştırırken günlüklerde garip [0] ve [1] var
Bu, `npx nx start` komutunun kaputun altında daha fazla komut çalıştırması nedeniyle bekleniyor.
#### E-postalar gönderilmiyor
Çoğu zaman bunun nedeni `worker`ın arka planda çalışmıyor olmasıdır. Çalıştırmayı deneyin
```
npx nx worker twenty-server
```
#### Microsoft 365 hesabımı Twenty'ye bağlayamıyorum
Çoğu zaman yöneticiniz hesabınıza Microsoft 365 Lisansı sağlamadığından kaynaklanır. Denetleyin [https://admin.microsoft.com/](https://admin.microsoft.com/Adminportal/Home).
Bir `AADSTS50020` hata kodu alırsanız, muhtemelen kişisel bir Microsoft hesabı kullanıyorsunuzdur. Bu henüz desteklenmiyor. Daha fazla bilgi [burada](https://learn.microsoft.com/fr-fr/troubleshoot/entra/entra-id/app-integration/error-code-aadsts50020-user-account-identity-provider-does-not-exist)
#### `yarn` çalıştırılırken konsolda uyarılar görünüyor
Uyarılar, `package.json`'da açıkça belirtilmeyen ek bağımlılıkları çekildiğini bildiriyor, bu yüzden kritik bir hata çıkmadığı sürece her şey beklendiği gibi çalışmalıdır.
#### Bir kullanıcı giriş sayfasına eriştiğinde, günlüklerde bir çalışma alanına erişmeye çalışan yetkisiz bir kullanıcıya ilişkin hata görünüyor
Bu beklenen bir durumdur; çünkü kullanıcı oturum açmadığında yetkisizdir ve kimliği doğrulanmamıştır.
#### Çalışanınızın çalışıp çalışmadığını nasıl kontrol edersiniz?
* [webhook-test.com](https://webhook-test.com/) adresine gidin ve **Benzersiz Webhook URL'nizi** kopyalayın.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/self-hosting/webhook-test.jpg" alt="Webhook testi" />
</div>
* Twenty uygulamanızı açın, `/settings` sayfasına gidin ve ekranın sol alt köşesinde bulunan **Gelişmiş** seçeneğini etkinleştirin.
* Yeni bir webhook oluşturun.
* **Benzersiz Webhook URL'nizi** Twenty'deki **Endpoint URL** alanına yapıştırın. **Filtreleri** `Şirketler` ve `Oluşturuldu` olarak ayarlayın.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/self-hosting/webhook-settings.jpg" alt="Webhook ayarları" />
</div>
* `/objects/companies` sayfasına gidin ve yeni bir şirket kaydı oluşturun.
* [webhook-test.com](https://webhook-test.com/) adresine geri dönün ve yeni bir **POST isteği** alınıp alınmadığını kontrol edin.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/self-hosting/webhook-test-result.jpg" alt="Webhook test sonucu" />
</div>
* Bir **POST isteği** alıyorsanız, çalışanınız başarıyla çalışıyor demektir. Aksi halde, çalışanınızı sorun giderme yapmanız gerekir.
#### Ön uç başlatılamıyor ve şu hatayı döndürüyor: TS5042: 'project' seçeneği komut satırında kaynak dosyalarla birlikte kullanılamaz
Aşağıdaki örnekte gösterildiği gibi `packages/twenty-ui/vite-config.ts` içindeki checker eklentisini yorum satırı hâline getirin:
```
plugins: [
react({ jsxImportSource: 'react' }),
tsconfigPaths(),
svgr(),
dts(dtsConfig),
// checker(checkersConfig),
wyw({
include: [
'**/OverflowingTextWithTooltip.tsx',
'**/Chip.tsx',
'**/Tag.tsx',
'**/Avatar.tsx',
'**/AvatarChip.tsx',
],
babelOptions: {
presets: ['@babel/preset-typescript', '@babel/preset-react'],
},
}),
],
```
#### Yönetim paneline erişilemiyor
Yönetim paneline erişim sağlamak için veritabanı konteynerinde `UPDATE core."user" SET "canAccessFullAdminPanel" = TRUE WHERE email = 'you@yourdomain.com';` komutunu çalıştırın.
#### Bir iş akışı çalıştırılırken, iş akışının çalıştırılması şu hatayla başarısız oluyor: "Mantık işlevi yürütme devre dışı. Etkinleştirmek için LOGIC_FUNCTION_TYPE değerini LOCAL veya LAMBDA olarak ayarlayın."
Üretimde, mantık işlevleri varsayılan olarak devre dışıdır. Bunları etkinleştirmek için `LOGIC_FUNCTION_TYPE` ortam değişkenini `LOCAL` veya `LAMBDA` olarak ayarlayın. Bu, ortam değişkenleri aracılığıyla veya yönetici panelindeki veritabanı değişkenleri üzerinden yapılandırılabilir. Ayrıntılar için [Mantık İşlevleri kurulum kılavuzu](/l/tr/developers/self-host/capabilities/setup#logic-functions-available-drivers) bölümüne bakın.
#### AI sohbetinde bir mesaj yazdığımda hemen yanıt vermiyor ve yanıtı görmek için sayfayı yenilemem gerekiyor
Bu, ters proxy'nizde akış tabanlı API yanıtları için gereken yapılandırmanın eksik olduğu anlamına gelir. Aşağıda bir örnek Nginx yapılandırması bulunmaktadır:
```
location ~* ^/api/.*stream|\/graphql\/stream {
proxy_pass http://twenty_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
gzip off;
#chunked_transfer_encoding off;
add_header X-Accel-Buffering no always;
}
```
### 1 tıklamayla Docker Compose
#### Giriş Yapılamıyor
Kurulumdan sonra giriş yapamıyorsanız:
1. Aşağıdaki komutları çalıştırın:
```bash
docker exec -it twenty-server-1 yarn
docker exec -it twenty-server-1 npx nx database:reset --configuration=no-seed
```
2. Docker konteynerlerini yeniden başlatın:
```bash
docker compose down
docker compose up -d
```
`database:reset` komutunun, veritabanınızı tamamen sileceğini ve sıfırdan yeniden oluşturacağını unutmayın.
#### Ters Proxy'nin Arkasında Bağlantı Sorunları
Eğer Twenty'yi ters proxy'nin arkasında çalıştırıyorsanız ve bağlantı sorunları yaşıyorsanız:
1. **SERVER_URL'yi doğrulayın:**
`.env` dosyanızdaki `SERVER_URL`'nin dış erişim URL'nizle eşleştiğinden, SSL etkinse `https`'i de içerdiğinden emin olun.
2. **Ters Proxy Ayarlarını Kontrol Edin:**
* Ters proxy'nizin Twenty sunucusuna talepleri düzgün bir şekilde yönlendirdiğinden emin olun.
* `X-Forwarded-For` ve `X-Forwarded-Proto` gibi başlıkların doğru şekilde ayarlandığından emin olun.
3. **Servisleri Yeniden Başlatın:**
Değişiklik yaptıktan sonra hem ters proxy'yi hem de Twenty konteynerlerini yeniden başlatın.
#### Resim yüklerken hata - izin reddedildi
Veri klasörünün sahipliğini ana bilgisayarda root'tan başka bir kullanıcıya ve gruba geçirmek bu sorunu çözer.
#### İş akışında Logic işlevini test ederken 'Dosya Bulunamadı' hatası
Eksik bağımlılıklar sorununu çözmek için twenty-server container'ınızda `yarn command:prod application:rebuild-default-deps` komutunu çalıştırın.
## Yardım Almak
Bu kılavuzda ele alınmayan sorunlarla karşılaşırsanız:
* Günlükleri Kontrol Edin:
Hata mesajları için konteyner günlüklerine bakın:
```bash
docker compose logs
```
* Topluluk Desteği:
[Twenty topluluğuna](https://github.com/twentyhq/twenty/issues) veya [destek kanallarına](https://discord.gg/cx5n4Jzs57) yardım için ulaşın.

View File

@@ -0,0 +1,106 @@
---
title: Yükseltme rehberi
icon: arrow-up-right-dots
---
## Genel kılavuzlar
**Yükseltme işlemine başlamadan önce veritabanınızı her zaman yedekleyin**; bunun için şunu çalıştırın:
```bash
docker exec -it {db_container_name_or_id} pg_dumpall -U {postgres_user} > databases_backup.sql
```
Yedekten geri yüklemek için:
```bash
cat databases_backup.sql | docker exec -i {db_container_name_or_id} psql -U {postgres_user}
```
Docker Compose kullanıyorsanız, aşağıdaki adımları izleyin:
1. Twenty'yi durdurun: `docker compose down`
2. `docker-compose.yml` dosyanızın yanındaki `.env` dosyasında `TAG` değerini değiştirin
3. Twenty'yi başlatın: `docker compose up -d`
Sunucu, başlatma sırasında gerekli tüm yükseltme migrasyonlarını otomatik olarak çalıştırır. Manuel bir komut gerekmez.
## v2.34+ sürümüne yükseltme — En az PostgreSQL 15
Twenty v2.34, PostgreSQL 15 veya daha yeni bir sürüm gerektirir. Resmî Docker Compose yapılandırması PostgreSQL 16 kullanır. Örneğiniz haricî olarak yönetilen bir veritabanına bağlanıyorsa v2.34 sunucusunu başlatmadan önce onu yükseltin.
## Sürümler arası yükseltmeler (v1.23+)
Twenty, **v1.23** sürümünden itibaren sürümler arası yükseltmeleri destekler. Desteklenen herhangi bir sürümden, ara sürümlerin her birine tek tek geçmeden doğrudan en son sürüme atlayabilirsiniz.
Örneğin, v1.23'ten doğrudan v2.0'a yükseltme tamamen desteklenir.
## v2.5+ sürümüne yükseltme — bekleme hâlindeki şifreleme zarfı
**v2.5** ile birlikte, Twenty beklemede olan gizli verileri (OAuth belirteçleri, uygulama değişkenleri, imzalama için kullanılan özel anahtarlar, hassas yapılandırma değerleri, TOTP sırları) sürümlendirilmiş ve `ENCRYPTION_KEY` (veya `ENCRYPTION_KEY` ayarlı değilse `APP_SECRET`) ile şifrelenmiş bir `enc:v2:` zarfının içinde saklar.
v2.5 sürümündeki ilk açılış, mevcut satırları yeni zarfa **geri dolduran** yavaş yükseltme komutlarını çalıştırır. Bunlar idempotandır — sunucuyu durdurup yeniden başlattığınızda kaldığı yerden devam eder — ancak büyük veritabanlarında biraz zaman alabilirler. İlerlemeyi `upgrade:status` ile izleyebilirsiniz.
Geri doldurmanın, en başından itibaren satırları onun altında yazabilmesi için, v2.5 yükseltmesinden **önce** özel bir `ENCRYPTION_KEY` ayarlamalısınız. Geri doldurmadan sonra anahtarları değiştirmek bir [anahtar döndürme](/l/tr/developers/self-host/capabilities/key-rotation) gerektirir.
## Gizli verilerin ve imzalama anahtarlarının döndürülmesi
`ENCRYPTION_KEY` döndürme, JWT imzalama anahtarını döndürme veya sızdırılmış bir imzalama anahtarını iptal etme gibi günlük operasyonel görevler için özel [Anahtar döndürme kılavuzu](/l/tr/developers/self-host/capabilities/key-rotation) bölümüne bakın.
## Yükseltme durumunu kontrol etme
`upgrade:status` komutu, örneğinizin ve çalışma alanı migrasyonlarının mevcut durumunu incelemenizi sağlar. Yükseltme sorunlarında hata ayıklamak veya bir destek talebi oluştururken kullanışlıdır.
Sunucu konteynerinden çalıştırın:
```bash
docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status
```
Örnek çıktı:
```sh
APP_VERSION: v1.23.0
Instance
Inferred version: 1.23.0
Latest command: 1.23.0_DropWorkspaceVersionColumnFastInstanceCommand_1785000000000
Status: Up to date
Executed by: v1.23.0
At: 2026-04-16T11:43:58.823Z
Workspace
Apple (20202020-1c25-4d02-bf25-6aeccf7ea419)
Inferred version: 1.23.0
Latest command: 1.23.0_UpdateGlobalObjectContextCommandMenuItemsCommand_1780000005000
Status: Up to date
Executed by: v1.23.0
At: 2026-04-16T11:44:09.361Z
Summary
Instance: Up to date
Workspaces: 1 up to date, 0 behind, 0 failed (1 total)
```
### Seçenekler
| Bayrak | Açıklama |
| ------------------------- | ---------------------------------------------------------------------------------------- |
| `-w, --workspace-id <id>` | Belirli bir çalışma alanına göre filtreleme yapar. Birden çok kez verilebilir. |
| `-f, --failed-only` | Güncel çalışma alanlarını gizler, yalnızca geride kalan ve başarısız girdileri gösterir. |
## Sorun Giderme
Yükseltme bazı çalışma alanlarında başarısız olursa, sunucu başarısız olan adımın ötesine geçmez. Sunucuyu yeniden başlatmak (`docker compose up -d`), yükseltmeyi kaldığı yerden yeniden dener.
Sorunları hızlıca belirlemek için şunu çalıştırın:
```bash
docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status --failed-only
```
Bu, yalnızca geride kalan veya başarısız olan çalışma alanlarını ve her bir başarısızlık için hata iletisini gösterir.
## v1.23'ten önce
Örneğiniz v1.23'ten eskiyse, v1.23'e ulaşana kadar her ana etiketli sürümden (v1.6'dan v1.7'ye, ardından v1.7'den v1.8'e vb.) kademeli olarak yükseltmeniz gerekir. Bundan sonra, doğrudan en son sürüme geçebilirsiniz.

View File

@@ -0,0 +1,26 @@
---
title: Kendi Kendine Barındırma
description: Twenty'yi kendi altyapınızda dağıtın ve yönetin.
---
<Frame>
<img src="/images/user-guide/what-is-twenty/20.png" alt="AI" />
</Frame>
## Genel Bakış
Twenty, kendi altyapınızda kendi kendinize barındırılabilir; bu da verileriniz ve dağıtımınız üzerinde tam kontrol sağlar.
## Neden Kendi Kendine Barındırma?
* **Veri sahipliği**: Tüm CRM verilerini kendi sunucularınızda tutun
* **Uyumluluk**: Veri yerleşimi için düzenleyici gereklilikleri karşılayın
* **Özelleştirme**: Platformu değiştirmek ve genişletmek için tam erişim
## Başlarken
<CardGroup cols={2}>
<Card title="Docker Compose" icon="docker" href="/l/tr/developers/self-host/capabilities/docker-compose">
Docker ile hızlı kurulum
</Card>
</CardGroup>