1428 lines
80 KiB
Plaintext
1428 lines
80 KiB
Plaintext
---
|
|
title: تطبيقات Twenty
|
|
description: أنشئ وأدِر تخصيصات Twenty على هيئة كود.
|
|
---
|
|
|
|
<Warning>
|
|
التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
|
|
</Warning>
|
|
|
|
## ما هي التطبيقات؟
|
|
|
|
تتيح لك التطبيقات إنشاء وإدارة تخصيصات Twenty **ككود**. بدلًا من تكوين كل شيء عبر واجهة المستخدم، تُعرِّف نموذج بياناتك ووظائف المنطق في الكود — مما يجعل البناء والصيانة والنشر إلى مساحات عمل متعددة أسرع.
|
|
|
|
**ما الذي يمكنك فعله اليوم:**
|
|
|
|
* عرِّف كائنات وحقولًا مخصصة على شكل كود (نموذج بيانات مُدار)
|
|
* أنشئ وظائف منطقية مع مشغلات مخصصة
|
|
* تعريف المهارات والوكلاء للذكاء الاصطناعي
|
|
* انشر التطبيق نفسه عبر مساحات عمل متعددة
|
|
|
|
## المتطلبات الأساسية
|
|
|
|
* Node.js 24+ وYarn 4
|
|
* Docker (لخادم تطوير Twenty المحلي)
|
|
|
|
## البدء
|
|
|
|
أنشئ تطبيقًا جديدًا باستخدام المولّد الرسمي. يمكنه بدء مثيل محلي من Twenty تلقائيًا لك:
|
|
|
|
```bash filename="Terminal"
|
|
# إنشاء تطبيق جديد — ستعرض واجهة سطر الأوامر خيار بدء خادم Twenty محلي
|
|
npx create-twenty-app@latest my-twenty-app
|
|
cd my-twenty-app
|
|
|
|
# ابدأ وضع التطوير: يُزامن التغييرات المحلية تلقائيًا مع مساحة العمل الخاصة بك
|
|
yarn twenty dev
|
|
```
|
|
|
|
### إدارة الخادم المحلي
|
|
|
|
يتضمن SDK أوامر لإدارة خادم تطوير Twenty محلي (صورة Docker متكاملة تتضمن PostgreSQL وRedis والخادم والعامل):
|
|
|
|
```bash filename="Terminal"
|
|
# ابدأ الخادم المحلي (يسحب الصورة إذا لزم الأمر)
|
|
yarn twenty server start
|
|
|
|
# تحقّق من حالة الخادم
|
|
yarn twenty server status
|
|
|
|
# بثّ سجلات الخادم
|
|
yarn twenty server logs
|
|
|
|
# أوقف الخادم
|
|
yarn twenty server stop
|
|
|
|
# أعد ضبط جميع البيانات وابدأ من جديد
|
|
yarn twenty server reset
|
|
```
|
|
|
|
يأتي الخادم المحلي مهيأً مسبقًا بمساحة عمل ومستخدم (`tim@apple.dev` / `tim@apple.dev`)، بحيث يمكنك البدء في التطوير فورًا دون أي إعداد يدوي.
|
|
|
|
### المصادقة
|
|
|
|
وصّل تطبيقك بالخادم المحلي باستخدام OAuth:
|
|
|
|
```bash filename="Terminal"
|
|
# المصادقة عبر OAuth (يفتح المتصفح)
|
|
yarn twenty remote add --local
|
|
```
|
|
|
|
يدعم المُنشئ وضعين للتحكم في ملفات الأمثلة التي سيتم تضمينها:
|
|
|
|
```bash filename="Terminal"
|
|
# الافتراضي (شامل): جميع الأمثلة (كائن، حقل، دالة منطقية، مكوّن الواجهة الأمامية، عرض، عنصر قائمة التنقل، مهارة، وكيل)
|
|
npx create-twenty-app@latest my-app
|
|
|
|
# الأدنى: الملفات الأساسية فقط (application-config.ts و default-role.ts)
|
|
npx create-twenty-app@latest my-app --minimal
|
|
```
|
|
|
|
### كيفية استخدام مثيل محلي من Twenty
|
|
|
|
إذا كنت تقوم بتشغيل مثيل محلي من Twenty بالفعل، فيمكنك الاتصال به بدلًا من استخدام Docker. مرِّر المنفذ الذي يستمع عليه الخادم المحلي لديك (القيمة الافتراضية: `3000`):
|
|
|
|
```bash filename="Terminal"
|
|
# During scaffolding
|
|
npx create-twenty-app@latest my-app --port 3000
|
|
|
|
# Or after scaffolding
|
|
yarn twenty remote add --local --port 3000
|
|
```
|
|
|
|
من هنا يمكنك:
|
|
|
|
```bash filename="Terminal"
|
|
# أضف كيانًا جديدًا إلى تطبيقك (موجّه)
|
|
yarn twenty entity:add
|
|
|
|
# راقب سجلات وظائف تطبيقك
|
|
yarn twenty function:logs
|
|
|
|
# نفّذ وظيفة بالاسم
|
|
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
|
|
|
# نفّذ دالة ما قبل التثبيت
|
|
yarn twenty function:execute --preInstall
|
|
|
|
# نفّذ دالة ما بعد التثبيت
|
|
yarn twenty function:execute --postInstall
|
|
|
|
# ابنِ التطبيق للتوزيع
|
|
yarn twenty build
|
|
|
|
# انشر التطبيق إلى npm أو إلى خادم Twenty
|
|
yarn twenty publish
|
|
|
|
# أزل تثبيت التطبيق من مساحة العمل الحالية
|
|
yarn twenty uninstall
|
|
|
|
# اعرض مساعدة الأوامر
|
|
yarn twenty help
|
|
```
|
|
|
|
راجع أيضًا: صفحات مرجع CLI لـ [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) و[twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
|
|
|
## هيكل المشروع (مُنشأ بالقالب)
|
|
|
|
عند تشغيل `npx create-twenty-app@latest my-twenty-app`، يقوم المُهيئ بما يلي:
|
|
|
|
* ينسخ تطبيقًا أساسيًا مصغّرًا إلى `my-twenty-app/`
|
|
* يضيف اعتمادًا محليًا `twenty-sdk` وتهيئة Yarn 4
|
|
* ينشئ ملفات ضبط ونصوصًا مرتبطة بـ `twenty` CLI
|
|
* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالتا ما قبل التثبيت وما بعد التثبيت) بالإضافة إلى ملفات أمثلة استنادًا إلى وضع الإنشاء.
|
|
|
|
يبدو التطبيق المُنشأ حديثًا باستخدام الوضع الافتراضي `--exhaustive` كما يلي:
|
|
|
|
```text filename="my-twenty-app/"
|
|
my-twenty-app/
|
|
package.json
|
|
yarn.lock
|
|
.gitignore
|
|
.nvmrc
|
|
.yarnrc.yml
|
|
.yarn/
|
|
install-state.gz
|
|
.oxlintrc.json
|
|
tsconfig.json
|
|
README.md
|
|
public/ # مجلد الأصول العامة (صور، خطوط، إلخ)
|
|
src/
|
|
├── application-config.ts # مطلوب - إعدادات التطبيق الرئيسية
|
|
├── roles/
|
|
│ └── default-role.ts # الدور الافتراضي للدوال المنطقية
|
|
├── objects/
|
|
│ └── example-object.ts # تعريف كائن مخصص — مثال
|
|
├── fields/
|
|
│ └── example-field.ts # تعريف حقل مستقل — مثال
|
|
├── logic-functions/
|
|
│ ├── hello-world.ts # دالة منطقية — مثال
|
|
│ ├── pre-install.ts # دالة منطقية لما قبل التثبيت
|
|
│ └── post-install.ts # دالة منطقية لما بعد التثبيت
|
|
├── front-components/
|
|
│ └── hello-world.tsx # مكوّن واجهة أمامية — مثال
|
|
├── views/
|
|
│ └── example-view.ts # تعريف عرض محفوظ — مثال
|
|
├── navigation-menu-items/
|
|
│ └── example-navigation-menu-item.ts # رابط تنقّل في الشريط الجانبي — مثال
|
|
├── skills/
|
|
│ └── example-skill.ts # تعريف مهارة لوكيل الذكاء الاصطناعي — مثال
|
|
└── agents/
|
|
└── example-agent.ts # تعريف وكيل الذكاء الاصطناعي — مثال
|
|
```
|
|
|
|
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`).
|
|
|
|
بشكل عام:
|
|
|
|
* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` بالإضافة إلى نص برمجي `twenty` يفوِّض إلى `twenty` CLI المحلي. شغِّل `yarn twenty help` لعرض جميع الأوامر المتاحة.
|
|
* **.gitignore**: يتجاهل العناصر الشائعة مثل `node_modules` و`.yarn` و`generated/` (عميل مضبوط الأنواع) و`dist/` و`build/` ومجلدات التغطية وملفات السجلات وملفات `.env*`.
|
|
* **yarn.lock**، **.yarnrc.yml**، **.yarn/**: تقوم بقفل وتكوين حزمة أدوات Yarn 4 المستخدمة في المشروع.
|
|
* **.nvmrc**: يثبّت إصدار Node.js المتوقع للمشروع.
|
|
* **.oxlintrc.json** و **tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك.
|
|
* **README.md**: ملف README قصير في جذر التطبيق يتضمن تعليمات أساسية.
|
|
* **public/**: مجلد لتخزين الأصول العامة (صور، خطوط، ملفات ثابتة) التي سيتم تقديمها مع تطبيقك. الملفات الموضوعة هنا تُرفع أثناء المزامنة وتكون متاحة أثناء وقت التشغيل.
|
|
* **src/**: المكان الرئيسي حيث تعرّف تطبيقك ككود
|
|
|
|
### اكتشاف الكيانات
|
|
|
|
يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define<Entity>({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`:
|
|
|
|
| دالة مساعدة | نوع الكيان |
|
|
| ---------------------------------- | ---------------------------------------------- |
|
|
| `defineObject()` | تعريفات كائنات مخصصة |
|
|
| `defineLogicFunction()` | تعريفات الوظائف المنطقية |
|
|
| `definePreInstallLogicFunction()` | دالة منطقية لما قبل التثبيت (تعمل قبل التثبيت) |
|
|
| `definePostInstallLogicFunction()` | دالة منطقية لما بعد التثبيت (تعمل بعد التثبيت) |
|
|
| `defineFrontComponent()` | Front component definitions |
|
|
| `defineRole()` | تعريفات الأدوار |
|
|
| `defineField()` | امتدادات الحقول للكائنات الموجودة |
|
|
| `defineView()` | تعريفات العروض المحفوظة |
|
|
| `defineNavigationMenuItem()` | تعريفات عناصر قائمة التنقل |
|
|
| `defineSkill()` | تعريفات مهارات وكيل الذكاء الاصطناعي |
|
|
| `defineAgent()` | تعريفات وكلاء الذكاء الاصطناعي |
|
|
|
|
<Note>
|
|
**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define<Entity>({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا.
|
|
</Note>
|
|
|
|
مثال على كيان تم اكتشافه:
|
|
|
|
```typescript
|
|
// This file can be named anything and placed anywhere in src/
|
|
import { defineObject, FieldType } from 'twenty-sdk';
|
|
|
|
export default defineObject({
|
|
universalIdentifier: '...',
|
|
nameSingular: 'postCard',
|
|
// ... rest of config
|
|
});
|
|
```
|
|
|
|
ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات:
|
|
|
|
* سيقوم `yarn twenty dev` بتوليد عميلين API مضبوطي الأنواع تلقائيًا في `node_modules/twenty-sdk/clients`: `CoreApiClient` (لبيانات مساحة العمل عبر `/graphql`) و`MetadataApiClient` (لتكوين مساحة العمل وتحميل الملفات عبر `/metadata`).
|
|
* `yarn twenty entity:add` سيضيف ملفات تعريف الكيانات ضمن `src/` لكائناتك المخصّصة، والوظائف، ومكوّنات الواجهة الأمامية، والأدوار، والمهارات، وغير ذلك.
|
|
|
|
## المصادقة
|
|
|
|
في المرة الأولى التي تشغّل فيها `yarn twenty auth:login`، سيُطلب منك إدخال:
|
|
|
|
* عنوان URL لواجهة برمجة التطبيقات (الافتراضي http://localhost:3000 أو ملف تعريف مساحة العمل الحالية لديك)
|
|
* مفتاح واجهة برمجة التطبيقات
|
|
|
|
تُخزَّن بيانات اعتمادك لكل مستخدم في `~/.twenty/config.json`. You can maintain multiple profiles and switch between them.
|
|
|
|
### Managing workspaces
|
|
|
|
```bash filename="Terminal"
|
|
# تسجيل الدخول تفاعليًا (مُوصى به)
|
|
yarn twenty auth:login
|
|
|
|
# تسجيل الدخول إلى ملف تعريف لمساحة عمل محددة
|
|
yarn twenty auth:login --workspace my-custom-workspace
|
|
|
|
# عرض جميع مساحات العمل المُكوَّنة
|
|
yarn twenty auth:list
|
|
|
|
# تبديل مساحة العمل الافتراضية (تفاعليًا)
|
|
yarn twenty auth:switch
|
|
|
|
# التبديل إلى مساحة عمل محددة
|
|
yarn twenty auth:switch production
|
|
|
|
# التحقق من حالة المصادقة الحالية
|
|
yarn twenty auth:status
|
|
```
|
|
|
|
بمجرد أن تقوم بالتبديل بين مساحات العمل باستخدام `yarn twenty auth:switch`، ستستخدم جميع الأوامر اللاحقة تلك المساحة افتراضيًا. You can still override it temporarily with `--workspace <name>`.
|
|
|
|
## استخدم موارد SDK (الأنواع والتكوين)
|
|
|
|
يوفّر twenty-sdk كتلَ بناءٍ مضبوطة الأنواع ودوال مساعدة تستخدمها داخل تطبيقك. فيما يلي الأجزاء الأساسية التي ستتعامل معها غالبًا.
|
|
|
|
### دوال مساعدة
|
|
|
|
يوفّر SDK دوالًا مساعدة لتعريف كيانات تطبيقك. كما هو موضح في [اكتشاف الكيانات](#entity-detection)، يجب استخدام `export default define<Entity>({...})` كي يتم اكتشاف كياناتك:
|
|
|
|
| دالة | الغرض |
|
|
| ---------------------------------- | ---------------------------------------------------- |
|
|
| `defineApplication()` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) |
|
|
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
|
|
| `defineLogicFunction()` | تعريف وظائف منطقية مع معالجات |
|
|
| `definePreInstallLogicFunction()` | تعريف دالة منطقية لما قبل التثبيت (واحدة لكل تطبيق) |
|
|
| `definePostInstallLogicFunction()` | تعريف دالة منطقية لما بعد التثبيت (واحدة لكل تطبيق) |
|
|
| `defineFrontComponent()` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة |
|
|
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
|
|
| `defineField()` | وسّع الكائنات الموجودة بحقول إضافية |
|
|
| `defineView()` | تعريف العروض المحفوظة للكائنات |
|
|
| `defineNavigationMenuItem()` | تعريف روابط التنقل في الشريط الجانبي |
|
|
| `defineSkill()` | عرّف مهارات وكيل الذكاء الاصطناعي |
|
|
| `defineAgent()` | عرِّف وكلاء الذكاء الاصطناعي باستخدام موجهات النظام |
|
|
|
|
تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع.
|
|
|
|
### تعريف الكائنات
|
|
|
|
تصف الكائنات المخصصة كلًا من المخطط والسلوك للسجلات في مساحة عملك. استخدم `defineObject()` لتعريف كائنات مع تحقق مدمج:
|
|
|
|
```typescript
|
|
// src/app/postCard.object.ts
|
|
import { defineObject, FieldType } from 'twenty-sdk';
|
|
|
|
enum PostCardStatus {
|
|
DRAFT = 'DRAFT',
|
|
SENT = 'SENT',
|
|
DELIVERED = 'DELIVERED',
|
|
RETURNED = 'RETURNED',
|
|
}
|
|
|
|
export default defineObject({
|
|
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
|
|
nameSingular: 'postCard',
|
|
namePlural: 'postCards',
|
|
labelSingular: 'Post Card',
|
|
labelPlural: 'Post Cards',
|
|
description: 'A post card object',
|
|
icon: 'IconMail',
|
|
fields: [
|
|
{
|
|
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
|
|
name: 'content',
|
|
type: FieldType.TEXT,
|
|
label: 'Content',
|
|
description: "Postcard's content",
|
|
icon: 'IconAbc',
|
|
},
|
|
{
|
|
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
|
|
name: 'recipientName',
|
|
type: FieldType.FULL_NAME,
|
|
label: 'Recipient name',
|
|
icon: 'IconUser',
|
|
},
|
|
{
|
|
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
|
|
name: 'recipientAddress',
|
|
type: FieldType.ADDRESS,
|
|
label: 'Recipient address',
|
|
icon: 'IconHome',
|
|
},
|
|
{
|
|
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
|
|
name: 'status',
|
|
type: FieldType.SELECT,
|
|
label: 'Status',
|
|
icon: 'IconSend',
|
|
defaultValue: `'${PostCardStatus.DRAFT}'`,
|
|
options: [
|
|
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
|
|
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
|
|
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
|
|
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
|
|
],
|
|
},
|
|
{
|
|
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
|
|
name: 'deliveredAt',
|
|
type: FieldType.DATE_TIME,
|
|
label: 'Delivered at',
|
|
icon: 'IconCheck',
|
|
isNullable: true,
|
|
defaultValue: null,
|
|
},
|
|
],
|
|
});
|
|
```
|
|
|
|
النقاط الرئيسية:
|
|
|
|
* استخدم `defineObject()` للحصول على تحقق مدمج ودعم أفضل من IDE.
|
|
* `universalIdentifier` يجب أن يكون فريدًا وثابتًا عبر عمليات النشر.
|
|
* يتطلب كل حقل `name` و`type` و`label` ومعرّف `universalIdentifier` ثابتًا خاصًا به.
|
|
* المصفوفة `fields` اختيارية — يمكنك تعريف كائنات بدون حقول مخصصة.
|
|
* يمكنك إنشاء كائنات جديدة باستخدام `yarn twenty entity:add`، والذي يرشدك خلال التسمية والحقول والعلاقات.
|
|
|
|
<Note>
|
|
**يتم إنشاء الحقول الأساسية تلقائيًا.** عند تعريف كائن مخصص، يضيف Twenty تلقائيًا حقولًا قياسية
|
|
مثل `id` و`name` و`createdAt` و`updatedAt` و`createdBy` و`updatedBy` و`deletedAt`.
|
|
لا تحتاج إلى تعريف هذه في مصفوفة `fields` — أضف فقط حقولك المخصصة.
|
|
يمكنك تجاوز الحقول الافتراضية من خلال تعريف حقل بالاسم نفسه في مصفوفة `fields` الخاصة بك،
|
|
لكن هذا غير مستحسن.
|
|
</Note>
|
|
|
|
### تعريف الحقول على الكائنات الموجودة
|
|
|
|
استخدم `defineField()` لإضافة حقول مخصصة إلى الكائنات الموجودة — سواء الكائنات القياسية (مثل `company` و`person` و`opportunity`) أو الكائنات المخصصة التي تُعرِّفها تطبيقات أخرى. يوجد كل حقل في ملفه الخاص ويشير إلى الكائن الهدف بواسطة `universalIdentifier` الخاص به.
|
|
|
|
للإشارة إلى الكائنات القياسية، استورد `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` من `twenty-sdk`. يوفر هذا الثابت معرِّفات مستقرة لجميع الكائنات المضمنة وحقولها:
|
|
|
|
```typescript
|
|
// src/fields/apollo-total-funding.field.ts
|
|
import {
|
|
defineField,
|
|
FieldType,
|
|
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
|
} from 'twenty-sdk';
|
|
|
|
export default defineField({
|
|
universalIdentifier: 'c90ae72d-4ddf-4f22-882f-eef98c91e40e',
|
|
objectUniversalIdentifier:
|
|
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
|
|
type: FieldType.CURRENCY,
|
|
name: 'apolloTotalFunding',
|
|
label: 'Total Funding',
|
|
description: 'Total funding raised by the company',
|
|
icon: 'IconCash',
|
|
});
|
|
```
|
|
|
|
النقاط الرئيسية:
|
|
|
|
* `objectUniversalIdentifier` يُحدِّد لـ Twenty الكائن الذي سيُرفَق به الحقل. استخدم `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.<objectName>.universalIdentifier` للكائنات القياسية.
|
|
* يتطلب كل حقل `universalIdentifier` ثابتًا خاصًا به، و`name`، و`type`، و`label`، و`objectUniversalIdentifier` الخاص بالكائن الهدف.
|
|
* يمكنك إنشاء حقول جديدة باستخدام `yarn twenty entity:add` واختيار خيار الحقل.
|
|
* يُصدَّر `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` أيضًا باسم `STANDARD_OBJECT` لسهولة الاستخدام — كلاهما يشير إلى الثابت نفسه.
|
|
|
|
تشمل الكائنات القياسية المتاحة: `attachment`، `blocklist`، `calendarChannel`، `calendarEvent`، `calendarEventParticipant`، `company`، `connectedAccount`، `dashboard`، `favorite`، `favoriteFolder`، `message`، `messageChannel`، `messageParticipant`، `messageThread`، `note`، `noteTarget`، `opportunity`، `person`، `task`، `taskTarget`، `timelineActivity`، `workflow`، `workflowAutomatedTrigger`، `workflowRun`، `workflowVersion`، و`workspaceMember`.
|
|
|
|
يُوفِّر كل كائن قياسي أيضًا معرّفات حقوله. على سبيل المثال، للإشارة إلى حقل محدد على كائن قياسي ضمن أذونات الأدوار:
|
|
|
|
```typescript
|
|
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier
|
|
```
|
|
|
|
#### حقول العلاقات على الكائنات الموجودة
|
|
|
|
يمكنك أيضًا تعريف حقول علاقات تربط الكائنات الموجودة بكائناتك المخصصة:
|
|
|
|
```typescript
|
|
// src/fields/people-on-call-recording.field.ts
|
|
import { defineField, FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk';
|
|
import { CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/call-recording';
|
|
import { CALL_RECORDING_ON_PERSON_ID } from 'src/fields/call-recording-on-person.field';
|
|
|
|
export default defineField({
|
|
universalIdentifier: '4a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d',
|
|
objectUniversalIdentifier:
|
|
CALL_RECORDING_OBJECT_UNIVERSAL_IDENTIFIER,
|
|
type: FieldType.RELATION,
|
|
name: 'person',
|
|
label: 'Person',
|
|
relationTargetObjectMetadataUniversalIdentifier:
|
|
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
|
|
relationTargetFieldMetadataUniversalIdentifier:
|
|
CALL_RECORDING_ON_PERSON_ID,
|
|
relationType: RelationType.MANY_TO_ONE,
|
|
});
|
|
```
|
|
|
|
### تكوين التطبيق (application-config.ts)
|
|
|
|
كل تطبيق لديه ملف واحد `application-config.ts` يصف:
|
|
|
|
* **هوية التطبيق**: المعرفات، اسم العرض، والوصف.
|
|
* **كيفية تشغيل وظائفه**: الدور الذي تستخدمه للأذونات.
|
|
* **متغيرات (اختياري)**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة.
|
|
* **(اختياري) دالة ما قبل التثبيت**: دالة منطقية تعمل قبل تثبيت التطبيق.
|
|
* **(Optional) post-install function**: a logic function that runs after the app is installed.
|
|
|
|
Use `defineApplication()` to define your application configuration:
|
|
|
|
```typescript
|
|
// src/application-config.ts
|
|
import { defineApplication } from 'twenty-sdk';
|
|
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
|
|
|
export default defineApplication({
|
|
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
|
displayName: 'تطبيق Twenty الخاص بي',
|
|
description: 'أول تطبيق لي لـ Twenty',
|
|
icon: 'IconWorld',
|
|
applicationVariables: {
|
|
DEFAULT_RECIPIENT_NAME: {
|
|
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
|
|
description: 'الاسم الافتراضي للمستلم للبطاقات البريدية',
|
|
value: 'Jane Doe',
|
|
isSecret: false,
|
|
},
|
|
},
|
|
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
|
});
|
|
```
|
|
|
|
الملاحظات:
|
|
|
|
* حقول `universalIdentifier` هي معرّفات حتمية تخصك؛ أنشئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة.
|
|
* `applicationVariables` تصبح متغيرات بيئة لوظائفك (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`).
|
|
* `defaultRoleUniversalIdentifier` يجب أن يطابق ملف الدور (انظر أدناه).
|
|
* يتم اكتشاف دوال ما قبل التثبيت وما بعد التثبيت تلقائيًا أثناء إنشاء ملف البيان. راجع [دوال ما قبل التثبيت](#pre-install-functions) و[دوال ما بعد التثبيت](#post-install-functions).
|
|
|
|
#### الأدوار والصلاحيات
|
|
|
|
يمكن للتطبيقات تعريف أدوار تُغلّف الصلاحيات على كائنات وإجراءات مساحة العمل لديك. يعين الحقل `defaultRoleUniversalIdentifier` في `application-config.ts` الدور الافتراضي الذي تستخدمه وظائف المنطق في تطبيقك.
|
|
|
|
* مفتاح واجهة البرمجة في وقت التشغيل المحقون باسم `TWENTY_API_KEY` مستمد من دور الوظيفة الافتراضي هذا.
|
|
* سيُقيَّد العميل مضبوط الأنواع بالأذونات الممنوحة لذلك الدور.
|
|
* اتبع مبدأ أقل الامتياز: أنشئ دورًا مخصصًا بالأذونات التي تحتاجها وظائفك فقط، ثم أشِر إلى معرّفه الشامل.
|
|
|
|
##### الدور الافتراضي للوظيفة (\*.role.ts)
|
|
|
|
عند توليد تطبيق جديد بالقالب، ينشئ CLI أيضًا ملف دور افتراضي. استخدم `defineRole()` لتعريف أدوار مع تحقق مدمج:
|
|
|
|
```typescript
|
|
// 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: false,
|
|
canUpdateAllObjectRecords: false,
|
|
canSoftDeleteAllObjectRecords: false,
|
|
canDestroyAllObjectRecords: false,
|
|
canUpdateAllSettings: false,
|
|
canBeAssignedToAgents: false,
|
|
canBeAssignedToUsers: false,
|
|
canBeAssignedToApiKeys: false,
|
|
objectPermissions: [
|
|
{
|
|
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
|
canReadObjectRecords: true,
|
|
canUpdateObjectRecords: true,
|
|
canSoftDeleteObjectRecords: false,
|
|
canDestroyObjectRecords: false,
|
|
},
|
|
],
|
|
fieldPermissions: [
|
|
{
|
|
objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050',
|
|
fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff',
|
|
canReadFieldValue: false,
|
|
canUpdateFieldValue: false,
|
|
},
|
|
],
|
|
permissionFlags: [PermissionFlag.APPLICATIONS],
|
|
});
|
|
```
|
|
|
|
يُشار بعد ذلك إلى `universalIdentifier` لهذا الدور في `application-config.ts` باسم `defaultRoleUniversalIdentifier`. بعبارة أخرى:
|
|
|
|
* **\\*.role.ts** يحدد ما يمكن أن يفعله الدور الافتراضي للوظيفة.
|
|
* **application-config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك أذوناته.
|
|
|
|
الملاحظات:
|
|
|
|
* ابدأ من الدور المُنشأ بالقالب، ثم قيّده تدريجيًا باتباع مبدأ أقل الامتياز.
|
|
* استبدل `objectPermissions` و`fieldPermissions` بالكائنات/الحقول التي تحتاجها وظائفك.
|
|
* `permissionFlags` تتحكم في الوصول إلى القدرات على مستوى المنصة. اجعلها في الحد الأدنى؛ أضف فقط ما تحتاجه.
|
|
* اطّلع على مثال عملي في تطبيق Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
|
|
|
### تكوين الوظيفة المنطقية ونقطة الدخول
|
|
|
|
كل ملف وظيفة يستخدم `defineLogicFunction()` لتصدير تكوين مع معالج ومشغّلات اختيارية.
|
|
|
|
```typescript
|
|
// src/app/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,
|
|
triggers: [
|
|
// مشغّل مسار HTTP عام '/s/post-card/create'
|
|
{
|
|
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
|
|
type: 'route',
|
|
path: '/post-card/create',
|
|
httpMethod: 'GET',
|
|
isAuthRequired: false,
|
|
},
|
|
// مُشغّل كرون (نمط CRON)
|
|
// {
|
|
// universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
|
|
// type: 'cron',
|
|
// pattern: '0 0 1 1 *',
|
|
// },
|
|
// مُشغّل حدث قاعدة البيانات
|
|
// {
|
|
// universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
|
|
// type: 'databaseEvent',
|
|
// eventName: 'person.updated',
|
|
// updatedFields: ['name'],
|
|
// },
|
|
],
|
|
});
|
|
```
|
|
|
|
أنواع المشغلات الشائعة:
|
|
|
|
* **route**: يعرِض وظيفتك على مسار وطريقة HTTP **تحت نقطة النهاية `/s/`**:
|
|
|
|
> مثال: `path: '/post-card/create',` -> الاستدعاء على `<APP_URL>/s/post-card/create`
|
|
|
|
* **cron**: يشغّل وظيفتك على جدول باستخدام تعبير CRON.
|
|
* **databaseEvent**: يعمل على أحداث دورة حياة كائنات مساحة العمل. عندما تكون عملية الحدث هي `updated`، يمكن تحديد الحقول المحددة المراد الاستماع إليها في مصفوفة `updatedFields`. إذا تُركت غير معرّفة أو فارغة، فسيؤدي أي تحديث إلى تشغيل الدالة.
|
|
|
|
> مثال: `person.updated`
|
|
|
|
الملاحظات:
|
|
|
|
* المصفوفة `triggers` اختيارية. يمكن استخدام الوظائف بدون مشغلات كوظائف مساعدة تُستدعى بواسطة وظائف أخرى.
|
|
* يمكنك مزج أنواع متعددة من المشغلات في وظيفة واحدة.
|
|
|
|
### دوال ما قبل التثبيت
|
|
|
|
دالة ما قبل التثبيت هي دالة منطقية تعمل تلقائيًا قبل تثبيت تطبيقك على مساحة عمل. يفيد ذلك في مهام التحقق، وفحص المتطلبات المسبقة، أو تجهيز حالة مساحة العمل قبل متابعة التثبيت الرئيسي.
|
|
|
|
عند إنشاء هيكل تطبيق جديد باستخدام `create-twenty-app`، يتم إنشاء دالة ما قبل التثبيت لك في `src/logic-functions/pre-install.ts`:
|
|
|
|
```typescript
|
|
// 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: '<generated-uuid>',
|
|
name: 'pre-install',
|
|
description: 'Runs before installation to prepare the application.',
|
|
timeoutSeconds: 300,
|
|
handler,
|
|
});
|
|
```
|
|
|
|
يمكنك أيضًا تنفيذ دالة ما قبل التثبيت يدويًا في أي وقت باستخدام CLI:
|
|
|
|
```bash filename="Terminal"
|
|
yarn twenty function:execute --preInstall
|
|
```
|
|
|
|
النقاط الرئيسية:
|
|
|
|
* تستخدم دوال ما قبل التثبيت `definePreInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`).
|
|
* يتلقى المُعالج `InstallLogicFunctionPayload` يحوي `{ previousVersion: string }` — إصدار التطبيق الذي كان مُثبّتًا سابقًا (أو سلسلة فارغة للتثبيتات الجديدة).
|
|
* يُسمح بدالة ما قبل التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة.
|
|
* يتم تعيين `universalIdentifier` للدالة تلقائيًا كـ `preInstallLogicFunctionUniversalIdentifier` في بيان التطبيق أثناء الإنشاء — لست بحاجة إلى الإشارة إليه في `defineApplication()`.
|
|
* تم ضبط المهلة الافتراضية على 300 ثانية (5 دقائق) للسماح بمهام التحضير الأطول.
|
|
* لا تحتاج دوال ما قبل التثبيت إلى مُشغِّلات — إذ يستدعيها النظام الأساسي قبل التثبيت أو يدويًا عبر `function:execute --preInstall`.
|
|
|
|
### Post-install functions
|
|
|
|
A post-install function is a logic function that runs automatically after your app is installed on a workspace. This is useful for one-time setup tasks such as seeding default data, creating initial records, or configuring workspace settings.
|
|
|
|
عند إنشاء هيكل تطبيق جديد باستخدام `create-twenty-app`، يتم إنشاء دالة ما بعد التثبيت لك في `src/logic-functions/post-install.ts`:
|
|
|
|
```typescript
|
|
// 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: '<generated-uuid>',
|
|
name: 'post-install',
|
|
description: 'Runs after installation to set up the application.',
|
|
timeoutSeconds: 300,
|
|
handler,
|
|
});
|
|
```
|
|
|
|
يمكنك أيضًا تنفيذ دالة ما بعد التثبيت يدويًا في أي وقت باستخدام CLI:
|
|
|
|
```bash filename="Terminal"
|
|
yarn twenty function:execute --postInstall
|
|
```
|
|
|
|
النقاط الرئيسية:
|
|
|
|
* تستخدم دوال ما بعد التثبيت `definePostInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`).
|
|
* يتلقى المُعالج `InstallLogicFunctionPayload` يحوي `{ previousVersion: string }` — إصدار التطبيق الذي كان مُثبّتًا سابقًا (أو سلسلة فارغة للتثبيتات الجديدة).
|
|
* يُسمح بدالة ما بعد التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة.
|
|
* يتم تعيين `universalIdentifier` للدالة تلقائيًا كـ `postInstallLogicFunctionUniversalIdentifier` في بيان التطبيق أثناء الإنشاء — لست بحاجة إلى الإشارة إليه في `defineApplication()`.
|
|
* تم تعيين مهلة افتراضية إلى 300 ثانية (5 دقائق) للسماح بمهام الإعداد الأطول مثل تهيئة البيانات.
|
|
* لا تحتاج دوال ما بعد التثبيت إلى مُشغِّلات — حيث يستدعيها النظام الأساسي أثناء التثبيت أو يدويًا عبر `function:execute --postInstall`.
|
|
|
|
### حمولة مشغل المسار
|
|
|
|
<Warning>
|
|
**تغيير غير متوافق (v1.16، يناير 2026):** لقد تغير تنسيق حمولة مشغل المسار. قبل v1.16، كانت معلمات الاستعلام، ومعلمات المسار، وجسم الطلب تُرسل مباشرةً كحمولة. بدءًا من v1.16، أصبحت متداخلة داخل كائن منظَّم `RoutePayload`.
|
|
|
|
**قبل v1.16:**
|
|
```typescript
|
|
const handler = async (params) => {
|
|
const { param1, param2 } = params; // Direct access
|
|
};
|
|
```
|
|
|
|
**بعد v1.16:**
|
|
```typescript
|
|
const handler = async (event: RoutePayload) => {
|
|
const { param1, param2 } = event.body; // Access via .body
|
|
const { queryParam } = event.queryStringParameters;
|
|
const { id } = event.pathParameters;
|
|
};
|
|
```
|
|
|
|
**لترحيل الدوال الحالية:** حدّث المعالج لديك لفكّ البنية من `event.body` أو `event.queryStringParameters` أو `event.pathParameters` بدلاً من القراءة مباشرةً من كائن params.
|
|
</Warning>
|
|
|
|
عندما يستدعي مشغّل المسار وظيفتك المنطقية، يتلقى كائنًا من النوع `RoutePayload` يتبع تنسيق AWS HTTP API v2. استورد النوع من `twenty-sdk`:
|
|
|
|
```typescript
|
|
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
|
|
|
|
const handler = async (event: RoutePayload) => {
|
|
// Access request data
|
|
const { headers, queryStringParameters, pathParameters, body } = event;
|
|
|
|
// HTTP method and path are available in requestContext
|
|
const { method, path } = event.requestContext.http;
|
|
|
|
return { message: 'Success' };
|
|
};
|
|
```
|
|
|
|
يحتوي نوع `RoutePayload` على البنية التالية:
|
|
|
|
| الخاصية | النوع | الوصف |
|
|
| ---------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------- |
|
|
| `headers` | `Record<string, string \| undefined>` | رؤوس HTTP (فقط تلك المدرجة في `forwardedRequestHeaders`) |
|
|
| `queryStringParameters` | `Record<string, string \| undefined>` | معلمات سلسلة الاستعلام (تُضمّ القيم المتعددة باستخدام فواصل) |
|
|
| `pathParameters` | `Record<string, string \| undefined>` | معلمات المسار المستخرجة من نمط المسار (على سبيل المثال، `/users/:id` → `{ id: '123' }`) |
|
|
| `المحتوى` | `object \| null` | جسم الطلب المُحلَّل (JSON) |
|
|
| `isBase64Encoded` | `قيمة منطقية` | ما إذا كان جسم الطلب مُرمَّزًا بترميز base64 |
|
|
| `requestContext.http.method` | `string` | طريقة HTTP (GET, POST, PUT, PATCH, DELETE) |
|
|
| `requestContext.http.path` | `string` | المسار الخام للطلب |
|
|
|
|
### تمرير رؤوس HTTP
|
|
|
|
افتراضيًا، **لا** تُمرَّر رؤوس HTTP من الطلبات الواردة إلى وظيفتك المنطقية لأسباب أمنية. للوصول إلى رؤوس محددة، قم بإدراجها صراحةً في مصفوفة `forwardedRequestHeaders`:
|
|
|
|
```typescript
|
|
export default defineLogicFunction({
|
|
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
|
name: 'webhook-handler',
|
|
handler,
|
|
triggers: [
|
|
{
|
|
universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
|
|
type: 'route',
|
|
path: '/webhook',
|
|
httpMethod: 'POST',
|
|
isAuthRequired: false,
|
|
forwardedRequestHeaders: ['x-webhook-signature', 'content-type'],
|
|
},
|
|
],
|
|
});
|
|
```
|
|
|
|
في المعالج الخاص بك، يمكنك حينها الوصول إلى هذه الرؤوس:
|
|
|
|
```typescript
|
|
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>
|
|
تُحوَّل أسماء الرؤوس إلى أحرف صغيرة. يمكنك الوصول إليها باستخدام مفاتيح بأحرف صغيرة (على سبيل المثال، `event.headers['content-type']`).
|
|
</Note>
|
|
|
|
يمكنك إنشاء وظائف جديدة بطريقتين:
|
|
|
|
* **مُنشأ بالقالب**: شغّل `yarn twenty entity:add` واختر خيار إضافة وظيفة منطقية جديدة. يُولّد هذا ملفًا مبدئيًا مع معالج وتكوين.
|
|
* **يدوي**: أنشئ ملفًا جديدًا `*.logic-function.ts` واستخدم `defineLogicFunction()` مع اتباع النمط نفسه.
|
|
|
|
### تمييز دالة منطقية كأداة
|
|
|
|
يمكن إتاحة الدوال المنطقية بوصفها **أدوات** لوكلاء الذكاء الاصطناعي وسير العمل. عندما يتم تمييز دالة كأداة، تصبح قابلة للاكتشاف بواسطة ميزات الذكاء الاصطناعي الخاصة بـ Twenty ويمكن اختيارها كخطوة في أتمتة سير العمل.
|
|
|
|
لتمييز دالة منطقية كأداة، عيّن `isTool: true` وقدّم `toolInputSchema` يصف معاملات الإدخال المتوقعة باستخدام [مخطط JSON](https://json-schema.org/):
|
|
|
|
```typescript
|
|
// 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,
|
|
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'],
|
|
},
|
|
});},{
|
|
```
|
|
|
|
النقاط الرئيسية:
|
|
|
|
* **`isTool`** (`boolean`, الافتراضي: `false`): عند ضبطه على `true`، يتم تسجيل الدالة كأداة وتصبح متاحة لوكلاء الذكاء الاصطناعي ولأتمتة سير العمل.
|
|
* **`toolInputSchema`** (`object`, اختياري): كائن JSON Schema يصف المعلمات التي تقبلها دالتك. يستخدم وكلاء الذكاء الاصطناعي هذا المخطط لفهم المدخلات التي تتوقعها الأداة وللتحقق من صحة الاستدعاءات. إذا تم إغفاله، فالقيمة الافتراضية للمخطط هي `{ type: 'object', properties: {} }` (من دون معلمات).
|
|
* الدوال التي لديها `isTool: false` (أو غير معيَّنة) **غير** معروضة كأدوات. لا يزال بالإمكان تنفيذها مباشرةً أو استدعاؤها بواسطة دوال أخرى، لكنها لن تظهر في اكتشاف الأدوات.
|
|
* **تسمية الأداة**: عند كشفها كأداة، يتم تطبيع اسم الدالة تلقائيًا إلى `logic_function_<name>` (تحويله إلى أحرف صغيرة، واستبدال المحارف غير الأبجدية الرقمية بشرطات سفلية). على سبيل المثال، `enrich-company` تصبح `logic_function_enrich_company`.
|
|
* يمكنك دمج `isTool` مع المشغِّلات — إذ يمكن للدالة أن تكون أداة (قابلة للاستدعاء من قِبل وكلاء الذكاء الاصطناعي) وأن تُشغَّل بواسطة أحداث (cron، وأحداث قاعدة البيانات، والمسارات) في الوقت نفسه.
|
|
|
|
<Note>
|
|
**اكتب `description` جيدًا.** يعتمد وكلاء الذكاء الاصطناعي على حقل `description` الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها.
|
|
</Note>
|
|
|
|
### المكوّنات الأمامية
|
|
|
|
تتيح لك المكوّنات الأمامية إنشاء مكوّنات React مخصّصة تُعرَض داخل واجهة مستخدم Twenty. استخدم `defineFrontComponent()` لتعريف مكوّنات مع تحقّق مدمج:
|
|
|
|
```typescript
|
|
// src/front-components/my-widget.tsx
|
|
import { defineFrontComponent } from 'twenty-sdk';
|
|
|
|
const MyWidget = () => {
|
|
return (
|
|
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
|
|
<h1>My Custom Widget</h1>
|
|
<p>This is a custom front component for Twenty.</p>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default defineFrontComponent({
|
|
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
|
name: 'my-widget',
|
|
description: 'A custom widget component',
|
|
component: MyWidget,
|
|
});
|
|
```
|
|
|
|
النقاط الرئيسية:
|
|
|
|
* المكوّنات الأمامية هي مكوّنات React تُعرَض ضمن سياقات معزولة داخل Twenty.
|
|
* يشير الحقل `component` إلى مكوّن React الخاص بك.
|
|
* يتم بناء المكوّنات ومزامنتها تلقائيًا أثناء `yarn twenty dev`.
|
|
|
|
يمكنك إنشاء مكوّنات أمامية جديدة بطريقتين:
|
|
|
|
* **مُنشأ بالقالب**: شغّل `yarn twenty entity:add` واختر خيار إضافة مكوّن أمامي جديد.
|
|
* **يدوي**: أنشئ ملفًا جديدًا `.tsx` واستخدم `defineFrontComponent()` مع اتباع النمط نفسه.
|
|
|
|
#### أين يمكن استخدام مكوّنات الواجهة الأمامية
|
|
|
|
يمكن عرض مكوّنات الواجهة الأمامية في موقعين داخل Twenty:
|
|
|
|
* **اللوحة الجانبية** — المكوّنات غير عديمة الرأس تفتح في اللوحة الجانبية اليمنى. هذا هو السلوك الافتراضي عندما يتم تشغيل مكوّن واجهة أمامية من قائمة الأوامر.
|
|
* **الويدجت (لوحات المعلومات وصفحات السجلات)** — يمكن تضمين مكوّنات الواجهة الأمامية كويدجت داخل تخطيطات الصفحات. عند تكوين لوحة معلومات أو تخطيط صفحة سجل، يمكن للمستخدمين إضافة ويدجت لمكوّن واجهة أمامية.
|
|
|
|
#### عديم الرأس مقابل غير عديم الرأس
|
|
|
|
تأتي مكوّنات الواجهة الأمامية بوضعَي عرض يتحكّم بهما الخيار `isHeadless`:
|
|
|
|
**غير عديم الرأس (افتراضي)** — يعرض المكوّن واجهة مستخدم مرئية. عند تشغيله من قائمة الأوامر يفتح في اللوحة الجانبية. هذا هو السلوك الافتراضي عندما تكون `isHeadless` تساوي `false` أو يتم تجاهلها.
|
|
|
|
**عديم الرأس** — يتم تركيب المكوّن بشكل غير مرئي في الخلفية. لا يفتح اللوحة الجانبية. تم تصميم المكوّنات عديمة الرأس لإجراءات تنفّذ منطقًا ثم تُزيل تركيبها ذاتيًا — على سبيل المثال، تشغيل مهمة غير متزامنة، أو الانتقال إلى صفحة، أو إظهار نافذة تأكيد منبثقة. تتوافق بشكل طبيعي مع مكوّنات Command في SDK الموصوفة أدناه.
|
|
|
|
```typescript
|
|
export default defineFrontComponent({
|
|
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
|
name: 'my-action',
|
|
description: 'Runs an action without opening the side panel',
|
|
component: MyAction,
|
|
isHeadless: true,
|
|
command: {
|
|
universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901',
|
|
label: 'Run my action',
|
|
},
|
|
});
|
|
```
|
|
|
|
#### إضافة عناصر قائمة الأوامر
|
|
|
|
لجعل مكوّن واجهة أمامية يظهر كعنصر في قائمة الأوامر في Twenty، أضف الخاصية `command` إلى `defineFrontComponent()`. عند فتح المستخدمين لقائمة الأوامر (Cmd+K / Ctrl+K)، يظهر العنصر ويشغّل مكوّن الواجهة الأمامية عند النقر.
|
|
|
|
يقبل كائن `command` الحقول التالية:
|
|
|
|
| الحقل | النوع | الوصف |
|
|
| --------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------- |
|
|
| `universalIdentifier` | `string` (إلزامي) | معرّف فريد لعنصر قائمة الأوامر |
|
|
| `التسمية` | `string` (إلزامي) | التسمية المعروضة في قائمة الأوامر |
|
|
| `أيقونة` | `string` (اختياري) | اسم الأيقونة (مثال: `'IconSparkles'`) |
|
|
| `isPinned` | `boolean` (اختياري) | ما إذا كان الأمر مثبتًا أعلى القائمة |
|
|
| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (اختياري) | `GLOBAL` يعرض الأمر في كل مكان؛ `RECORD_SELECTION` يعرضه فقط في سياقات السجلّات |
|
|
| `availabilityObjectUniversalIdentifier` | `string` (اختياري) | تقييد الأمر بنوع كائن محدّد (مثال: Person) |
|
|
|
|
إليك مثالًا من تطبيق تسجيل المكالمات يضيف أمرًا محصورًا بسجلات Person:
|
|
|
|
```typescript
|
|
import { defineFrontComponent } from 'twenty-sdk';
|
|
|
|
export default defineFrontComponent({
|
|
universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012',
|
|
name: 'Summarize Person Call Recordings',
|
|
description: 'Generates a summary of call recordings for a person',
|
|
component: SummarizePersonRecordings,
|
|
command: {
|
|
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123',
|
|
label: 'Summarize call recordings',
|
|
icon: 'IconSparkles',
|
|
isPinned: false,
|
|
availabilityType: 'RECORD_SELECTION',
|
|
availabilityObjectUniversalIdentifier:
|
|
'20202020-e674-48e5-a542-72570eee7213',
|
|
},
|
|
});
|
|
```
|
|
|
|
عند مزامنة الأمر، يظهر في قائمة الأوامر. إذا كان مكوّن الواجهة الأمامية غير عديم الرأس، تُفتح اللوحة الجانبية مع عرض المكوّن بداخلها. إذا كان عديم الرأس، فسيتم تركيب المكوّن في الخلفية وتنفيذ منطقه.
|
|
|
|
#### مكوّنات Command في SDK
|
|
|
|
توفر حزمة `twenty-sdk` أربعة مكوّنات مساعدة من نوع Command مصممة للمكوّنات عديمة الرأس في الواجهة الأمامية. كل مكوّن ينفّذ إجراءً عند التركيب، ويتعامل مع الأخطاء بعرض إشعار Snackbar، ويزيل تركيب مكوّن الواجهة الأمامية تلقائيًا عند الانتهاء.
|
|
|
|
استوردها من `twenty-sdk/command`:
|
|
|
|
* **`Command`** — يشغّل رد نداء غير متزامن عبر الخاصية `execute`.
|
|
* **`CommandLink`** — ينتقل إلى مسار في التطبيق. الخصائص: `to`، `params`، `queryParams`، `options`.
|
|
* **`CommandModal`** — يفتح نافذة تأكيد منبثقة. إذا أكّد المستخدم، ينفّذ رد النداء `execute`. الخصائص: `title`، `subtitle`، `execute`، `confirmButtonText`، `confirmButtonAccent`.
|
|
* **`CommandOpenSidePanelPage`** — يفتح صفحة محدّدة في اللوحة الجانبية. الخصائص: `page`، `pageTitle`، `pageIcon`.
|
|
|
|
فيما يلي مثال كامل لمكوّن واجهة أمامية عديم الرأس يستخدم `Command` لتشغيل إجراء من قائمة الأوامر:
|
|
|
|
```typescript
|
|
// 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',
|
|
},
|
|
});
|
|
```
|
|
|
|
ومثال يستخدم `CommandModal` لطلب التأكيد قبل التنفيذ:
|
|
|
|
```typescript
|
|
// 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',
|
|
},
|
|
});
|
|
```
|
|
|
|
#### سياق التنفيذ
|
|
|
|
يتلقّى كل مكوّن واجهة أمامية سياق تنفيذ يوفّر معلومات حول مكان وكيفية تشغيله. يمكنك الوصول إلى قيم السياق باستخدام الخطافات من `twenty-sdk`:
|
|
|
|
| الخطّاف | نوع القيمة المرجعة | الوصف |
|
|
| ----------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------- |
|
|
| `useFrontComponentId()` | `string` | المعرّف الفريد لمثيل مكوّن الواجهة الأمامية الحالي |
|
|
| `useRecordId()` | `string \| null` | معرّف السجل الحالي، عندما يعمل المكوّن في سياق سجل (مثال: ويدجت صفحة سجل أو أمر محصور بسجل). يُرجع `null` خلاف ذلك. |
|
|
| `useUserId()` | `string \| null` | معرّف المستخدم الحالي |
|
|
|
|
```typescript
|
|
import { useRecordId, useUserId } from 'twenty-sdk';
|
|
|
|
const MyWidget = () => {
|
|
const recordId = useRecordId();
|
|
const userId = useUserId();
|
|
|
|
return (
|
|
<div>
|
|
<p>Record: {recordId ?? 'none'}</p>
|
|
<p>User: {userId ?? 'anonymous'}</p>
|
|
</div>
|
|
);
|
|
};
|
|
```
|
|
|
|
السياق تفاعلي — إذا تغيّر السجل المحيط، فستُرجع الخطافات القيم المحدّثة تلقائيًا.
|
|
|
|
#### دوال واجهة برمجة تطبيقات المضيف
|
|
|
|
تعمل مكوّنات الواجهة الأمامية في بيئة معزولة، لكنها تستطيع التفاعل مع واجهة مستخدم Twenty عبر مجموعة من الدوال التي يوفّرها المضيف. استوردها مباشرةً من `twenty-sdk`:
|
|
|
|
```typescript
|
|
import {
|
|
navigate,
|
|
closeSidePanel,
|
|
enqueueSnackbar,
|
|
unmountFrontComponent,
|
|
openSidePanelPage,
|
|
openCommandConfirmationModal,
|
|
} from 'twenty-sdk';
|
|
```
|
|
|
|
| دالة | التوقيع | الوصف |
|
|
| ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `التنقل` | `(to, params?, queryParams?, options?) => Promise<void>` | الانتقال إلى مسار تطبيق محدّد النوع داخل Twenty |
|
|
| `closeSidePanel` | `() => Promise<void>` | إغلاق اللوحة الجانبية |
|
|
| `enqueueSnackbar` | `(params) => Promise<void>` | عرض إشعار Snackbar. المعاملات: `message`، `variant` (`'error'`، `'success'`، `'info'`، `'warning'`)، `duration` اختياري، `detailedMessage`، `dedupeKey` |
|
|
| `unmountFrontComponent` | `() => Promise<void>` | إلغاء تركيب مكوّن الواجهة الأمامية الحالي (تستخدمه المكوّنات عديمة الرأس للتنظيف بعد التنفيذ) |
|
|
| `openSidePanelPage` | `(params) => Promise<void>` | فتح صفحة في اللوحة الجانبية. المعاملات: `page`، `pageTitle`، `pageIcon`، `shouldResetSearchState` |
|
|
| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | عرض نافذة تأكيد منبثقة والانتظار لرد المستخدم. المعاملات: `title`، `subtitle`، `confirmButtonText`، `confirmButtonAccent` (`'default'`، `'blue'`، `'danger'`) |
|
|
|
|
فيما يلي مثال يستخدم واجهة برمجة تطبيقات المضيف لعرض Snackbar وإغلاق اللوحة الجانبية بعد اكتمال الإجراء:
|
|
|
|
```typescript
|
|
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,
|
|
});
|
|
```
|
|
|
|
### المهارات
|
|
|
|
تُحدِّد المهارات تعليمات وإمكانات قابلة لإعادة الاستخدام يمكن لوكلاء الذكاء الاصطناعي استخدامها داخل مساحة العمل لديك. استخدم `defineSkill()` لتعريف مهارات مع تحقّق مدمج:
|
|
|
|
```typescript
|
|
// src/skills/example-skill.ts
|
|
import { defineSkill } from 'twenty-sdk';
|
|
|
|
export default defineSkill({
|
|
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
|
name: 'sales-outreach',
|
|
label: 'التواصل البيعي',
|
|
description: 'يرشد وكيل الذكاء الاصطناعي خلال عملية منظّمة للتواصل البيعي',
|
|
icon: 'IconBrain',
|
|
content: `أنت مساعد للتواصل البيعي. عند التواصل مع عميل محتمل:
|
|
1. ابحث عن الشركة وآخر الأخبار
|
|
2. حدِّد دور العميل المحتمل ونقاط الألم المرجّحة
|
|
3. صِغ رسالة مخصّصة تشير إلى تفاصيل محدّدة
|
|
4. حافظ على نبرة احترافية ولكن حوارية`,
|
|
});
|
|
```
|
|
|
|
النقاط الرئيسية:
|
|
|
|
* `name` هي سلسلة معرّف فريدة للمهارة (يُنصَح باستخدام kebab-case).
|
|
* `label` هو اسم العرض المقروء للبشر الظاهر في واجهة المستخدم.
|
|
* `content` يحتوي على تعليمات المهارة — وهو النص الذي يستخدمه وكيل الذكاء الاصطناعي.
|
|
* `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم.
|
|
* `description` (اختياري) يوفّر سياقًا إضافيًا حول غرض المهارة.
|
|
|
|
يمكنك إنشاء مهارات جديدة بطريقتين:
|
|
|
|
* **مُنشأ بالقالب**: شغِّل `yarn twenty entity:add` واختر خيار إضافة مهارة جديدة.
|
|
* **يدوي**: أنشئ ملفًا جديدًا واستخدم `defineSkill()` مع اتباع النمط نفسه.
|
|
|
|
### الوكلاء
|
|
|
|
تمكّنك ميزة الوكلاء من تعريف وكلاء ذكاء اصطناعي قادرين على العمل ضمن مساحة عملك، باستخدام موجهات النظام. استخدم `defineAgent()` لتعريف وكلاء مع تحقق مدمج:
|
|
|
|
```typescript
|
|
// src/agents/example-agent.ts
|
|
import { defineAgent } from 'twenty-sdk';
|
|
|
|
export default defineAgent({
|
|
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
|
name: 'sales-assistant',
|
|
label: 'Sales Assistant',
|
|
description: 'An AI agent that helps with sales tasks',
|
|
icon: 'IconRobot',
|
|
prompt: `You are a sales assistant. Help users with:
|
|
1. Researching prospects and companies
|
|
2. Drafting personalized outreach messages
|
|
3. Tracking follow-ups and next steps
|
|
4. Analyzing deal pipeline and suggesting actions`,
|
|
});
|
|
```
|
|
|
|
النقاط الرئيسية:
|
|
|
|
* `name` هي سلسلة معرّف فريدة للوكيل (يُنصَح باستخدام kebab-case).
|
|
* `label` هو اسم العرض المقروء للبشر الظاهر في واجهة المستخدم.
|
|
* `prompt` يحتوي موجه النظام — وهو نص التعليمات الذي يحدد سلوك الوكيل.
|
|
* `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم.
|
|
* `description` (اختياري) يوفّر سياقًا إضافيًا حول غرض الوكيل.
|
|
|
|
يمكنك إنشاء وكلاء جدد بطريقتين:
|
|
|
|
* **مُنشأ بالقالب**: شغِّل `yarn twenty entity:add` واختر خيار إضافة وكيل جديد.
|
|
* **يدوي**: أنشئ ملفًا جديدًا واستخدم `defineAgent()` مع اتباع النمط نفسه.
|
|
|
|
### عملاء مُولَّدون مضبوطو الأنواع
|
|
|
|
يتم توليد عميلين مضبوطي الأنواع تلقائيًا بواسطة `yarn twenty dev` وتخزينهما في `node_modules/twenty-sdk/clients` استنادًا إلى مخطط مساحة العمل لديك:
|
|
|
|
* **`CoreApiClient`** — يُجري استعلامات إلى نقطة النهاية `/graphql` للحصول على بيانات مساحة العمل
|
|
* **`MetadataApiClient`** — يستعلم عن نقطة النهاية `/metadata` لتكوين مساحة العمل وتحميل الملفات.
|
|
|
|
```typescript
|
|
import { CoreApiClient } from 'twenty-client-sdk/core';
|
|
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
|
|
|
const client = new CoreApiClient();
|
|
const { me } = await client.query({ me: { id: true, displayName: true } });
|
|
|
|
const metadataClient = new MetadataApiClient();
|
|
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });},{
|
|
```
|
|
|
|
`CoreApiClient` يُعاد توليده تلقائيًا بواسطة `yarn twenty dev` كلما تغيّرت كائناتك أو حقولك. `MetadataApiClient` يأتي مُجهزًا مسبقًا مع SDK.
|
|
|
|
#### بيانات الاعتماد وقت التشغيل في الوظائف المنطقية
|
|
|
|
عندما تعمل وظيفتك على Twenty، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئة قبل تنفيذ كودك:
|
|
|
|
* `TWENTY_API_URL`: عنوان URL الأساسي لواجهة Twenty البرمجية التي يستهدفها تطبيقك.
|
|
* `TWENTY_API_KEY`: مفتاح قصير العمر ذو نطاق يقتصر على الدور الافتراضي لوظيفة تطبيقك.
|
|
|
|
الملاحظات:
|
|
|
|
* لا تحتاج إلى تمرير عنوان URL أو مفتاح واجهة برمجة التطبيقات إلى العميل المُولَّد. يقوم بقراءة `TWENTY_API_URL` و`TWENTY_API_KEY` من process.env وقت التشغيل.
|
|
* تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `application-config.ts` عبر `defaultRoleUniversalIdentifier`. هذا هو الدور الافتراضي الذي تستخدمه الوظائف المنطقية في تطبيقك.
|
|
* يمكن للتطبيقات تعريف أدوار لاتباع مبدأ أقل الامتياز. امنح فقط الأذونات التي تحتاجها وظائفك، ثم وجّه `defaultRoleUniversalIdentifier` إلى المعرّف الشامل لذلك الدور.
|
|
|
|
#### رفع الملفات
|
|
|
|
يتضمن `MetadataApiClient` طريقة `uploadFile` لإرفاق الملفات بالحقول من نوع ملف ضمن كائنات مساحة العمل الخاصة بك. نظرًا لأن عملاء GraphQL القياسيون لا يدعمون تحميل الملفات متعددة الأجزاء افتراضيًا، يوفر العميل هذه الطريقة المخصصة التي تطبق [مواصفة طلب GraphQL متعدد الأجزاء](https://github.com/jaydenseric/graphql-multipart-request-spec) في الخلفية.
|
|
|
|
```typescript
|
|
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, // محتوى الملف كـ Buffer
|
|
'invoice.pdf', // اسم الملف
|
|
'application/pdf', // نوع MIME (القيمة الافتراضية 'application/octet-stream')
|
|
'58a0a314-d7ea-4865-9850-7fb84e72f30b', // المعرّف العالمي للحقل
|
|
);
|
|
|
|
console.log(uploadedFile);
|
|
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
|
|
```
|
|
|
|
توقيع الطريقة:
|
|
|
|
```typescript
|
|
uploadFile(
|
|
fileBuffer: Buffer,
|
|
filename: string,
|
|
contentType: string,
|
|
fieldMetadataUniversalIdentifier: string,
|
|
): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }>
|
|
```
|
|
|
|
| المعلمة | النوع | الوصف |
|
|
| ---------------------------------- | -------- | ---------------------------------------------------------------------------------- |
|
|
| `fileBuffer` | `Buffer` | المحتوى الخام للملف |
|
|
| `filename` | `string` | اسم الملف (يُستخدم للتخزين والعرض) |
|
|
| `contentType` | `string` | نوع MIME للملف (القيمة الافتراضية هي `application/octet-stream` إذا لم يتم تحديده) |
|
|
| `fieldMetadataUniversalIdentifier` | `string` | قيمة `universalIdentifier` لحقل نوع الملف في كائنك |
|
|
|
|
النقاط الرئيسية:
|
|
|
|
* تتوفر طريقة `uploadFile` على `MetadataApiClient` لأن عملية الـ mutation الخاصة بالرفع تُعالَج عبر نقطة النهاية `/metadata`.
|
|
* تستخدم `universalIdentifier` الخاص بالحقل (وليس المعرّف الخاص بمساحة العمل)، ليعمل كود الرفع لديك عبر أي مساحة عمل مُثبَّت فيها تطبيقك — بما يتماشى مع كيفية إشارة التطبيقات إلى الحقول في كل مكان آخر.
|
|
* العنوان `url` المُعاد هو عنوان URL موقّع يمكنك استخدامه للوصول إلى الملف المرفوع.
|
|
|
|
### مثال Hello World
|
|
|
|
استكشف مثالًا بسيطًا شاملًا من البداية إلى النهاية يوضح الكائنات والوظائف المنطقية والمكوّنات الأمامية ومشغّلات متعددة [هنا](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
|
|
|
## بناء تطبيقك
|
|
|
|
بمجرد أن تطوّر تطبيقك باستخدام `app:dev`، استخدم `app:build` لإنشاء حزمة قابلة للتوزيع منه.
|
|
|
|
```bash filename="Terminal"
|
|
# ابنِ التطبيق (الإخراج يذهب إلى .twenty/output/)
|
|
yarn twenty build
|
|
|
|
# ابنِ وأنشئ ملف tarball (.tgz) للتوزيع
|
|
yarn twenty build --tarball
|
|
```
|
|
|
|
عملية البناء:
|
|
|
|
1. **يقوم بتحليل ملف البيان والتحقق من صحته** — يقرأ جميع الكيانات `defineX()` من ملفات المصدر لديك ويُتحقّق من بنية ملف البيان.
|
|
2. **يُصرِّف دوال المنطق ومكوّنات الواجهة** — يُجمّع مصادر TypeScript إلى ملفات ESM `.mjs` باستخدام esbuild.
|
|
3. **يولّد قيم التحقّق** — يحسب تجزئات MD5 لكل ملف مُبنًى، وتُخزَّن في ملف البيان كـ `builtHandlerChecksum` / `builtComponentChecksum`.
|
|
4. **ينشئ عميل API مضبوط الأنواع** — يفحص مخطط GraphQL ويُنشئ عميلَي `CoreApiClient` و`MetadataApiClient` مضبوطي الأنواع.
|
|
5. **يشغّل فحص الأنواع لـ TypeScript** — يشغّل `tsc --noEmit` لاكتشاف أخطاء الأنواع قبل النشر.
|
|
6. **يعيد البناء باستخدام العميل المُولَّد** — يُجري مرحلة ترجمة ثانية بحيث تُدرَج أنواع العميل المُولَّد.
|
|
7. **ينشئ أرشيف tar اختياريًا** — إذا تم تمرير `--tarball`، يشغّل `npm pack` لإنشاء ملف `.tgz` جاهز للتوزيع.
|
|
|
|
مخرجات البناء في `.twenty/output/` تتضمّن:
|
|
|
|
```text
|
|
.twenty/output/
|
|
├── manifest.json # Manifest with checksums for all built files
|
|
├── package.json # Copied from app root
|
|
├── yarn.lock # Copied from app root
|
|
├── src/
|
|
│ ├── logic-functions/ # Compiled .mjs logic function files
|
|
│ └── front-components/ # Compiled .mjs front component files
|
|
├── public/ # Static assets (if any)
|
|
└── my-app-1.0.0.tgz # Only with --tarball flag
|
|
```
|
|
|
|
| الخيار | الوصف |
|
|
| ----------- | -------------------------------------------------- |
|
|
| `[appPath]` | المسار إلى دليل التطبيق (افتراضيًا: الدليل الحالي) |
|
|
| `--tarball` | قم أيضًا بحزم المخرجات في أرشيف `.tgz` |
|
|
|
|
## نشر تطبيقك
|
|
|
|
استخدم `app:publish` لتوزيع تطبيقك — إما إلى سجل npm أو مباشرةً إلى خادم Twenty.
|
|
|
|
### النشر إلى npm (الإعداد الافتراضي)
|
|
|
|
```bash filename="Terminal"
|
|
# انشر إلى npm (يتطلب تسجيل الدخول إلى npm)
|
|
yarn twenty publish
|
|
|
|
# انشر باستخدام وسم توزيع (مثل beta، next)
|
|
yarn twenty publish --tag beta
|
|
```
|
|
|
|
يقوم هذا ببناء التطبيق وتشغيل `npm publish` من دليل `.twenty/output/`. بعد ذلك يمكن تثبيت الحزمة المنشورة من سوق Twenty بواسطة أي مساحة عمل.
|
|
|
|
### النشر إلى خادم Twenty
|
|
|
|
```bash filename="Terminal"
|
|
# انشر مباشرةً إلى خادم Twenty
|
|
yarn twenty publish --server https://app.twenty.com
|
|
```
|
|
|
|
يقوم هذا ببناء التطبيق مع أرشيف tar، ويرفعه إلى الخادم عبر العملية `uploadAppTarball` في GraphQL، ويبدأ التثبيت في خطوة واحدة. يكون هذا مفيدًا لعمليات النشر الخاصة أو للاختبار مقابل خادم محدّد.
|
|
|
|
| الخيار | الوصف |
|
|
| ----------------- | -------------------------------------------------------- |
|
|
| `[appPath]` | المسار إلى دليل التطبيق (افتراضيًا: الدليل الحالي) |
|
|
| `--server <url>` | انشر إلى خادم Twenty بدلًا من npm |
|
|
| `--token <token>` | رمز المصادقة للخادم المستهدف |
|
|
| `--tag <tag>` | علامة توزيع npm (مثل `beta`، `next`) — للنشر عبر npm فقط |
|
|
|
|
## تسجيل التطبيق
|
|
|
|
قبل أن يمكن تثبيت تطبيق في مساحة عمل، يجب أن يكون **مسجّلًا**. التسجيل هو سجل بيانات وصفية يوضّح مصدر التطبيق وكيفية مصادقته. يُعالَج هذا تلقائيًا بواسطة CLI في معظم الحالات.
|
|
|
|
### أنواع المصادر
|
|
|
|
لكل تسجيل **نوع مصدر** يحدّد كيفية تحديد ملفات التطبيق أثناء التثبيت:
|
|
|
|
| نوع المصدر | كيفية تحديد الملفات | حالة الاستخدام النموذجية |
|
|
| ---------- | ------------------------------------------------------------------------- | --------------------------------------- |
|
|
| `LOCAL` | تتم مزامنة الملفات في الوقت الفعلي بواسطة مُراقِب CLI — يتم تخطّي التثبيت | التطوير باستخدام `app:dev` |
|
|
| `NPM` | تُجلب من سجل npm عبر الحقل `sourcePackage` | تطبيقات منشورة على npm |
|
|
| `TARBALL` | تُستخرَج من ملف `.tgz` مرفوع ومخزَّن على الخادم | تطبيقات خاصة منشورة باستخدام `--server` |
|
|
|
|
### كيفية إجراء التسجيل
|
|
|
|
* **`app:dev`** — ينشئ تلقائيًا تسجيلًا من نوع `LOCAL` في المرة الأولى التي تشغّل فيها وضع التطوير لمساحة عمل.
|
|
* **`app:publish --server`** — يرفع أرشيف tar وينشئ (أو يحدّث) تسجيلًا من نوع `TARBALL`، ثم يثبّت التطبيق.
|
|
* **سوق npm** — يتم إنشاء تسجيلات `NPM` عند مزامنة التطبيقات من سجل npm إلى كتالوج سوق Twenty.
|
|
* **واجهة برمجة تطبيقات GraphQL** — يمكنك أيضًا إنشاء التسجيلات برمجيًا عبر العملية `createApplicationRegistration`.
|
|
|
|
### التسجيل مقابل التثبيت
|
|
|
|
**التسجيل** و**التثبيت** مفهومان منفصلان:
|
|
|
|
* **التسجيل** (`ApplicationRegistration`) هو سجل بيانات وصفية عام يصف التطبيق: اسمه، نوع المصدر، بيانات اعتماد OAuth، وحالة إدراجه في السوق. وهو موجود بشكل مستقل عن أي مساحة عمل.
|
|
* **التثبيت** (`Application`) هو مثيل لكل مساحة عمل. عند قيام مستخدم بتثبيت تطبيق، تقوم Twenty بحلّ الحزمة من مصدر التسجيل، وتكتب الملفات المُبنَاة إلى التخزين، وتزامن البيان التعريفي (إنشاء الكائنات والحقول ودوال المنطق، إلخ) في مساحة العمل تلك.
|
|
|
|
يمكن تثبيت تسجيل واحد في العديد من مساحات العمل. تحصل كل مساحة عمل على نسختها الخاصة من ملفات التطبيق ونموذج البيانات.
|
|
|
|
### بيانات اعتماد OAuth
|
|
|
|
يتضمن كل تسجيل بيانات اعتماد OAuth (`oAuthClientId` و`oAuthClientSecret`) يتم إنشاؤها وقت الإنشاء. يستخدمها التطبيق لمصادقة طلبات واجهة برمجة التطبيقات بالنيابة عن المستخدمين. يُعرَض سر العميل مرةً **واحدة** عند الإنشاء — خزّنه بأمان. يمكنك تدويره لاحقًا عبر العملية `rotateApplicationRegistrationClientSecret`.
|
|
|
|
## إعداد يدوي (بدون المهيئ)
|
|
|
|
بينما نوصي باستخدام `create-twenty-app` للحصول على أفضل تجربة للبدء، يمكنك أيضًا إعداد مشروع يدويًا. لا تثبّت CLI عالميًا. بدل ذلك، أضف `twenty-sdk` كاعتماد محلي واربط سكربتًا واحدًا في ملف package.json لديك:
|
|
|
|
```bash filename="Terminal"
|
|
yarn add -D twenty-sdk
|
|
```
|
|
|
|
ثم أضف سكربتًا باسم `twenty`:
|
|
|
|
```json filename="package.json"
|
|
{
|
|
"scripts": {
|
|
"twenty": "twenty"
|
|
}
|
|
}
|
|
```
|
|
|
|
الآن يمكنك تشغيل جميع الأوامر عبر `yarn twenty <command>`، مثلًا: `yarn twenty dev`، `yarn twenty help`، إلخ.
|
|
|
|
## استكشاف الأخطاء وإصلاحها
|
|
|
|
* أخطاء المصادقة: شغّل `yarn twenty auth:login` وتأكد من أن مفتاح واجهة برمجة التطبيقات لديك يمتلك الأذونات المطلوبة.
|
|
* يتعذّر الاتصال بالخادم: تحقق من عنوان URL لواجهة البرمجة وأن خادم Twenty قابل للوصول.
|
|
* الأنواع أو العميل مفقود/قديم: أعد تشغيل `yarn twenty dev` — فهو يولِّد العميل مضبوط الأنواع تلقائيًا.
|
|
* وضع التطوير لا يزامن: تأكد من أن `yarn twenty dev` قيد التشغيل وأن التغييرات غير متجاهلة في بيئتك.
|
|
|
|
قناة المساعدة على Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
|