1937 lines
85 KiB
Plaintext
1937 lines
85 KiB
Plaintext
---
|
||
title: Uygulama Geliştirme
|
||
description: Nesneleri, mantık fonksiyonlarını, ön uç bileşenlerini ve daha fazlasını Twenty SDK ile tanımlayın.
|
||
---
|
||
|
||
<Warning>
|
||
Uygulamalar şu anda alfa aşamasında. Özellik işlevsel ancak hâlâ gelişmekte.
|
||
</Warning>
|
||
|
||
`twenty-sdk` paketi, uygulamanızı oluşturmak için türlendirilmiş yapı taşları sağlar. Bu sayfa, SDK'da mevcut olan tüm varlık türlerini ve API istemcilerini kapsar.
|
||
|
||
## DefineEntity fonksiyonları
|
||
|
||
SDK, uygulama varlıklarınızı tanımlamak için fonksiyonlar sağlar. SDK'nin varlıklarınızı algılayabilmesi için `export default defineEntity({...})` kullanmanız gerekir. Bu fonksiyonlar, derleme zamanında yapılandırmanızı doğrular ve IDE otomatik tamamlama ile tür güvenliği sağlar.
|
||
|
||
<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. Dosyaları türe göre gruplamak (örn. `logic-functions/`, `roles/`) bir gereklilik değil, yalnızca bir gelenektir.
|
||
</Note>
|
||
|
||
<AccordionGroup>
|
||
<Accordion title="defineRole" description="Rol izinlerini ve nesne erişimini yapılandırın">
|
||
|
||
Roller, çalışma alanınızdaki nesneler ve eylemler üzerindeki izinleri kapsar.
|
||
|
||
```ts restricted-company-role.ts
|
||
import {
|
||
defineRole,
|
||
PermissionFlag,
|
||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||
} from 'twenty-sdk';
|
||
|
||
export default defineRole({
|
||
universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6',
|
||
label: 'My new role',
|
||
description: 'A role that can be used in your workspace',
|
||
canReadAllObjectRecords: false,
|
||
canUpdateAllObjectRecords: false,
|
||
canSoftDeleteAllObjectRecords: false,
|
||
canDestroyAllObjectRecords: false,
|
||
canUpdateAllSettings: false,
|
||
canBeAssignedToAgents: false,
|
||
canBeAssignedToUsers: false,
|
||
canBeAssignedToApiKeys: false,
|
||
objectPermissions: [
|
||
{
|
||
objectUniversalIdentifier:
|
||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
|
||
canReadObjectRecords: true,
|
||
canUpdateObjectRecords: true,
|
||
canSoftDeleteObjectRecords: false,
|
||
canDestroyObjectRecords: false,
|
||
},
|
||
],
|
||
fieldPermissions: [
|
||
{
|
||
objectUniversalIdentifier:
|
||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
|
||
fieldUniversalIdentifier:
|
||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier,
|
||
canReadFieldValue: false,
|
||
canUpdateFieldValue: false,
|
||
},
|
||
],
|
||
permissionFlags: [PermissionFlag.APPLICATIONS],
|
||
});
|
||
```
|
||
|
||
</Accordion>
|
||
<Accordion title="defineApplication" description="Uygulama meta verilerini yapılandırın (zorunlu, uygulama başına bir adet)">
|
||
|
||
Her uygulamanın, şunları tanımlayan tam olarak bir adet `defineApplication` çağrısı olmalıdır:
|
||
|
||
* **Kimlik**: tanımlayıcılar, görünen ad ve açıklama.
|
||
* **İzinler**: işlevlerinin ve ön bileşenlerinin hangi rolü kullandığı.
|
||
* **(İsteğe bağlı) Değişkenler**: fonksiyonlarınıza ortam değişkenleri olarak sunulan anahtar–değer çiftleri.
|
||
* **(İsteğe bağlı) Kurulum öncesi / kurulum sonrası fonksiyonlar**: kurulumdan önce veya sonra çalışan mantık fonksiyonları.
|
||
|
||
```ts src/application-config.ts
|
||
import { defineApplication } from 'twenty-sdk';
|
||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||
|
||
export default defineApplication({
|
||
universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d',
|
||
displayName: 'My Twenty App',
|
||
description: 'My first Twenty app',
|
||
icon: 'IconWorld',
|
||
applicationVariables: {
|
||
DEFAULT_RECIPIENT_NAME: {
|
||
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
|
||
description: 'Default recipient name for postcards',
|
||
value: 'Jane Doe',
|
||
isSecret: false,
|
||
},
|
||
},
|
||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||
});
|
||
```
|
||
|
||
Notlar:
|
||
* `universalIdentifier` alanları, size ait deterministik kimliklerdir. Bunları bir kez oluşturun ve senkronizasyonlar boyunca kararlı tutun.
|
||
* `applicationVariables`, fonksiyonlarınız ve ön bileşenleriniz için ortam değişkenlerine dönüşür (örn. `DEFAULT_RECIPIENT_NAME`, `process.env.DEFAULT_RECIPIENT_NAME` olarak kullanılabilir).
|
||
* `defaultRoleUniversalIdentifier`, `defineRole()` ile tanımlanmış bir role referans vermelidir (yukarıya bakın).
|
||
* Kurulum öncesi ve kurulum sonrası fonksiyonlar manifest derlemesi sırasında otomatik olarak algılanır — bunlara `defineApplication()` içinde referans vermeniz gerekmez.
|
||
|
||
#### Pazaryeri meta verileri
|
||
|
||
Eğer [uygulamanızı yayımlamayı](/l/tr/developers/extend/apps/publishing) planlıyorsanız, bu isteğe bağlı alanlar uygulamanızın pazaryerinde nasıl görüneceğini kontrol eder:
|
||
|
||
| Alan | Açıklama |
|
||
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `author` | Yazar veya şirket adı |
|
||
| `category` | Pazaryerinde filtreleme için uygulama kategorisi |
|
||
| `logoUrl` | Uygulamanızın logosuna giden yol (örn. `public/logo.png`) |
|
||
| `screenshots` | Ekran görüntüsü yollarının dizisi (örn. `public/screenshot-1.png`) |
|
||
| `aboutDescription` | "Hakkında" sekmesi için daha uzun bir markdown açıklaması. Belirtilmezse, pazaryeri npm'deki paketin `README.md` dosyasını kullanır |
|
||
| `websiteUrl` | Web sitenize bağlantı |
|
||
| `termsUrl` | Hizmet Koşulları'na bağlantı |
|
||
| `emailSupport` | Destek e-posta adresi |
|
||
| `issueReportUrl` | Sorun izleyicisine bağlantı |
|
||
|
||
#### Roller ve izinler
|
||
|
||
`application-config.ts` içindeki `defaultRoleUniversalIdentifier`, uygulamanızın mantık fonksiyonları ve ön bileşenleri tarafından kullanılan varsayılan rolü belirtir. Ayrıntılar için yukarıdaki `defineRole` bölümüne bakın.
|
||
|
||
* `TWENTY_APP_ACCESS_TOKEN` olarak enjekte edilen çalışma zamanı belirteci bu rolden türetilir.
|
||
* Türlendirilmiş istemci, o role tanınan izinlerle sınırlandırılır.
|
||
* En az ayrıcalık ilkesini izleyin: Yalnızca fonksiyonlarınızın ihtiyaç duyduğu izinlere sahip özel bir rol oluşturun.
|
||
|
||
##### Varsayılan fonksiyon rolü
|
||
|
||
Yeni bir uygulama iskeleti oluşturduğunuzda, CLI varsayılan bir rol dosyası oluşturur:
|
||
|
||
```ts src/roles/default-role.ts
|
||
import { defineRole, PermissionFlag } from 'twenty-sdk';
|
||
|
||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||
'b648f87b-1d26-4961-b974-0908fd991061';
|
||
|
||
export default defineRole({
|
||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||
label: 'Default function role',
|
||
description: 'Default role for function Twenty client',
|
||
canReadAllObjectRecords: true,
|
||
canUpdateAllObjectRecords: false,
|
||
canSoftDeleteAllObjectRecords: false,
|
||
canDestroyAllObjectRecords: false,
|
||
canUpdateAllSettings: false,
|
||
canBeAssignedToAgents: false,
|
||
canBeAssignedToUsers: false,
|
||
canBeAssignedToApiKeys: false,
|
||
objectPermissions: [],
|
||
fieldPermissions: [],
|
||
permissionFlags: [],
|
||
});
|
||
```
|
||
|
||
Bu rolün `universalIdentifier` değeri, `application-config.ts` içinde `defaultRoleUniversalIdentifier` olarak referans verilir:
|
||
|
||
* **\*.role.ts**, bir rolün neler yapabileceğini tanımlar.
|
||
* **application-config.ts**, fonksiyonlarınızın izinlerini devralması için bu role işaret eder.
|
||
|
||
Notlar:
|
||
* Oluşturulan rolden başlayın ve en az ayrıcalık ilkesini izleyerek bunu aşamalı olarak kısıtlayın.
|
||
* `objectPermissions` ve `fieldPermissions` değerlerini, fonksiyonlarınızın ihtiyaç duyduğu nesneler ve alanlarla değiştirin.
|
||
* `permissionFlags`, platform düzeyindeki yeteneklere erişimi kontrol eder. Bunları asgari düzeyde tutun.
|
||
* Çalışan bir örnek için bkz.: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||
|
||
</Accordion>
|
||
<Accordion title="defineObject" description="Alanlara sahip özel nesneler tanımlayın">
|
||
|
||
Özel nesneler, çalışma alanınızdaki kayıtlar için hem şemayı hem de davranışı tanımlar. Yerleşik doğrulamayla nesneler tanımlamak için `defineObject()` kullanın:
|
||
|
||
```ts postCard.object.ts
|
||
import { defineObject, FieldType } from 'twenty-sdk';
|
||
|
||
enum PostCardStatus {
|
||
DRAFT = 'DRAFT',
|
||
SENT = 'SENT',
|
||
DELIVERED = 'DELIVERED',
|
||
RETURNED = 'RETURNED',
|
||
}
|
||
|
||
export default defineObject({
|
||
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
|
||
nameSingular: 'postCard',
|
||
namePlural: 'postCards',
|
||
labelSingular: 'Post Card',
|
||
labelPlural: 'Post Cards',
|
||
description: 'A post card object',
|
||
icon: 'IconMail',
|
||
fields: [
|
||
{
|
||
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
|
||
name: 'content',
|
||
type: FieldType.TEXT,
|
||
label: 'Content',
|
||
description: "Postcard's content",
|
||
icon: 'IconAbc',
|
||
},
|
||
{
|
||
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
|
||
name: 'recipientName',
|
||
type: FieldType.FULL_NAME,
|
||
label: 'Recipient name',
|
||
icon: 'IconUser',
|
||
},
|
||
{
|
||
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
|
||
name: 'recipientAddress',
|
||
type: FieldType.ADDRESS,
|
||
label: 'Recipient address',
|
||
icon: 'IconHome',
|
||
},
|
||
{
|
||
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
|
||
name: 'status',
|
||
type: FieldType.SELECT,
|
||
label: 'Status',
|
||
icon: 'IconSend',
|
||
defaultValue: `'${PostCardStatus.DRAFT}'`,
|
||
options: [
|
||
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
|
||
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
|
||
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
|
||
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
|
||
],
|
||
},
|
||
{
|
||
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
|
||
name: 'deliveredAt',
|
||
type: FieldType.DATE_TIME,
|
||
label: 'Delivered at',
|
||
icon: 'IconCheck',
|
||
isNullable: true,
|
||
defaultValue: null,
|
||
},
|
||
],
|
||
});
|
||
```
|
||
|
||
Önemli noktalar:
|
||
|
||
* Yerleşik doğrulama ve daha iyi IDE desteği için `defineObject()` kullanın.
|
||
* `universalIdentifier` dağıtımlar arasında benzersiz ve kararlı olmalıdır.
|
||
* Her alan bir `name`, `type`, `label` ve kendi kararlı `universalIdentifier` değerini gerektirir.
|
||
* `fields` dizisi isteğe bağlıdır — özel alanlar olmadan da nesneler tanımlayabilirsiniz.
|
||
* `yarn twenty add` kullanarak, adlandırma, alanlar ve ilişkiler konusunda sizi yönlendirerek yeni nesneler oluşturabilirsiniz.
|
||
|
||
<Note>
|
||
**Temel alanlar otomatik olarak oluşturulur.** Özel bir nesne tanımladığınızda Twenty, standart alanları otomatik olarak ekler
|
||
örneğin `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` ve `deletedAt`.
|
||
Bunları `fields` dizinizde tanımlamanız gerekmez — yalnızca özel alanlarınızı ekleyin.
|
||
`fields` dizinizde aynı ada sahip bir alan tanımlayarak varsayılan alanları geçersiz kılabilirsiniz,
|
||
ancak bu önerilmez.
|
||
</Note>
|
||
|
||
</Accordion>
|
||
<Accordion title="defineField — Standart alanlar" description="Mevcut nesneleri ek alanlarla genişletin">
|
||
|
||
Sahibi olmadığınız nesnelere alan eklemek için `defineField()` kullanın — standart Twenty nesneleri (Person, Company, vb.) gibi. veya diğer uygulamalardaki nesneler. `defineObject()` içindeki satır içi alanların aksine, bağımsız alanlar hangi nesneyi genişlettiklerini belirtmek için bir `objectUniversalIdentifier` gerektirir:
|
||
|
||
```ts src/fields/company-loyalty-tier.field.ts
|
||
import { defineField, FieldType } from 'twenty-sdk';
|
||
|
||
export default defineField({
|
||
universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890',
|
||
objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object
|
||
name: 'loyaltyTier',
|
||
type: FieldType.SELECT,
|
||
label: 'Loyalty Tier',
|
||
icon: 'IconStar',
|
||
options: [
|
||
{ value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' },
|
||
{ value: 'SILVER', label: 'Silver', position: 1, color: 'gray' },
|
||
{ value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' },
|
||
],
|
||
});
|
||
```
|
||
|
||
Önemli noktalar:
|
||
* `objectUniversalIdentifier` hedef nesneyi tanımlar. Standart nesneler için, `twenty-sdk`'den dışa aktarılan `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`'ı kullanın.
|
||
* Alanları `defineObject()` içinde satır içi tanımlarken, `objectUniversalIdentifier`'a ihtiyacınız yoktur — üst nesneden devralınır.
|
||
* `defineField()`, `defineObject()` ile oluşturmadığınız nesnelere alan eklemenin tek yoludur.
|
||
|
||
</Accordion>
|
||
<Accordion title="defineField — İlişki alanları" description="Nesneleri çift yönlü ilişkilerle birbirine bağlayın">
|
||
|
||
İlişkiler nesneleri birbirine bağlar. Twenty'de ilişkiler her zaman **çift yönlüdür** — her iki tarafı da tanımlarsınız ve her taraf diğerine başvurur.
|
||
|
||
İki ilişki türü vardır:
|
||
|
||
| İlişki türü | Açıklama | Yabancı anahtar var mı? |
|
||
| ------------- | --------------------------------------------------------- | ----------------------- |
|
||
| `MANY_TO_ONE` | Bu nesnenin birçok kaydı, hedefin bir kaydını işaret eder | Evet (`joinColumnName`) |
|
||
| `ONE_TO_MANY` | Bu nesnenin bir kaydı, hedefin birçok kaydına sahiptir | Hayır (ters taraf) |
|
||
|
||
#### İlişkiler nasıl çalışır
|
||
|
||
Her ilişki, birbirine referans veren iki alan gerektirir:
|
||
|
||
1. **MANY_TO_ONE** tarafı — yabancı anahtarı tutan nesne üzerinde bulunur
|
||
2. **ONE_TO_MANY** tarafı — koleksiyona sahip olan nesne üzerinde bulunur
|
||
|
||
Her iki alan da `FieldType.RELATION` kullanır ve `relationTargetFieldMetadataUniversalIdentifier` aracılığıyla birbirine karşılıklı referans verir.
|
||
|
||
#### Örnek: Posta Kartı'nın birçok Alıcısı vardır
|
||
|
||
Bir `PostCard`'ın birçok `PostCardRecipient` kaydına gönderilebildiğini varsayalım. Her alıcı tam olarak bir posta kartına aittir.
|
||
|
||
**Adım 1: PostCard üzerinde ONE_TO_MANY tarafını tanımlayın** ("bir" taraf):
|
||
|
||
```ts src/fields/post-card-recipients-on-post-card.field.ts
|
||
import { defineField, FieldType, RelationType } from 'twenty-sdk';
|
||
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';
|
||
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ğer dosyada içe aktarın. Derleme sistemi bunları derleme zamanında çözer.
|
||
</Note>
|
||
|
||
#### 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';
|
||
import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object';
|
||
|
||
export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333';
|
||
export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444';
|
||
|
||
export default defineField({
|
||
universalIdentifier: PERSON_FIELD_ID,
|
||
objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER,
|
||
type: FieldType.RELATION,
|
||
name: 'person',
|
||
label: 'Person',
|
||
description: 'Person matching with the self hosting user',
|
||
isNullable: true,
|
||
relationTargetObjectMetadataUniversalIdentifier:
|
||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
|
||
relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID,
|
||
universalSettings: {
|
||
relationType: RelationType.MANY_TO_ONE,
|
||
onDelete: OnDeleteAction.SET_NULL,
|
||
joinColumnName: 'personId',
|
||
},
|
||
});
|
||
```
|
||
|
||
#### İlişki alanı özellikleri
|
||
|
||
| Özellik | Zorunlu | Açıklama |
|
||
| ------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
|
||
| `type` | Evet | `FieldType.RELATION` olmalıdır |
|
||
| `relationTargetObjectMetadataUniversalIdentifier` | Evet | Hedef nesnenin `universalIdentifier` değeri |
|
||
| `relationTargetFieldMetadataUniversalIdentifier` | Evet | Hedef nesnedeki eşleşen alanın `universalIdentifier` değeri |
|
||
| `universalSettings.relationType` | Evet | `RelationType.MANY_TO_ONE` veya `RelationType.ONE_TO_MANY` |
|
||
| `universalSettings.onDelete` | Yalnızca MANY_TO_ONE | Başvurulan kayıt silindiğinde ne olacağı: `CASCADE`, `SET_NULL`, `RESTRICT` veya `NO_ACTION` |
|
||
| `universalSettings.joinColumnName` | Yalnızca MANY_TO_ONE | Yabancı anahtar için veritabanı sütun adı (örn. `postCardId`) |
|
||
|
||
#### defineObject içinde satır içi ilişki alanları
|
||
|
||
İlişki alanlarını doğrudan `defineObject()` içinde de tanımlayabilirsiniz. Bu durumda, `objectUniversalIdentifier`'ı atlayın — üst nesneden devralınır:
|
||
|
||
```ts
|
||
export default defineObject({
|
||
universalIdentifier: '...',
|
||
nameSingular: 'postCardRecipient',
|
||
// ...
|
||
fields: [
|
||
{
|
||
universalIdentifier: POST_CARD_FIELD_ID,
|
||
type: FieldType.RELATION,
|
||
name: 'postCard',
|
||
label: 'Post Card',
|
||
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
|
||
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
|
||
universalSettings: {
|
||
relationType: RelationType.MANY_TO_ONE,
|
||
onDelete: OnDeleteAction.CASCADE,
|
||
joinColumnName: 'postCardId',
|
||
},
|
||
},
|
||
// ... other fields
|
||
],
|
||
});
|
||
```
|
||
</Accordion>
|
||
<Accordion title="defineLogicFunction" description="Mantık fonksiyonlarını ve tetikleyicilerini tanımlayın">
|
||
|
||
Her fonksiyon dosyası, bir işleyici ve isteğe bağlı tetikleyiciler içeren bir yapılandırmayı dışa aktarmak için `defineLogicFunction()` kullanır.
|
||
|
||
```ts src/logic-functions/createPostCard.logic-function.ts
|
||
import { defineLogicFunction } from 'twenty-sdk';
|
||
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
|
||
import { CoreApiClient, type Person } from 'twenty-client-sdk/core';
|
||
|
||
const handler = async (params: RoutePayload) => {
|
||
const client = new CoreApiClient();
|
||
const name = 'name' in params.queryStringParameters
|
||
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||
: 'Hello world';
|
||
|
||
const result = await client.mutation({
|
||
createPostCard: {
|
||
__args: { data: { name } },
|
||
id: true,
|
||
name: true,
|
||
},
|
||
});
|
||
return result;
|
||
};
|
||
|
||
export default defineLogicFunction({
|
||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||
name: 'create-new-post-card',
|
||
timeoutSeconds: 2,
|
||
handler,
|
||
httpRouteTriggerSettings: {
|
||
path: '/post-card/create',
|
||
httpMethod: 'GET',
|
||
isAuthRequired: false,
|
||
},
|
||
/*databaseEventTriggerSettings: {
|
||
eventName: 'people.created',
|
||
},*/
|
||
/*cronTriggerSettings: {
|
||
pattern: '0 0 1 1 *',
|
||
},*/
|
||
});
|
||
```
|
||
|
||
Kullanılabilir tetikleyici türleri:
|
||
* **httpRoute**: Fonksiyonunuzu bir HTTP yolu ve yöntemiyle **`/s/` uç noktasının altında** kullanıma sunar:
|
||
> örn. `path: '/post-card/create'` `https://your-twenty-server.com/s/post-card/create` adresinden çağrılabilir
|
||
* **cron**: Bir CRON ifadesi kullanarak fonksiyonunuzu bir zamanlamayla çalıştırır.
|
||
* **databaseEvent**: Çalışma alanı nesnesi yaşam döngüsü olaylarında çalışır. Olay işlemi `updated` olduğunda, dinlenecek belirli alanlar `updatedFields` dizisinde belirtilebilir. Tanımsız veya boş bırakılırsa, herhangi bir güncelleme fonksiyonu tetikler.
|
||
> örn. `person.updated`, `*.created`, `company.*`
|
||
|
||
<Note>
|
||
Bir fonksiyonu CLI kullanarak manuel olarak da çalıştırabilirsiniz:
|
||
|
||
```bash filename="Terminal"
|
||
yarn twenty exec -n create-new-post-card -p '{"key": "value"}'
|
||
```
|
||
|
||
```bash filename="Terminal"
|
||
yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||
```
|
||
|
||
Günlükleri şu şekilde izleyebilirsiniz:
|
||
|
||
```bash filename="Terminal"
|
||
yarn twenty logs
|
||
```
|
||
</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` içinden içe aktarın:
|
||
|
||
```ts
|
||
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
|
||
|
||
const handler = async (event: RoutePayload) => {
|
||
const { headers, queryStringParameters, pathParameters, body } = event;
|
||
const { method, path } = event.requestContext.http;
|
||
|
||
return { message: 'Success' };
|
||
};
|
||
```
|
||
|
||
`RoutePayload` 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 }` |
|
||
| `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>
|
||
|
||
#### Bir fonksiyonu araç olarak sunma
|
||
|
||
Mantık işlevleri, yapay zeka ajanları ve iş akışları için **araçlar** olarak sunulabilir. Bir fonksiyon bir araç olarak işaretlendiğinde, Twenty'nin yapay zeka özellikleri tarafından keşfedilebilir hâle gelir ve iş akışı otomasyonlarında kullanılabilir.
|
||
|
||
Bir mantık fonksiyonunu araç olarak işaretlemek için `isTool: true` olarak ayarlayın:
|
||
|
||
```ts src/logic-functions/enrich-company.logic-function.ts
|
||
import { defineLogicFunction } from 'twenty-sdk';
|
||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||
|
||
const handler = async (params: { companyName: string; domain?: string }) => {
|
||
const client = new CoreApiClient();
|
||
|
||
const result = await client.mutation({
|
||
createTask: {
|
||
__args: {
|
||
data: {
|
||
title: `Enrich data for ${params.companyName}`,
|
||
body: `Domain: ${params.domain ?? 'unknown'}`,
|
||
},
|
||
},
|
||
id: true,
|
||
},
|
||
});
|
||
|
||
return { taskId: result.createTask.id };
|
||
};
|
||
|
||
export default defineLogicFunction({
|
||
universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
|
||
name: 'enrich-company',
|
||
description: 'Enrich a company record with external data',
|
||
timeoutSeconds: 10,
|
||
handler,
|
||
isTool: true,
|
||
});
|
||
```
|
||
|
||
Önemli noktalar:
|
||
|
||
* `isTool` özelliğini tetikleyicilerle birleştirebilirsiniz — bir fonksiyon aynı anda hem bir araç (yapay zeka ajanları tarafından çağrılabilir) olabilir hem de olaylar tarafından tetiklenebilir.
|
||
* **`toolInputSchema`** (isteğe bağlı): Fonksiyonunuzun kabul ettiği parametreleri tanımlayan bir JSON Schema nesnesi. Şema, kaynak kodun statik analizinden otomatik olarak oluşturulur, ancak bunu açıkça belirleyebilirsiniz:
|
||
|
||
```ts
|
||
export default defineLogicFunction({
|
||
...,
|
||
toolInputSchema: {
|
||
type: 'object',
|
||
properties: {
|
||
companyName: {
|
||
type: 'string',
|
||
description: 'The name of the company to enrich',
|
||
},
|
||
domain: {
|
||
type: 'string',
|
||
description: 'The company website domain (optional)',
|
||
},
|
||
},
|
||
required: ['companyName'],
|
||
},
|
||
});
|
||
```
|
||
|
||
<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>
|
||
<Accordion title="definePreInstallLogicFunction" description="Bir kurulum öncesi mantık işlevi tanımlayın (uygulama başına bir adet)">
|
||
|
||
Kurulum öncesi işlev, uygulamanız bir çalışma alanına yüklenmeden önce otomatik olarak çalışan bir mantık işlevidir. Bu, doğrulama görevleri, önkoşul kontrolleri veya ana kurulum başlamadan önce çalışma alanı durumunun hazırlanması için yararlıdır.
|
||
|
||
```ts src/logic-functions/pre-install.ts
|
||
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||
|
||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||
};
|
||
|
||
export default definePreInstallLogicFunction({
|
||
universalIdentifier: 'e0604b9e-e946-456b-886d-3f27d9a6b324',
|
||
name: 'pre-install',
|
||
description: 'Runs before installation to prepare the application.',
|
||
timeoutSeconds: 300,
|
||
handler,
|
||
});
|
||
```
|
||
|
||
Ayrıca kurulum öncesi işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz:
|
||
|
||
```bash filename="Terminal"
|
||
yarn twenty exec --preInstall
|
||
```
|
||
|
||
Önemli noktalar:
|
||
* Kurulum öncesi işlevler `definePreInstallLogicFunction()` kullanır — tetikleyici ayarlarını atlayan (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) özel bir varyanttır.
|
||
* İşleyici, `{ previousVersion: string }` içeren bir `InstallLogicFunctionPayload` alır — daha önce yüklü olan uygulamanın sürümü (veya yeni kurulumlar için boş bir dize).
|
||
* Uygulama başına yalnızca bir kurulum öncesi işlevine izin verilir. Birden fazla tespit edilirse manifest oluşturma hataya düşer.
|
||
* İşlevin `universalIdentifier` değeri, oluşturma sırasında uygulama manifestinde otomatik olarak `preInstallLogicFunctionUniversalIdentifier` olarak ayarlanır — `defineApplication()` içinde buna atıfta bulunmanıza gerek yoktur.
|
||
* Varsayılan zaman aşımı, daha uzun hazırlık görevlerine izin vermek için 300 saniye (5 dakika) olarak ayarlanmıştır.
|
||
|
||
</Accordion>
|
||
<Accordion title="definePostInstallLogicFunction" description="Bir kurulum sonrası mantık işlevi tanımlayın (uygulama başına bir adet)">
|
||
|
||
Kurulum sonrası işlev, uygulamanız bir çalışma alanına yüklendikten sonra otomatik olarak çalışan bir mantık işlevidir. Bu, varsayılan verileri tohumlama, ilk kayıtları oluşturma veya çalışma alanı ayarlarını yapılandırma gibi tek seferlik kurulum görevleri için yararlıdır.
|
||
|
||
```ts src/logic-functions/post-install.ts
|
||
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||
|
||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||
};
|
||
|
||
export default definePostInstallLogicFunction({
|
||
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
|
||
name: 'post-install',
|
||
description: 'Runs after installation to set up the application.',
|
||
timeoutSeconds: 300,
|
||
handler,
|
||
});
|
||
```
|
||
|
||
Ayrıca kurulum sonrası işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz:
|
||
|
||
```bash filename="Terminal"
|
||
yarn twenty exec --postInstall
|
||
```
|
||
|
||
Önemli noktalar:
|
||
* Kurulum sonrası işlevler `definePostInstallLogicFunction()` kullanır — tetikleyici ayarlarını atlayan (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) özel bir varyanttır.
|
||
* İşleyici, `{ previousVersion: string }` içeren bir `InstallLogicFunctionPayload` alır — daha önce yüklü olan uygulamanın sürümü (veya yeni kurulumlar için boş bir dize).
|
||
* Uygulama başına yalnızca bir kurulum sonrası işlevine izin verilir. Birden fazla tespit edilirse manifest oluşturma hataya düşer.
|
||
* İşlevin `universalIdentifier` değeri, oluşturma sırasında uygulama manifestinde otomatik olarak `postInstallLogicFunctionUniversalIdentifier` olarak ayarlanır — `defineApplication()` içinde buna atıfta bulunmanıza gerek yoktur.
|
||
* Varsayılan zaman aşımı, veri tohumlama gibi daha uzun kurulum görevlerine izin vermek için 300 saniye (5 dakika) olarak ayarlanmıştır.
|
||
|
||
</Accordion>
|
||
<Accordion title="defineFrontComponent" description="Özel kullanıcı arayüzü için ön uç bileşenlerini tanımlayın">
|
||
|
||
Ön uç bileşenler, Twenty'nin UI'si içinde doğrudan görüntülenen React bileşenleridir. Remote DOM kullanan izole bir Web Worker içinde çalışırlar — kodunuz izole bir ortamda (sandbox) çalışır ancak bir iframe içinde değil, sayfada yerel olarak işlenir.
|
||
|
||
#### Ön bileşenlerin kullanılabileceği yerler
|
||
|
||
Ön bileşenler, Twenty içinde iki konumda işlenebilir:
|
||
|
||
* **Yan panel** — Headless olmayan ön bileşenler, sağ taraftaki yan panelde açılır. Bir ön bileşen komut menüsünden tetiklendiğinde varsayılan davranış budur.
|
||
* **Widget'lar (panolar ve kayıt sayfaları)** — Ön bileşenler, sayfa düzenlerine widget olarak gömülebilir. Bir pano veya kayıt sayfası düzeni yapılandırılırken kullanıcılar bir ön bileşen widget'ı ekleyebilir.
|
||
|
||
#### Basit örnek
|
||
|
||
Bir ön uç bileşenini çalışırken görmenin en hızlı yolu, onu bir komut olarak kaydetmektir. `isPinned: true` ile bir `command` alanı eklemek, sayfanın sağ üst köşesinde hızlı işlem düğmesi olarak görünmesini sağlar — herhangi bir sayfa düzenine gerek yoktur:
|
||
|
||
```tsx src/front-components/hello-world.tsx
|
||
import { defineFrontComponent } from 'twenty-sdk';
|
||
|
||
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,
|
||
command: {
|
||
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
|
||
shortLabel: 'Hello',
|
||
label: 'Hello World',
|
||
icon: 'IconBolt',
|
||
isPinned: true,
|
||
availabilityType: 'GLOBAL',
|
||
},
|
||
});
|
||
```
|
||
|
||
`yarn twenty dev` ile senkronize ettikten sonra, 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.
|
||
|
||
{/* TODO: add screenshot of the rendered front component */}
|
||
|
||
#### Yapılandırma alanları
|
||
|
||
| Alan | Zorunlu | Açıklama |
|
||
| --------------------- | ------- | ------------------------------------------------------------------------------------------ |
|
||
| `universalIdentifier` | Evet | Bu bileşen için kalıcı benzersiz kimlik |
|
||
| `component` | Evet | Bir React bileşen fonksiyonu |
|
||
| `name` | Hayır | Görünen Ad |
|
||
| `description` | Hayır | Bileşenin ne yaptığına dair açıklama |
|
||
| `isHeadless` | Hayır | Bileşenin görünür bir kullanıcı arayüzü yoksa `true` olarak ayarlayın (aşağıya bakın) |
|
||
| `command` | Hayır | Bileşeni bir komut olarak kaydedin (aşağıda [komut seçeneklerine](#command-options) bakın) |
|
||
|
||
#### Bir ön uç bileşenini bir sayfaya yerleştirme
|
||
|
||
Komutların ötesinde, bir ön uç bileşenini bir **sayfa düzeninde** widget olarak ekleyerek doğrudan bir kayıt sayfasına gömebilirsiniz. Ayrıntılar için [definePageLayout](#definepagelayout) bölümüne bakın.
|
||
|
||
#### Headless ve headless olmayan
|
||
|
||
Ön bileşenler, `isHeadless` seçeneğiyle kontrol edilen iki işleme kipiyle gelir:
|
||
|
||
**Headless olmayan (varsayılan)** — Bileşen görünür bir kullanıcı arayüzü (UI) oluşturur. Komut menüsünden tetiklendiğinde yan panelde açılır. `isHeadless` `false` olduğunda veya belirtilmediğinde bu varsayılan davranıştır.
|
||
|
||
**Headless (`isHeadless: true`)** — Bileşen arka planda görünmez şekilde bağlanır. Yan paneli açmaz. Headless bileşenler, mantığı çalıştırıp ardından kendilerini kaldıran eylemler için tasarlanmıştır — örneğin, bir async görevi çalıştırma, bir sayfaya gitme veya bir onay modalı gösterme. Aşağıda açıklanan SDK Command bileşenleriyle doğal olarak eşleşirler.
|
||
|
||
```tsx src/front-components/sync-tracker.tsx
|
||
import { defineFrontComponent, useRecordId, enqueueSnackbar } from 'twenty-sdk';
|
||
import { useEffect } from 'react';
|
||
|
||
const SyncTracker = () => {
|
||
const recordId = useRecordId();
|
||
|
||
useEffect(() => {
|
||
enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
|
||
}, [recordId]);
|
||
|
||
return null;
|
||
};
|
||
|
||
export default defineFrontComponent({
|
||
universalIdentifier: '...',
|
||
name: 'sync-tracker',
|
||
description: 'Tracks record views silently',
|
||
isHeadless: true,
|
||
component: SyncTracker,
|
||
});
|
||
```
|
||
|
||
Bileşen `null` döndürdüğü için, Twenty bunun için bir kapsayıcı oluşturmayı atlar — düzende boş alan görünmez. Bileşen yine de tüm hook'lara ve host iletişim API'sine erişime sahiptir.
|
||
|
||
#### SDK Command bileşenleri
|
||
|
||
`twenty-sdk` paketi, headless ön bileşenler için tasarlanmış dört Command yardımcı bileşeni sağlar. Her bileşen bağlandığında bir eylem yürütür, hataları bir snackbar bildirimi göstererek ele alır ve tamamlandığında ön bileşeni otomatik olarak kaldırır.
|
||
|
||
Bunları `twenty-sdk/command` içinden içe aktarın:
|
||
|
||
* **`Command`** — `execute` prop'u aracılığıyla async bir geri çağrıyı çalıştırır.
|
||
* **`CommandLink`** — Bir uygulama yoluna gider. Props: `to`, `params`, `queryParams`, `options`.
|
||
* **`CommandModal`** — Bir onay modalı açar. Kullanıcı onaylarsa `execute` geri çağrısını yürütür. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`.
|
||
* **`CommandOpenSidePanelPage`** — Belirli bir yan panel sayfasını açar. Props: `page`, `pageTitle`, `pageIcon`.
|
||
|
||
`Command` kullanarak komut menüsünden bir eylem çalıştıran headless bir ön bileşenin tam örneği:
|
||
|
||
```tsx src/front-components/run-action.tsx
|
||
import { defineFrontComponent } from 'twenty-sdk';
|
||
import { Command } from 'twenty-sdk/command';
|
||
import { CoreApiClient } from 'twenty-sdk/clients';
|
||
|
||
const RunAction = () => {
|
||
const execute = async () => {
|
||
const client = new CoreApiClient();
|
||
|
||
await client.mutation({
|
||
createTask: {
|
||
__args: { data: { title: 'Created by my app' } },
|
||
id: true,
|
||
},
|
||
});
|
||
};
|
||
|
||
return <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,
|
||
command: {
|
||
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
|
||
label: 'Run my action',
|
||
icon: 'IconPlayerPlay',
|
||
},
|
||
});
|
||
```
|
||
|
||
Ve yürütmeden önce onay istemek için `CommandModal` kullanan bir örnek:
|
||
|
||
```tsx src/front-components/delete-draft.tsx
|
||
import { defineFrontComponent } from 'twenty-sdk';
|
||
import { CommandModal } from 'twenty-sdk/command';
|
||
|
||
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,
|
||
command: {
|
||
universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567',
|
||
label: 'Delete draft',
|
||
icon: 'IconTrash',
|
||
},
|
||
});
|
||
```
|
||
|
||
#### Çalışma zamanı bağlamına erişme
|
||
|
||
Bileşeninizin içinde, geçerli kullanıcıya, kayda ve bileşen örneğine erişmek için SDK hook'larını kullanın:
|
||
|
||
```tsx src/front-components/record-info.tsx
|
||
import {
|
||
defineFrontComponent,
|
||
useUserId,
|
||
useRecordId,
|
||
useFrontComponentId,
|
||
} from 'twenty-sdk';
|
||
|
||
const RecordInfo = () => {
|
||
const userId = useUserId();
|
||
const recordId = useRecordId();
|
||
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 |
|
||
| `useRecordId()` | `string` veya `null` | Geçerli kaydın ID'si (bir kayıt sayfasına yerleştirildiğinde) |
|
||
| `useFrontComponentId()` | `string` | Bu bileşen örneğinin ID'si |
|
||
| `useFrontComponentExecutionContext(selector)` | değişir | Bir seçici işlevle tam yürütme bağlamına erişin |
|
||
|
||
#### Host iletişim API'si
|
||
|
||
Ön uç bileşenleri, `twenty-sdk`'deki işlevleri kullanarak gezinmeyi, modalları ve bildirimleri tetikleyebilir:
|
||
|
||
| Fonksiyon | Açıklama |
|
||
| ----------------------------------------------- | ---------------------------------- |
|
||
| `navigate(to, params?, queryParams?, options?)` | Uygulamada bir sayfaya git |
|
||
| `openSidePanelPage(params)` | Bir yan panel aç |
|
||
| `closeSidePanel()` | Yan paneli kapat |
|
||
| `openCommandConfirmationModal(params)` | Bir onay iletişim kutusu göster |
|
||
| `enqueueSnackbar(params)` | Bir toast bildirimi göster |
|
||
| `unmountFrontComponent()` | Bileşeni kaldır (unmount) |
|
||
| `updateProgress(progress)` | Bir ilerleme göstergesini güncelle |
|
||
|
||
Bir eylem tamamlandıktan sonra bir snackbar göstermek ve yan paneli kapatmak için host API'sini kullanan bir örnek:
|
||
|
||
```tsx src/front-components/archive-record.tsx
|
||
import { defineFrontComponent, useRecordId } from 'twenty-sdk';
|
||
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk';
|
||
import { CoreApiClient } from 'twenty-sdk/clients';
|
||
|
||
const ArchiveRecord = () => {
|
||
const recordId = useRecordId();
|
||
|
||
const handleArchive = async () => {
|
||
const client = new CoreApiClient();
|
||
|
||
await client.mutation({
|
||
updateTask: {
|
||
__args: { id: recordId, data: { status: 'ARCHIVED' } },
|
||
id: true,
|
||
},
|
||
});
|
||
|
||
await enqueueSnackbar({
|
||
message: 'Record archived',
|
||
variant: 'success',
|
||
});
|
||
|
||
await closeSidePanel();
|
||
};
|
||
|
||
return (
|
||
<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,
|
||
});
|
||
```
|
||
|
||
#### Komut seçenekleri
|
||
|
||
`defineFrontComponent` içine bir `command` alanı eklemek, bileşeni komut menüsüne (Cmd+K) kaydeder. `isPinned` `true` ise, sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak da görünür.
|
||
|
||
| Alan | Zorunlu | Açıklama |
|
||
| --------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `universalIdentifier` | Evet | Komut için kararlı benzersiz kimlik |
|
||
| `label` | Evet | Komut menüsünde (Cmd+K) gösterilen tam etiket |
|
||
| `shortLabel` | Hayır | Sabitlenmiş hızlı işlem düğmesinde görüntülenen daha kısa etiket |
|
||
| `icon` | Hayır | Etiketin yanında görüntülenen simge adı (örn. `'IconBolt'`, `'IconSend'`) |
|
||
| `isPinned` | Hayır | `true` olduğunda, komutu sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak gösterir |
|
||
| `availabilityType` | Hayır | Komutun nerede görüneceğini kontrol eder: `'GLOBAL'` (her zaman kullanılabilir), `'RECORD_SELECTION'` (yalnızca kayıtlar seçiliyken) veya `'FALLBACK'` (başka hiçbir komut eşleşmediğinde gösterilir) |
|
||
| `availabilityObjectUniversalIdentifier` | Hayır | Komutu belirli bir nesne türünün sayfalarıyla sınırlandırın (örn. yalnızca Company kayıtlarında) |
|
||
| `conditionalAvailabilityExpression` | Hayır | Komutun görünür olup olmadığını dinamik olarak kontrol eden bir boolean ifade (aşağıya bakın) |
|
||
|
||
#### Koşullu kullanılabilirlik ifadeleri
|
||
|
||
`conditionalAvailabilityExpression` alanı, geçerli sayfa bağlamına göre bir komutun ne zaman görünür olacağını kontrol etmenizi sağlar. İfadeler oluşturmak için `twenty-sdk`'den türlendirilmiş değişkenleri ve operatörleri içe aktarın:
|
||
|
||
```tsx
|
||
import {
|
||
defineFrontComponent,
|
||
pageType,
|
||
numberOfSelectedRecords,
|
||
objectPermissions,
|
||
everyEquals,
|
||
isDefined,
|
||
} from 'twenty-sdk';
|
||
|
||
export default defineFrontComponent({
|
||
universalIdentifier: '...',
|
||
name: 'bulk-action',
|
||
component: BulkAction,
|
||
command: {
|
||
universalIdentifier: '...',
|
||
label: 'Bulk Update',
|
||
availabilityType: 'RECORD_SELECTION',
|
||
conditionalAvailabilityExpression: everyEquals(
|
||
objectPermissions,
|
||
'canUpdateObjectRecords',
|
||
true,
|
||
),
|
||
},
|
||
});
|
||
```
|
||
|
||
**Bağlam değişkenleri** — bunlar sayfanın mevcut durumunu temsil eder:
|
||
|
||
| Değişken | Tür | Açıklama |
|
||
| ------------------------------ | --------- | ----------------------------------------------------------------- |
|
||
| `pageType` | `string` | Geçerli sayfa türü (örn. `'RecordIndexPage'`, `'RecordShowPage'`) |
|
||
| `isInSidePanel` | `boolean` | Bileşenin bir yan panelde oluşturulup oluşturulmadığı |
|
||
| `numberOfSelectedRecords` | `number` | Şu anda seçili kayıt sayısı |
|
||
| `isSelectAll` | `boolean` | "tümünü seç" seçeneğinin etkin olup olmadığı |
|
||
| `selectedRecords` | `array` | Seçili kayıt nesneleri |
|
||
| `favoriteRecordIds` | `array` | Favorilere eklenen kayıtların ID'leri |
|
||
| `objectPermissions` | `object` | Geçerli nesne türü için izinler |
|
||
| `targetObjectReadPermissions` | `object` | Hedef nesne için okuma izinleri |
|
||
| `targetObjectWritePermissions` | `object` | Hedef nesne için yazma izinleri |
|
||
| `featureFlags` | `object` | Etkin özellik bayrakları |
|
||
| `objectMetadataItem` | `object` | Geçerli nesne türünün üst verileri |
|
||
| `hasAnySoftDeleteFilterOnView` | `boolean` | Geçerli görünümde soft-delete filtresi olup olmadığı |
|
||
|
||
**Operatörler** — değişkenleri boolean ifadelere dönüştürmek için birleştirin:
|
||
|
||
| Operatör | Açıklama |
|
||
| ----------------------------------- | --------------------------------------------------------- |
|
||
| `isDefined(value)` | Değer null/undefined değilse `true` |
|
||
| `isNonEmptyString(value)` | Değer boş olmayan bir string ise `true` |
|
||
| `includes(array, value)` | Dizi değeri içeriyorsa `true` |
|
||
| `includesEvery(array, prop, value)` | Her bir öğenin özelliği değeri içeriyorsa `true` |
|
||
| `every(array, prop)` | Özellik her öğede truthy ise `true` |
|
||
| `everyDefined(array, prop)` | Özellik her öğede tanımlıysa `true` |
|
||
| `everyEquals(array, prop, value)` | Özellik her öğede değere eşitse `true` |
|
||
| `some(array, prop)` | Özellik en az bir öğede truthy ise `true` |
|
||
| `someDefined(array, prop)` | Özellik en az bir öğede tanımlıysa `true` |
|
||
| `someEquals(array, prop, value)` | Özellik en az bir öğede değere eşitse `true` |
|
||
| `someNonEmptyString(array, prop)` | Özellik en az bir öğede boş olmayan bir string ise `true` |
|
||
| `none(array, prop)` | Özellik her öğede falsy ise `true` |
|
||
| `noneDefined(array, prop)` | Özellik her öğede tanımsızsa `true` |
|
||
| `noneEquals(array, prop, value)` | Özellik hiçbir öğede değere eşit değilse `true` |
|
||
|
||
#### Genel varlıklar
|
||
|
||
Ön uç bileşenleri, `getPublicAssetUrl` kullanarak uygulamanın `public/` dizinindeki dosyalara erişebilir:
|
||
|
||
```tsx
|
||
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk';
|
||
|
||
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](#accessing-public-assets-with-getpublicasseturl) bakın.
|
||
|
||
#### Stil
|
||
|
||
Ön uç bileşenleri birden fazla biçimlendirme yaklaşımını destekler. Şunları kullanabilirsiniz:
|
||
|
||
* **Satır içi stiller** — `style={{ color: 'red' }}`
|
||
* **Twenty UI bileşenleri** — `twenty-sdk/ui` içinden içe aktarın (Button, Tag, Status, Chip, Avatar ve daha fazlası)
|
||
* **Emotion** — `@emotion/react` ile CSS-in-JS
|
||
* **Styled-components** — `styled.div` kalıpları
|
||
* **Tailwind CSS** — yardımcı sınıflar
|
||
* **React ile uyumlu herhangi bir CSS-in-JS kitaplığı**
|
||
|
||
```tsx
|
||
import { defineFrontComponent } from 'twenty-sdk';
|
||
import { Button, Tag, Status } from 'twenty-sdk/ui';
|
||
|
||
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,
|
||
});
|
||
```
|
||
|
||
</Accordion>
|
||
|
||
<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';
|
||
|
||
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';
|
||
|
||
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.
|
||
|
||
</Accordion>
|
||
<Accordion title="defineView" description="Nesneler için kaydedilmiş görünümler tanımlayın">
|
||
|
||
Görünümler, bir nesnenin kayıtlarının nasıl görüntüleneceğine ilişkin kaydedilmiş yapılandırmalardır — hangi alanların görünür olacağını, sıralarını ve uygulanan filtreleri veya grupları içerir. Uygulamanızla önceden yapılandırılmış görünümler sunmak için `defineView()` kullanın:
|
||
|
||
```ts src/views/example-view.ts
|
||
import { defineView, ViewKey } from 'twenty-sdk';
|
||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
|
||
import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
|
||
|
||
export default defineView({
|
||
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||
name: 'All example items',
|
||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||
icon: 'IconList',
|
||
key: ViewKey.INDEX,
|
||
position: 0,
|
||
fields: [
|
||
{
|
||
universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0',
|
||
fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||
position: 0,
|
||
isVisible: true,
|
||
size: 200,
|
||
},
|
||
],
|
||
});
|
||
```
|
||
|
||
Önemli noktalar:
|
||
* `objectUniversalIdentifier`, bu görünümün hangi nesneye uygulanacağını belirtir.
|
||
* `key`, görünüm türünü belirler (ör. ana liste görünümü için `ViewKey.INDEX`).
|
||
* `fields`, hangi sütunların görüneceğini ve sıralarını kontrol eder. Her alan bir `fieldMetadataUniversalIdentifier` öğesine referans verir.
|
||
* Daha gelişmiş yapılandırmalar için `filters`, `filterGroups`, `groups` ve `fieldGroups` de tanımlayabilirsiniz.
|
||
* `position`, aynı nesne için birden fazla görünüm olduğunda sıralamayı kontrol eder.
|
||
|
||
</Accordion>
|
||
<Accordion title="defineNavigationMenuItem" description="Kenar çubuğu gezinme bağlantılarını tanımlayın">
|
||
|
||
Gezinme menüsü öğeleri, çalışma alanı kenar çubuğuna özel girişler ekler. Görünümlere, harici URL'lere veya nesnelere bağlanmak için `defineNavigationMenuItem()` kullanın:
|
||
|
||
```ts src/navigation-menu-items/example-navigation-menu-item.ts
|
||
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk';
|
||
import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view';
|
||
|
||
export default defineNavigationMenuItem({
|
||
universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c',
|
||
name: 'example-navigation-menu-item',
|
||
icon: 'IconList',
|
||
color: 'blue',
|
||
position: 0,
|
||
type: NavigationMenuItemType.VIEW,
|
||
viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
|
||
});
|
||
```
|
||
|
||
Önemli noktalar:
|
||
* `type`, menü öğesinin neye bağlanacağını belirler: kaydedilmiş bir görünüm için `NavigationMenuItemType.VIEW` veya harici bir URL için `NavigationMenuItemType.LINK`.
|
||
* Görünüm bağlantıları için `viewUniversalIdentifier` ayarlayın. Harici bağlantılar için `link` ayarlayın.
|
||
* `position`, kenar çubuğundaki sıralamayı kontrol eder.
|
||
* `icon` ve `color` (isteğe bağlı) görünümü özelleştirir.
|
||
|
||
</Accordion>
|
||
<Accordion title="definePageLayout" description="Kayıt görünümleri için özel sayfa düzenleri tanımlayın">
|
||
|
||
Sayfa düzenleri, bir kayıt ayrıntı sayfasının nasıl görüneceğini özelleştirmenizi sağlar — hangi sekmelerin görüneceği, her sekmenin içinde hangi widget'ların olacağı ve bunların nasıl düzenleneceği. Uygulamanızla özel düzenler sunmak için `definePageLayout()` kullanın:
|
||
|
||
```ts src/page-layouts/example-record-page-layout.ts
|
||
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk';
|
||
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
|
||
import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world';
|
||
|
||
export default definePageLayout({
|
||
universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134',
|
||
name: 'Example Record Page',
|
||
type: 'RECORD_PAGE',
|
||
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||
tabs: [
|
||
{
|
||
universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5',
|
||
title: 'Hello World',
|
||
position: 50,
|
||
icon: 'IconWorld',
|
||
layoutMode: PageLayoutTabLayoutMode.CANVAS,
|
||
widgets: [
|
||
{
|
||
universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d',
|
||
title: 'Hello World',
|
||
type: 'FRONT_COMPONENT',
|
||
configuration: {
|
||
configurationType: 'FRONT_COMPONENT',
|
||
frontComponentUniversalIdentifier:
|
||
HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||
},
|
||
},
|
||
],
|
||
},
|
||
],
|
||
});
|
||
```
|
||
|
||
Önemli noktalar:
|
||
* `type` genellikle belirli bir nesnenin ayrıntı görünümünü özelleştirmek için `'RECORD_PAGE'` olur.
|
||
* `objectUniversalIdentifier`, bu düzenin hangi nesneye uygulanacağını belirtir.
|
||
* Her `tab`, bir `title`, `position` ve `layoutMode` ile sayfanın bir bölümünü tanımlar (serbest biçimli düzen için `CANVAS`).
|
||
* Bir sekmenin içindeki her `widget`, bir ön uç bileşeni, bir ilişki listesi veya diğer yerleşik widget türlerini oluşturabilir.
|
||
* Sekmelerdeki `position`, sıralarını kontrol eder. Özel sekmeleri yerleşik olanların sonrasına yerleştirmek için daha yüksek değerler kullanın (ör. 50).
|
||
|
||
</Accordion>
|
||
</AccordionGroup>
|
||
|
||
## Genel varlıklar (`public/` klasörü)
|
||
|
||
Uygulamanızın kökündeki `public/` klasörü, statik dosyaları barındırır — görseller, simgeler, yazı tipleri veya uygulamanızın çalışma zamanında ihtiyaç duyduğu diğer varlıklar. Bu dosyalar derlemelere otomatik olarak dahil edilir, geliştirme modunda senkronize edilir ve sunucuya yüklenir.
|
||
|
||
`public/` içine yerleştirilen dosyalar şunlardır:
|
||
|
||
* **Herkese açık olarak erişilebilir** — sunucuya senkronize edildikten sonra varlıklar genel bir URL'den sunulur. Onlara erişmek için kimlik doğrulama gerekmez.
|
||
* **Ön uç bileşenlerinde kullanılabilir** — React bileşenlerinizin içinde görseller, simgeler veya herhangi bir medyayı göstermek için varlık URL'lerini kullanın.
|
||
* **Mantık işlevlerinde kullanılabilir** — e-postalarda, API yanıtlarında veya herhangi bir sunucu tarafı mantıkta varlık URL'lerine referans verin.
|
||
* **Pazar yeri üst verileri için kullanılır** — `defineApplication()` içindeki `logoUrl` ve `screenshots` alanları bu klasördeki dosyalara referans verir (örn. `public/logo.png`). Bunlar, uygulamanız yayımlandığında pazar yerinde görüntülenir.
|
||
* **Geliştirme modunda otomatik senkronize edilir** — `public/` içinde bir dosya eklediğinizde, güncellediğinizde veya sildiğinizde otomatik olarak sunucuya senkronize edilir. Yeniden başlatma gerekmez.
|
||
* **Derlemelere dahil edilir** — `yarn twenty build`, tüm genel varlıkları dağıtım çıktısına paketler.
|
||
|
||
### `getPublicAssetUrl` ile genel varlıklara erişme
|
||
|
||
`twenty-sdk` içindeki `getPublicAssetUrl` yardımcı işlevini kullanarak `public/` dizininizdeki bir dosyanın tam URL'sini alın. Hem **mantık işlevlerinde** hem de **ön uç bileşenlerinde** çalışır.
|
||
|
||
**Bir mantık işlevinde:**
|
||
|
||
```ts src/logic-functions/send-invoice.ts
|
||
import { defineLogicFunction, getPublicAssetUrl } from 'twenty-sdk';
|
||
|
||
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, getPublicAssetUrl } from 'twenty-sdk';
|
||
|
||
export default defineFrontComponent(() => {
|
||
const logoUrl = getPublicAssetUrl('logo.png');
|
||
|
||
return <img src={logoUrl} alt="App logo" />;
|
||
});
|
||
```
|
||
|
||
`path` bağımsız değişkeni, uygulamanızın `public/` klasörüne göre görelidir. Hem `getPublicAssetUrl('logo.png')` hem de `getPublicAssetUrl('public/logo.png')` aynı URL'ye çözümlenir — `public/` öneki varsa otomatik olarak kaldırılır.
|
||
|
||
## npm paketlerini kullanma
|
||
|
||
Uygulamanızda herhangi bir npm paketini yükleyip kullanabilirsiniz. Hem mantık işlevleri hem de ön uç bileşenleri, tüm bağımlılıkları çıktıya satır içi olarak ekleyen [esbuild](https://esbuild.github.io/) ile paketlenir — çalışma zamanında `node_modules` gerekmez.
|
||
|
||
### Bir paketi yükleme
|
||
|
||
```bash filename="Terminal"
|
||
yarn add axios
|
||
```
|
||
|
||
Ardından kodunuza içe aktarın:
|
||
|
||
```ts src/logic-functions/fetch-data.ts
|
||
import { defineLogicFunction } from 'twenty-sdk';
|
||
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';
|
||
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ı (`yarn twenty dev` veya `yarn twenty build`), her mantık işlevi ve her ön uç bileşeni için tek bir bağımsız dosya üretmek üzere esbuild kullanır. Tüm içe aktarılan paketler pakete satır içi eklenir.
|
||
|
||
**Mantık işlevleri**, Node.js ortamında çalışır. Node yerleşik modülleri (`fs`, `path`, `crypto`, `http` vb.) kullanılabilir ve kurulmaları gerekmez.
|
||
|
||
**Ön uç bileşenleri**, bir Web Worker içinde çalışır. Node'un yerleşik modülleri **kullanılamaz** — yalnızca tarayıcı ortamında çalışan tarayıcı API'leri ve npm paketleri kullanılabilir.
|
||
|
||
Her iki ortamda da `twenty-client-sdk/core` ve `twenty-client-sdk/metadata` önceden sağlanmış modüller olarak mevcuttur — bunlar paketlenmez, ancak çalışma zamanında sunucu tarafından çözülür.
|
||
|
||
## `yarn twenty add` ile varlıklar için iskelet oluşturma
|
||
|
||
Varlık dosyalarını elle oluşturmak yerine etkileşimli iskelet oluşturucuyu kullanabilirsiniz:
|
||
|
||
```bash filename="Terminal"
|
||
yarn twenty add
|
||
```
|
||
|
||
Bu, bir varlık türü seçmenizi ister ve gerekli alanlar boyunca size yol gösterir. Kararlı bir `universalIdentifier` ve doğru `defineEntity()` çağrısıyla kullanıma hazır bir dosya üretir.
|
||
|
||
İlk istemi atlamak için varlık türünü doğrudan da geçebilirsiniz:
|
||
|
||
```bash filename="Terminal"
|
||
yarn twenty add object
|
||
yarn twenty add logicFunction
|
||
yarn twenty add frontComponent
|
||
```
|
||
|
||
### Kullanılabilir varlık türleri
|
||
|
||
| Varlık türü | Komut | Oluşturulan dosya |
|
||
| -------------------- | ------------------------------------ | ------------------------------------- |
|
||
| Nesne | `yarn twenty add object` | `src/objects/<name>.ts` |
|
||
| Alan | `yarn twenty add field` | `src/fields/<name>.ts` |
|
||
| Mantık işlevi | `yarn twenty add logicFunction` | `src/logic-functions/<name>.ts` |
|
||
| Ön uç bileşeni | `yarn twenty add frontComponent` | `src/front-components/<name>.tsx` |
|
||
| Rol | `yarn twenty add role` | `src/roles/<name>.ts` |
|
||
| Beceri | `yarn twenty add skill` | `src/skills/<name>.ts` |
|
||
| Temsilci | `yarn twenty add agent` | `src/agents/<name>.ts` |
|
||
| Görünüm | `yarn twenty add view` | `src/views/<name>.ts` |
|
||
| Gezinme menüsü öğesi | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/<name>.ts` |
|
||
| Sayfa düzeni | `yarn twenty add pageLayout` | `src/page-layouts/<name>.ts` |
|
||
|
||
### İskelet oluşturucunun ürettikleri
|
||
|
||
Her varlık türünün kendi şablonu vardır. Örneğin, `yarn twenty add object` şunları sorar:
|
||
|
||
1. **Ad (tekil)** — ör. `invoice`
|
||
2. **Ad (çoğul)** — ör. `invoices`
|
||
3. **Etiket (tekil)** — adından otomatik doldurulur (ör. `Invoice`)
|
||
4. **Etiket (çoğul)** — otomatik doldurulur (ör. `Invoices`)
|
||
5. **Bir görünüm ve gezinme öğesi oluşturulsun mu?** — evet derseniz, iskelet oluşturucu yeni nesne için eşleşen bir görünüm ve kenar çubuğu bağlantısı da üretir.
|
||
|
||
Diğer varlık türlerinin istemleri daha basittir — çoğu yalnızca bir ad sorar.
|
||
|
||
`field` varlık türü daha ayrıntılıdır: alan adını, etiketi, türü (`TEXT`, `NUMBER`, `SELECT`, `RELATION` vb. gibi mevcut tüm alan türlerinin listesinden) ve hedef nesnenin `universalIdentifier` değerini sorar.
|
||
|
||
### Özel çıktı yolu
|
||
|
||
`--path` bayrağını kullanarak oluşturulan dosyayı özel bir konuma yerleştirin:
|
||
|
||
```bash filename="Terminal"
|
||
yarn twenty add logicFunction --path src/custom-folder
|
||
```
|
||
|
||
## Tipli API istemcileri (twenty-client-sdk)
|
||
|
||
`twenty-client-sdk` paketi, mantık fonksiyonlarınızdan ve ön uç bileşenlerinizden Twenty API ile etkileşim kurmak için tip tanımlı iki GraphQL istemcisi sağlar.
|
||
|
||
| İstemci | İçe Aktar | Uç nokta | Oluşturuldu mu? |
|
||
| ------------------- | ---------------------------- | ------------------------------------------------------------- | --------------------------------------- |
|
||
| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — çalışma alanı verileri (kayıtlar, nesneler) | Evet, geliştirme/derleme zamanında |
|
||
| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — çalışma alanı yapılandırması, dosya yüklemeleri | Hayır, önceden hazırlanmış olarak gelir |
|
||
|
||
<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 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 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.
|
||
|
||
```ts
|
||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||
|
||
const metadataClient = new MetadataApiClient();
|
||
|
||
// List first 10 objects in the workspace
|
||
const { objects } = await metadataClient.query({
|
||
objects: {
|
||
edges: {
|
||
node: {
|
||
id: true,
|
||
nameSingular: true,
|
||
namePlural: true,
|
||
labelSingular: true,
|
||
isCustom: true,
|
||
},
|
||
},
|
||
__args: {
|
||
filter: {},
|
||
paging: { first: 10 },
|
||
},
|
||
},
|
||
});
|
||
```
|
||
|
||
#### Dosya yükleme
|
||
|
||
`MetadataApiClient`, dosya türü alanlara dosya eklemek için bir `uploadFile` yöntemi içerir:
|
||
|
||
```ts
|
||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||
import * as fs from 'fs';
|
||
|
||
const metadataClient = new MetadataApiClient();
|
||
|
||
const fileBuffer = fs.readFileSync('./invoice.pdf');
|
||
|
||
const uploadedFile = await metadataClient.uploadFile(
|
||
fileBuffer, // file contents as a Buffer
|
||
'invoice.pdf', // filename
|
||
'application/pdf', // MIME type
|
||
'58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier
|
||
);
|
||
|
||
console.log(uploadedFile);
|
||
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
|
||
```
|
||
|
||
| Parametre | Tür | Açıklama |
|
||
| ---------------------------------- | -------- | --------------------------------------------------------------------------------- |
|
||
| `fileBuffer` | `Buffer` | Dosyanın ham içeriği |
|
||
| `filename` | `string` | Dosyanın adı (depolama ve görüntüleme için kullanılır) |
|
||
| `contentType` | `string` | MIME türü (belirtilmezse varsayılan olarak `application/octet-stream` kullanılır) |
|
||
| `fieldMetadataUniversalIdentifier` | `string` | Nesnenizdeki dosya türü alanının `universalIdentifier` değeri |
|
||
|
||
Önemli noktalar:
|
||
* Alan için `universalIdentifier` kullanır (çalışma alanına özgü kimliği değil), böylece yükleme kodunuz uygulamanızın yüklü olduğu herhangi bir çalışma alanında çalışır.
|
||
* Döndürülen `url`, yüklenen dosyaya erişmek için kullanabileceğiniz imzalı bir URL'dir.
|
||
|
||
</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` — Uygulamanızın varsayılan fonksiyon rolü kapsamında kısa ömürlü anahtar
|
||
|
||
Bunları istemcilere iletmeniz gerekmez — otomatik olarak `process.env`'den okurlar. API anahtarının izinleri, `application-config.ts` içinde `defaultRoleUniversalIdentifier` ile referans verilen role göre belirlenir.
|
||
</Note>
|
||
|
||
## Uygulamanızı test etme
|
||
|
||
SDK, test kodundan uygulamanızı derlemenize, dağıtmanıza, yüklemenize ve kaldırmanıza olanak tanıyan programatik API'ler sağlar. Tiplenmiş API istemcileriyle birlikte [Vitest](https://vitest.dev/) kullanarak, uygulamanızın gerçek bir Twenty sunucusunda uçtan uca çalıştığını doğrulayan entegrasyon testleri yazabilirsiniz.
|
||
|
||
### Kurulum
|
||
|
||
İskelet aracıyla oluşturulan uygulama zaten Vitest'i içerir. Manuel kurulum yaparsanız, bağımlılıkları yükleyin:
|
||
|
||
```bash filename="Terminal"
|
||
yarn add -D vitest vite-tsconfig-paths
|
||
```
|
||
|
||
Uygulamanızın kök dizininde bir `vitest.config.ts` oluşturun:
|
||
|
||
```ts vitest.config.ts
|
||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||
import { defineConfig } from 'vitest/config';
|
||
|
||
export default defineConfig({
|
||
plugins: [
|
||
tsconfigPaths({
|
||
projects: ['tsconfig.spec.json'],
|
||
ignoreConfigErrors: true,
|
||
}),
|
||
],
|
||
test: {
|
||
testTimeout: 120_000,
|
||
hookTimeout: 120_000,
|
||
include: ['src/**/*.integration-test.ts'],
|
||
setupFiles: ['src/__tests__/setup-test.ts'],
|
||
env: {
|
||
TWENTY_API_URL: 'http://localhost:2020',
|
||
TWENTY_API_KEY: 'your-api-key',
|
||
},
|
||
},
|
||
});
|
||
```
|
||
|
||
Testler çalışmadan önce sunucuya erişilebildiğini doğrulayan bir kurulum dosyası oluşturun:
|
||
|
||
```ts src/__tests__/setup-test.ts
|
||
import * as fs from 'fs';
|
||
import * as os from 'os';
|
||
import * as path from 'path';
|
||
import { beforeAll } from 'vitest';
|
||
|
||
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
|
||
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
|
||
|
||
beforeAll(async () => {
|
||
// Verify the server is running
|
||
const response = await fetch(`${TWENTY_API_URL}/healthz`);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(
|
||
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
|
||
'Start the server before running integration tests.',
|
||
);
|
||
}
|
||
|
||
// Write a temporary config for the SDK
|
||
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
|
||
|
||
fs.writeFileSync(
|
||
path.join(TEST_CONFIG_DIR, 'config.json'),
|
||
JSON.stringify({
|
||
remotes: {
|
||
local: {
|
||
apiUrl: process.env.TWENTY_API_URL,
|
||
apiKey: process.env.TWENTY_API_KEY,
|
||
},
|
||
},
|
||
defaultRemote: 'local',
|
||
}, null, 2),
|
||
);
|
||
});
|
||
```
|
||
|
||
### Programatik SDK API'leri
|
||
|
||
`twenty-sdk/cli` alt yolu, test kodundan doğrudan çağırabileceğiniz fonksiyonları dışa aktarır:
|
||
|
||
| Fonksiyon | Açıklama |
|
||
| -------------- | ----------------------------------------------------------------- |
|
||
| `appBuild` | Uygulamayı derleyin ve isteğe bağlı olarak bir tarball paketleyin |
|
||
| `appDeploy` | Bir tarball'ı sunucuya yükleyin |
|
||
| `appInstall` | Uygulamayı etkin çalışma alanına yükleyin |
|
||
| `appUninstall` | Uygulamayı etkin çalışma alanından kaldırın |
|
||
|
||
Her fonksiyon, `success: boolean` ile birlikte `data` veya `error` içeren bir sonuç nesnesi döndürür.
|
||
|
||
### Bir entegrasyon testi yazma
|
||
|
||
İşte uygulamayı derleyen, dağıtan ve yükleyen; ardından çalışma alanında göründüğünü doğrulayan tam bir örnek:
|
||
|
||
```ts src/__tests__/app-install.integration-test.ts
|
||
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
|
||
import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli';
|
||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||
|
||
const APP_PATH = process.cwd();
|
||
|
||
describe('App installation', () => {
|
||
beforeAll(async () => {
|
||
const buildResult = await appBuild({
|
||
appPath: APP_PATH,
|
||
tarball: true,
|
||
onProgress: (message: string) => console.log(`[build] ${message}`),
|
||
});
|
||
|
||
if (!buildResult.success) {
|
||
throw new Error(`Build failed: ${buildResult.error?.message}`);
|
||
}
|
||
|
||
const deployResult = await appDeploy({
|
||
tarballPath: buildResult.data.tarballPath!,
|
||
onProgress: (message: string) => console.log(`[deploy] ${message}`),
|
||
});
|
||
|
||
if (!deployResult.success) {
|
||
throw new Error(`Deploy failed: ${deployResult.error?.message}`);
|
||
}
|
||
|
||
const installResult = await appInstall({ appPath: APP_PATH });
|
||
|
||
if (!installResult.success) {
|
||
throw new Error(`Install failed: ${installResult.error?.message}`);
|
||
}
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await appUninstall({ appPath: APP_PATH });
|
||
});
|
||
|
||
it('should find the installed app in the workspace', async () => {
|
||
const metadataClient = new MetadataApiClient();
|
||
|
||
const result = await metadataClient.query({
|
||
findManyApplications: {
|
||
id: true,
|
||
name: true,
|
||
universalIdentifier: true,
|
||
},
|
||
});
|
||
|
||
const installedApp = result.findManyApplications.find(
|
||
(app: { universalIdentifier: string }) =>
|
||
app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
|
||
);
|
||
|
||
expect(installedApp).toBeDefined();
|
||
});
|
||
});
|
||
```
|
||
|
||
### Testleri çalıştırma
|
||
|
||
Yerel Twenty sunucunuzun çalıştığından emin olun, ardından:
|
||
|
||
```bash filename="Terminal"
|
||
yarn test
|
||
```
|
||
|
||
Veya geliştirme sırasında izleme modunda:
|
||
|
||
```bash filename="Terminal"
|
||
yarn test:watch
|
||
```
|
||
|
||
### Tip denetimi
|
||
|
||
Ayrıca testleri çalıştırmadan uygulamanızda tip denetimi çalıştırabilirsiniz:
|
||
|
||
```bash filename="Terminal"
|
||
yarn twenty typecheck
|
||
```
|
||
|
||
Bu, `tsc --noEmit` komutunu çalıştırır ve tüm tip hatalarını raporlar.
|
||
|
||
## CLI başvurusu
|
||
|
||
`dev`, `build`, `add` ve `typecheck` dışında CLI, fonksiyonları çalıştırma, günlükleri görüntüleme ve uygulama kurulumlarını yönetme komutları sağlar.
|
||
|
||
### Fonksiyonları çalıştırma (`yarn twenty exec`)
|
||
|
||
Bir mantık fonksiyonunu HTTP, cron veya veritabanı olayıyla tetiklemeden manuel olarak çalıştırın:
|
||
|
||
```bash filename="Terminal"
|
||
# Execute by function name
|
||
yarn twenty exec -n create-new-post-card
|
||
|
||
# Execute by universalIdentifier
|
||
yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||
|
||
# Pass a JSON payload
|
||
yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}'
|
||
|
||
# Execute pre-install or post-install functions
|
||
yarn twenty exec --preInstall
|
||
yarn twenty exec --postInstall
|
||
```
|
||
|
||
### Fonksiyon günlüklerini görüntüleme (`yarn twenty logs`)
|
||
|
||
Uygulamanızın mantık fonksiyonlarının yürütme günlüklerini akış olarak alın:
|
||
|
||
```bash filename="Terminal"
|
||
# Stream all function logs
|
||
yarn twenty logs
|
||
|
||
# Filter by function name
|
||
yarn twenty logs -n create-new-post-card
|
||
|
||
# Filter by universalIdentifier
|
||
yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||
```
|
||
|
||
<Note>
|
||
Bu, Docker konteyner günlüklerini gösteren `yarn twenty server logs` komutundan farklıdır. `yarn twenty logs`, uygulamanızın fonksiyon yürütme günlüklerini Twenty sunucusundan gösterir.
|
||
</Note>
|
||
|
||
### Bir uygulamayı kaldırma (`yarn twenty uninstall`)
|
||
|
||
Uygulamanızı etkin çalışma alanından kaldırın:
|
||
|
||
```bash filename="Terminal"
|
||
yarn twenty uninstall
|
||
|
||
# Skip the confirmation prompt
|
||
yarn twenty uninstall --yes
|
||
```
|
||
|
||
## Uzakları yönetme
|
||
|
||
Bir **uzak**, uygulamanızın bağlandığı Twenty sunucusudur. Kurulum sırasında iskelet oluşturucu sizin için otomatik olarak bir tane oluşturur. Dilediğiniz zaman daha fazla uzak ekleyebilir veya aralarında geçiş yapabilirsiniz.
|
||
|
||
```bash filename="Terminal"
|
||
# Add a new remote (opens a browser for OAuth login)
|
||
yarn twenty remote add
|
||
|
||
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
|
||
yarn twenty remote add --local
|
||
|
||
# Add a remote non-interactively (useful for CI)
|
||
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
|
||
|
||
# List all configured remotes
|
||
yarn twenty remote list
|
||
|
||
# Switch the active remote
|
||
yarn twenty remote switch <name>
|
||
```
|
||
|
||
Kimlik bilgileriniz `~/.twenty/config.json` içinde saklanır.
|
||
|
||
## GitHub Actions ile CI
|
||
|
||
İskelet oluşturucu, `.github/workflows/ci.yml` konumunda kullanıma hazır bir GitHub Actions iş akışı üretir. Entegrasyon testlerinizi `main` dalına yapılan her itmede ve çekme isteklerinde otomatik olarak çalıştırır.
|
||
|
||
İş akışı:
|
||
|
||
1. Kodunuzu çalışma alanına alır
|
||
2. `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` eylemini kullanarak geçici bir Twenty sunucusu başlatır
|
||
3. `yarn install --immutable` ile bağımlılıkları kurar
|
||
4. Eylem çıktılarından enjekte edilen `TWENTY_API_URL` ve `TWENTY_API_KEY` ile `yarn test` çalıştırır
|
||
|
||
```yaml .github/workflows/ci.yml
|
||
name: CI
|
||
|
||
on:
|
||
push:
|
||
branches:
|
||
- main
|
||
pull_request: {}
|
||
|
||
env:
|
||
TWENTY_VERSION: latest
|
||
|
||
jobs:
|
||
test:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Checkout
|
||
uses: actions/checkout@v4
|
||
|
||
- name: Spawn Twenty instance
|
||
id: twenty
|
||
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
|
||
with:
|
||
twenty-version: ${{ env.TWENTY_VERSION }}
|
||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
- name: Enable Corepack
|
||
run: corepack enable
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version-file: '.nvmrc'
|
||
cache: 'yarn'
|
||
|
||
- name: Install dependencies
|
||
run: yarn install --immutable
|
||
|
||
- name: Run integration tests
|
||
run: yarn test
|
||
env:
|
||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
|
||
```
|
||
|
||
Herhangi bir gizli değişken yapılandırmanız gerekmez — `spawn-twenty-docker-image` eylemi, koşucu içinde doğrudan geçici bir Twenty sunucusu başlatır ve bağlantı ayrıntılarını çıktı olarak verir. `GITHUB_TOKEN` gizli değişkeni GitHub tarafından otomatik olarak sağlanır.
|
||
|
||
`latest` yerine belirli bir Twenty sürümünü sabitlemek için iş akışının başındaki `TWENTY_VERSION` ortam değişkenini değiştirin.
|