الانتقال إلى المحتوى الرئيسي
خطافات التثبيت هي دوال منطقية خاصة تعمل أثناء دورة حياة التثبيت أو الترقية. تشارك نفس وقت تشغيل المعالج مثل دوال المنطق العادية وتتلقى InstallPayload ({ previousVersion?: string; newVersion: string } — تكون previousVersion بقيمة undefined في التثبيت الجديد)، ولكن يتم التصريح عنها بدوال تعريف خاصة بها وتعمل خارج نموذج المشغّل المعتاد (HTTP، وcron، وأحداث قاعدة البيانات). يمكن لكل تطبيق تعريف دالة واحدة على الأكثر لما قبل التثبيت ودالة واحدة على الأكثر لما بعد التثبيت. سيُنتِج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة من أيٍّ منهما.
┌─────────────────────────────────────────────────────────────┐
│ install flow                                                │
│                                                             │
│   upload package → [pre-install] → metadata migration →     │
│   generate SDK → [post-install]                             │
│                                                             │
│                  old schema visible    new schema visible   │
└─────────────────────────────────────────────────────────────┘

لمحة سريعة

definePreInstallLogicFunctiondefinePostInstallLogicFunction
عمليات التشغيلقبل ترحيل البيانات الوصفية — لا يزال المخطط والبيانات السابقة سليمينبعد الترحيل وإنشاء الـ SDK — أصبح المخطط الجديد في مكانه
التنفيذدائمًا متزامن؛ يحجب عملية التثبيتغير متزامن بشكل افتراضي (يُوضَع في قائمة الانتظار، 3 محاولات إعادة)؛ تفعيل التزامن اختياري عبر shouldRunSynchronously: true
عند الفشليتم إحباط التثبيت قبل أي تغيير في المخططغير متزامن: تُعاد المحاولة حتى 3 مرات. متزامن: يتلقى المستدعي POST_INSTALL_ERROR (لن يتم التراجع عن تغييرات المخطط).
الاستخدام النموذجيانسخ البيانات احتياطيًا أو أصلح بيانات قد يفقدها الترحيل؛ ارفض ترقية خطِرة بإلقاء استثناء.بذر بيانات افتراضية، تهيئة مساحة العمل، تسجيل موارد خارجية
قاعدة عامة: اجعل الافتراضي هو post-install. الجأ إلى ما قبل التثبيت فقط عندما يكون الترحيل نفسه هدّامًا وتحتاج إلى التقاط الحالة السابقة قبل أن تزول.
ترغب في…استخدام
بذر البيانات، تهيئة مساحة العمل، تسجيل موارد خارجيةpost-install
عمل طويل الأمد لا ينبغي أن يحجب استجابة التثبيتpost-install (الوضع غير المتزامن الافتراضي، مع محاولات إعادة من العامل)
إعداد سريع يعتمد عليه المستدعي مباشرةً بعد عودة التثبيتpost-install مع shouldRunSynchronously: true
قراءة البيانات أو نسخها احتياطيًا والتي قد يفقدها الترحيل القادمpre-install
رفض ترقية قد تُفسد البيانات الحاليةpre-install (ارمِ من المعالج)
تنفيذ مواءمة في كل ترقيةأي من الخطافين مع shouldRunOnVersionUpgrade: true

السلوك المشترك بين كلا الخطافين

  • إعداد التهيئة هو إعداد defineLogicFunction نفسه مطروحًا منه إعدادات المشغّل، مضافًا إليه shouldRunOnVersionUpgrade.
  • موعد تشغيله: في عمليات التثبيت الجديدة فقط، افتراضيًا. عيِّن shouldRunOnVersionUpgrade: true لتشغيله أيضًا عند الترقيات. استخدم previousVersion / newVersion للتفرع حسب مسار الترقية.
  • أهمية اللاّتغيّر (Idempotency): قد يُعاد تشغيل post-install غير المتزامن، وأيٌّ من الخطافين يُعاد تشغيله عند الترقيات عندما يكون shouldRunOnVersionUpgrade مفعّلًا.
  • يتم حقن بيئة دوال المنطق المعتادة (APPLICATION_ID، وAPP_ACCESS_TOKEN، وAPI_URL)، لذا يمكنك استدعاء Twenty API باستخدام رمز التطبيق الخاص بك.
  • يُربَط الخطّاف تلقائيًا بملف بيان التطبيق وقت الإنشاء (preInstallLogicFunction / postInstallLogicFunction) — لا حاجة للإشارة إليه في defineApplication().
  • القيمة الافتراضية لـ timeoutSeconds هي 300 للسماح بمهام إعداد أطول مثل بذر البيانات.
  • غير منفَّذ في نمط التطوير: يتخطى yarn twenty dev تدفق التثبيت ويزامن الملفات مباشرةً، لذا لا تعمل الخطافات هناك مطلقًا. بدلًا من ذلك، شغّلها يدويًا:
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
يعمل بعد انتهاء تثبيت تطبيقك: تمت مزامنة البيانات الوصفية، وتم إنشاء عميل SDK، وأصبح من الممكن الاستعلام عن المخطط الجديد. مثال — بذر سجل افتراضي في عمليات التثبيت الجديدة:
src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';

const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
  if (previousVersion) return; // fresh installs only

  const client = new CoreApiClient();
  await client.mutation({
    createPostCard: {
      __args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
      id: true,
    },
  });
};

export default definePostInstallLogicFunction({
  universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
  name: 'post-install',
  description: 'Seeds a welcome post card after install.',
  timeoutSeconds: 300,
  shouldRunOnVersionUpgrade: false,
  shouldRunSynchronously: false,
  handler,
});
تتحكم الشارة shouldRunSynchronously في نموذج التنفيذ:
  • false (الإعداد الافتراضي) — يُوضَع في قائمة انتظار الرسائل (retryLimit: 3) ويُشغِّله عامل. تعود استجابة التثبيت بمجرد وضع المهمة في قائمة الانتظار. يُستخدم للأعمال طويلة الأمد — بذر مجموعات بيانات كبيرة، وواجهات برمجة تطبيقات بطيئة لأطراف ثالثة.
  • true — يُنفَّذ مضمَّنًا أثناء تدفق التثبيت. يحجب طلب التثبيت حتى ينتهي المعالج؛ يظهر الخطأ الذي يتم رميه كـ POST_INSTALL_ERROR للمستدعي (بدون محاولات إعادة). يُستخدم للأعمال السريعة التي يجب إتمامها قبل الاستجابة. تم تطبيق الترحيل بالفعل في هذه المرحلة، لذا لا يؤدي الفشل إلى التراجع عن تغييرات المخطط — بل يُظهِر الخطأ فقط.
يعمل قبل ترحيل البيانات الوصفية، مقابل المخطط السابق — المكان المناسب لنسخ البيانات احتياطيًا التي قد يفقدها الترحيل، أو لرفض ترقية خطِرة. قبل التنفيذ، يُشغِّل الخادم مزامنة ذات طابع إضافي فقط “pared-down sync” تُسجِّل دالة ما قبل التثبيت للإصدار الجديد فقط؛ أما كل ما عدا ذلك — كائنات الإصدار السابق وحقوله وبياناته — فيبقى دون مساس عندما يعمل المعالج.ما قبل التثبيت دائمًا متزامن ويحجب عملية التثبيت. إذا رمى المعالج استثناءً، تُلغى عملية التثبيت قبل أي تغيير في المخطط — وتبقى مساحة العمل على الإصدار السابق بحالة متّسقة. هذا مقصود: ما قبل التثبيت هو فرصتك الأخيرة لرفض ترقية تنطوي على مخاطر.مثال — نسخ قيم حقل قديم قبل أن يُسقِطه الترحيل:
src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';

const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
  // Only the 1.x → 2.x upgrade drops the legacy `notes` field.
  if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) {
    return;
  }

  const client = new CoreApiClient();
  const { postCards } = await client.query({
    postCards: {
      __args: { filter: { notes: { isNot: null } } },
      edges: { node: { id: true, notes: true } },
    },
  });

  // Copy legacy `notes` into `description` before the migration drops the
  // column. If this fails, the upgrade aborts and the workspace stays on v1.
  for (const { node } of postCards.edges) {
    await client.mutation({
      updatePostCard: {
        __args: { id: node.id, data: { description: node.notes } },
        id: true,
      },
    });
  }
};

export default definePreInstallLogicFunction({
  universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
  name: 'pre-install',
  description: 'Backs up legacy notes into description before the v2 migration.',
  timeoutSeconds: 300,
  shouldRunOnVersionUpgrade: true,
  handler,
});