الانتقال إلى المحتوى الرئيسي
دوال المنطق هي دوال TypeScript على جانب الخادم تعمل على منصة Twenty. يمكن تشغيلها بواسطة طلبات HTTP أو جداول cron أو أحداث قاعدة البيانات — كما يمكن إتاحتها كأدوات لوكلاء الذكاء الاصطناعي.
كل ملف وظيفة يستخدم defineLogicFunction() لتصدير تكوين مع معالج ومشغّلات اختيارية.
src/logic-functions/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/logic-function';
import { CoreApiClient } from 'twenty-client-sdk/core';

const handler = async (params: RoutePayload) => {
  const client = new CoreApiClient();
  const body = (params.body ?? {}) as { name?: string };
  const name = body.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world';

  const result = await client.mutation({
    createPostCard: {
      __args: { data: { name } },
      id: true,
      name: true,
    },
  });
  return result;
};

export default defineLogicFunction({
  universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
  name: 'create-new-post-card',
  timeoutSeconds: 2,
  handler,
  httpRouteTriggerSettings: {
    path: '/post-card/create',
    httpMethod: 'POST',
    isAuthRequired: true,
  },
  /*databaseEventTriggerSettings: {
    eventName: 'people.created',
  },*/
  /*cronTriggerSettings: {
    pattern: '0 0 1 1 *',
  },*/
});
أنواع المشغّلات المتاحة:
  • httpRoute: يعرِض وظيفتك على مسار وطريقة HTTP تحت نقطة النهاية /s/:
مثال: path: '/post-card/create' يمكن استدعاؤه عبر https://your-twenty-server.com/s/post-card/create
لاستدعاء دالة منطقية يتم تشغيلها بواسطة مسار من مكون واجهة (بدون واجهة رسومية)، راجع قسم استدعاء دالة منطقية.
  • cron: يشغّل وظيفتك على جدول باستخدام تعبير CRON.
  • databaseEvent: يعمل على أحداث دورة حياة كائنات مساحة العمل. عندما تكون عملية الحدث هي updated، يمكن تحديد الحقول المحددة المراد الاستماع إليها في مصفوفة updatedFields. إذا تُركت غير معرّفة أو فارغة، فسيؤدي أي تحديث إلى تشغيل الدالة.
مثال: person.updated، *.created، company.*
يمكنك أيضًا تنفيذ دالة يدويًا باستخدام CLI:
yarn twenty dev:function:exec -n create-new-post-card -p '{"key": "value"}'
yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
يمكنك متابعة السجلات باستخدام:
yarn twenty dev:function:logs

حمولة مشغل المسار

عندما يستدعي مُشغِّل المسار وظيفتك المنطقية، فإنها تتلقّى كائن RoutePayload الذي يتبع صيغة AWS HTTP API v2. استورد نوع RoutePayload من twenty-sdk/logic-function:
import type { RoutePayload } from 'twenty-sdk/logic-function';

const handler = async (event: RoutePayload) => {
  const { headers, queryStringParameters, pathParameters, body } = event;
  const { method, path } = event.requestContext.http;

  return { message: 'Success' };
};
يحتوي نوع RoutePayload على البنية التالية:
الخاصيةالنوعالوصفمثال
headersRecord\<string, string | undefined>رؤوس HTTP (فقط تلك المدرجة في forwardedRequestHeaders)انظر القسم أدناه
queryStringParametersRecord\<string, string | undefined>معلمات سلسلة الاستعلام (تُضمّ القيم المتعددة باستخدام فواصل)/users?ids=1&ids=2&ids=3&name=Alice -> { ids: '1,2,3', name: 'Alice' }
pathParametersRecord\<string, string | undefined>معلمات المسار المستخرجة من نمط المسار/users/:id, /users/123 -> { id: '123' }
bodyobject | nullجسم الطلب المُحلَّل (JSON){ id: 1 } -> { id: 1 }
rawBodystring | undefinedنص الطلب الأصلي بترميز UTF-8، قبل تحليل JSON. مفيد للتحقق من تواقيع خطافات الويب على نمط HMAC (مثل X-Hub-Signature-256 الخاص بـ GitHub وStripe). undefined عندما لم يحتفظ وقت التشغيل بها.
isBase64Encodedbooleanما إذا كان جسم الطلب مُرمَّزًا بترميز base64
requestContext.http.methodstringطريقة HTTP (GET, POST, PUT, PATCH, DELETE)
requestContext.http.pathstringالمسار الخام للطلب

forwardedRequestHeaders

افتراضيًا، لا تُمرَّر رؤوس HTTP من الطلبات الواردة إلى دالتك المنطقية لأسباب أمنية. للوصول إلى رؤوس محددة، أدرِجها في مصفوفة forwardedRequestHeaders:
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'],
  },
});
في معالجك، يمكنك الوصول إلى الرؤوس المُمرَّرة بهذه الطريقة:
const handler = async (event: RoutePayload) => {
  const signature = event.headers['x-webhook-signature'];
  const contentType = event.headers['content-type'];

  // Validate webhook signature...
  return { received: true };
};
تُحوَّل أسماء الرؤوس إلى أحرف صغيرة. يمكنك الوصول إليها باستخدام مفاتيح بأحرف صغيرة (على سبيل المثال، event.headers['content-type']).

استجابة HTTP مخصصة

بشكل افتراضي، فإن إرجاع قيمة بسيطة من المعالج الخاص بك يعيدها كاستجابة 200 (بصيغة JSON للكائنات وtext/plain للسلاسل النصية). للتحكم في رمز الحالة ورؤوس الاستجابة، أعد كائن Response من twenty-sdk/logic-function:
import { Response } from 'twenty-sdk/logic-function';

const handler = async (event: RoutePayload) => {
  return new Response('<h1>Hello</h1>', {
    status: 201,
    headers: { 'content-type': 'text/html' },
  });
};
لأسباب أمنية، يتم تقييد ترويسات الاستجابة بقائمة مسموح بها. يتم إسقاط أي ترويسة ليست في القائمة (مثل Set-Cookie، وترويسات CORS مثل Access-Control-Allow-Origin، أو ترويسات X-* المخصصة) بصمت قبل إرسال الاستجابة. ترويسات الاستجابة المسموح بها هي:
  • content-type
  • content-language
  • content-disposition
  • cache-control
  • retry-after
يجب أن يكون رمز الحالة رمز حالة HTTP صالحًا (بين 100 و599). تتم مطابقة أسماء ترويسات الاستجابة دون حساسية لحالة الأحرف.

حمولة مُحفِّز حدث قاعدة البيانات

عندما يستدعي مُحفِّز حدث قاعدة البيانات دالة المنطق الخاصة بك، فإنه يستقبل كائن DatabaseEventPayload واحدًا لكل سجل تم تغييره. تجمع الحمولة بين البيانات الوصفية حول مساحة العمل والكائن المصدر وبين الحدث على مستوى السجل.
import type {
  DatabaseEventPayload,
  ObjectRecordCreateEvent,
  ObjectRecordDestroyEvent,
  ObjectRecordUpdateEvent,
} from 'twenty-sdk/logic-function';

type Person = {
  id: string;
  emails?: { primaryEmail?: string };
};
تتضمن الحمولة ما يلي:
الخاصيةالوصف
nameاسم الحدث، مثل person.updated.
workspaceIdمساحة العمل التي وقع فيها الحدث.
objectMetadataبيانات وصفية للكائن الذي تم تغييره.
recordIdمعرّف السجل الذي تم تغييره.
userId, userWorkspaceId, workspaceMemberIdحقول الفاعل عندما يكون الحدث ناتجًا عن مستخدم في مساحة العمل.
propertiesبيانات السجل الخاصة بالحدث، مع before وafter وdiff وupdatedFields اعتمادًا على العملية.
حدثبيانات السجل
person.createdevent.properties.after
person.updatedevent.properties.before, event.properties.after, event.properties.diff, event.properties.updatedFields
person.destroyedevent.properties.before
في عمليات الحذف اللين (soft deletes)، يتبع .deleted بنية نمط التحديث لأن حقل deletedAt في السجل يتغيّر. في عمليات الحذف الدائم، استخدم .destroyed.
databaseEventTriggerSettings.updatedFields يرشّح أيّ أحداث التحديث التي تُشغِّل الدالة. event.properties.updatedFields يوضّح لك أي الحقول تغيّرت فعليًا في الحدث الحالي.
مثال على حدث الإنشاء:
type PersonCreatedEvent = DatabaseEventPayload<
  ObjectRecordCreateEvent<Person>
>;

const handler = async (event: PersonCreatedEvent) => {
  const person = event.properties.after;

  return {
    personId: event.recordId,
    email: person.emails?.primaryEmail,
  };
};
مثال على حدث التحديث:
type PersonUpdatedEvent = DatabaseEventPayload<
  ObjectRecordUpdateEvent<Person>
>;

const handler = async (event: PersonUpdatedEvent) => {
  const { before, after, diff, updatedFields } = event.properties;

  return {
    personId: event.recordId,
    updatedFields,
    previousEmail: before.emails?.primaryEmail,
    currentEmail: after.emails?.primaryEmail,
    emailDiff: diff.emails,
  };
};
تشغيل المشغّل فقط عند تحديثات البريد الإلكتروني:
export default defineLogicFunction({
  ...,
  databaseEventTriggerSettings: {
    eventName: 'person.updated',
    updatedFields: ['emails'],
  },
});
مثال على حدث الحذف:
type PersonDestroyedEvent = DatabaseEventPayload<
  ObjectRecordDestroyEvent<Person>
>;

const handler = async (event: PersonDestroyedEvent) => {
  const personBeforeDestroy = event.properties.before;

  return {
    personId: event.recordId,
    email: personBeforeDestroy.emails?.primaryEmail,
  };
};

إتاحة دالة كأداة ذكاء اصطناعي أو كإجراء ضمن سير العمل

يمكن إتاحة دوال المنطق على واجهتين، ولكلٍ منهما مشغِّل خاص به:
  • toolTriggerSettings — يجعل الدالة قابلة للاكتشاف عبر ميزات الذكاء الاصطناعي الخاصة بـ Twenty (الدردشة، MCP، استدعاء الدوال). يستخدم JSON Schema القياسي، وهو التنسيق الذي تفهمه LLMs أصلاً.
  • workflowActionTriggerSettings — يجعل الدالة تظهر كخطوة في منشئ سير العمل المرئي. يستخدم InputSchema الغني الخاص بـ Twenty لكي يتمكن المُنشئ من عرض محرّرات الحقول المناسبة، وأدوات انتقاء المتغيّرات، والتسميات.
يمكن للدالة اختيار أحدهما، أو الآخر، أو كليهما. توجد جنبًا إلى جنب مع cronTriggerSettings وdatabaseEventTriggerSettings وhttpRouteTriggerSettings — النمط نفسه، والشكل نفسه.
src/logic-functions/enrich-company.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';

const handler = async (params: { companyName: string; domain?: string }) => {
  const client = new CoreApiClient();

  const result = await client.mutation({
    createTask: {
      __args: {
        data: {
          title: `Enrich data for ${params.companyName}`,
          body: `Domain: ${params.domain ?? 'unknown'}`,
        },
      },
      id: true,
    },
  });

  return { taskId: result.createTask.id };
};

export default defineLogicFunction({
  universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
  name: 'enrich-company',
  description: 'Enrich a company record with external data',
  timeoutSeconds: 10,
  handler,
  toolTriggerSettings: {},
});
النقاط الرئيسية:
  • يمكن للدالة مزج الواجهات — صرِّح بكلٍ من toolTriggerSettings وworkflowActionTriggerSettings لإتاحتها في الدردشة وفي منشئ سير العمل.
  • toolTriggerSettings.inputSchema وworkflowActionTriggerSettings.inputSchema كلاهما اختياري. عند الإغفال، يستنتج مُنشئ البيان هذه المخططات من الشيفرة المصدرية للمعالج (JSON Schema لأداة الذكاء الاصطناعي، وInputSchema الخاصة بـ Twenty لإجراء سير العمل). قدّم واحدًا صراحةً عندما ترغب في أنواع أكثر ثراءً — على سبيل المثال، مع حقول واعية بـ FieldMetadataType مثل CURRENCY أو RELATION لمنشئ سير العمل، أو مع حقول description التي يمكن لوكيل الذكاء الاصطناعي قراءتها:
export default defineLogicFunction({
  ...,
  toolTriggerSettings: {
    inputSchema: {
      type: 'object',
      properties: {
        companyName: {
          type: 'string',
          description: 'The name of the company to enrich',
        },
        domain: {
          type: 'string',
          description: 'The company website domain (optional)',
        },
      },
      required: ['companyName'],
    },
  },
});
اكتب description جيدًا. يعتمد وكلاء الذكاء الاصطناعي على حقل description الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها.
خطافات التثبيت — معالجات ما قبل التثبيت وما بعد التثبيت — تشترك في وقت التشغيل نفسه، ولكن يُصرَّح عنها بدوال تعريف خاصة بها ولا تأخذ إعدادات المشغّلات. راجع خطافات التثبيت (Install Hooks) لمعرفة definePreInstallLogicFunction و definePostInstallLogicFunction.

عملاء واجهة برمجة تطبيقات مضبوطة الأنواع (twenty-client-sdk)

توفر حزمة twenty-client-sdk عميلين لـ GraphQL ذوي أنواع ثابتة للتفاعل مع واجهة Twenty البرمجية من وظائفك المنطقية ومكوّنات الواجهة الأمامية.
العميلاستيرادنقطة النهايةمُولَّد؟
CoreApiClienttwenty-client-sdk/core/graphql — بيانات مساحة العمل (السجلات، الكائنات)نعم، في وقت التطوير/البناء
MetadataApiClienttwenty-client-sdk/metadata/metadata — تكوين مساحة العمل، رفع الملفاتلا، يأتي مُجهزًا مسبقًا
CoreApiClient هو العميل الرئيسي للاستعلام وتعديل بيانات مساحة العمل. يُولَّد من مخطط مساحة العمل لديك أثناء yarn twenty dev أو yarn twenty dev:build، لذا فهو مضبوط الأنواع بالكامل ليتوافق مع كائناتك وحقولك.
import { CoreApiClient } from 'twenty-client-sdk/core';

const client = new CoreApiClient();

// Query records
const { companies } = await client.query({
  companies: {
    edges: {
      node: {
        id: true,
        name: true,
        domainName: {
          primaryLinkLabel: true,
          primaryLinkUrl: true,
        },
      },
    },
  },
});

// Create a record
const { createCompany } = await client.mutation({
  createCompany: {
    __args: {
      data: {
        name: 'Acme Corp',
      },
    },
    id: true,
    name: true,
  },
});
يستخدم العميل صياغة مجموعة اختيار: مرِّر true لتضمين حقل، واستخدم __args للوسيطات، وعشّش الكائنات للعلاقات. ستحصل على إكمال تلقائي كامل وفحص للأنواع يعتمد على مخطط مساحة العمل لديك.
يتم توليد CoreApiClient في وقت التطوير/البناء. إذا استخدمته دون تشغيل yarn twenty dev أو yarn twenty dev:build أولًا، فسيؤدي ذلك إلى خطأ. تحدث عملية التوليد تلقائيًا — إذ يستطلع CLI مخطط GraphQL لمساحة عملك وينشئ عميلًا مضبوط الأنواع باستخدام @genql/cli.

استخدام CoreSchema للتعليقات التوضيحية للأنواع

CoreSchema يوفّر أنواع TypeScript المطابقة لكائنات مساحة العمل لديك — مفيد لتعيين أنواع حالة المكوّن أو معاملات الدوال:
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);
يأتي MetadataApiClient مُجهّزًا مسبقًا مع SDK (لا حاجة للتوليد). يستعلم عن نقطة النهاية /metadata للحصول على تكوين مساحة العمل والتطبيقات ورفع الملفات.
import { MetadataApiClient } from 'twenty-client-sdk/metadata';

const metadataClient = new MetadataApiClient();

// List first 10 objects in the workspace
const { objects } = await metadataClient.query({
  objects: {
    edges: {
      node: {
        id: true,
        nameSingular: true,
        namePlural: true,
        labelSingular: true,
        isCustom: true,
      },
    },
    __args: {
      filter: {},
      paging: { first: 10 },
    },
  },
});

رفع الملفات

يتضمن MetadataApiClient طريقة uploadFile لإرفاق الملفات بالحقول من نوع الملف:
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://...' }
المعلمةالنوعالوصف
fileBufferBufferالمحتوى الخام للملف
filenamestringاسم الملف (يُستخدم للتخزين والعرض)
contentTypestringنوع MIME (القيمة الافتراضية application/octet-stream إذا لم يُحدَّد)
fieldMetadataUniversalIdentifierstringقيمة universalIdentifier لحقل نوع الملف في كائنك
النقاط الرئيسية:
  • يستخدم universalIdentifier الخاص بالحقل (وليس معرّفه الخاص بمساحة العمل)، بحيث يعمل كود الرفع لديك عبر أي مساحة عمل مُثبَّت فيها تطبيقك.
  • العنوان url المُعاد هو عنوان URL موقّع يمكنك استخدامه للوصول إلى الملف المرفوع.
عند تشغيل كودك على Twenty (وظائف منطقية أو مكوّنات أمامية)، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئية:
  • TWENTY_API_URL — عنوان URL الأساسي لواجهة Twenty البرمجية
  • TWENTY_APP_ACCESS_TOKEN — مفتاح قصير العمر ذو نطاق يقتصر على الدور الافتراضي لوظيفة تطبيقك
لست بحاجة إلى تمرير هذه القيم إلى العملاء — فهي تُقرأ تلقائيًا من process.env. تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المُعلن باستخدام defineApplicationRole() (أو المشار إليه عبر defaultRoleUniversalIdentifier في application-config.ts).