diff --git a/packages/twenty-docs/docs.json b/packages/twenty-docs/docs.json
index 749d9216d2..947725652c 100644
--- a/packages/twenty-docs/docs.json
+++ b/packages/twenty-docs/docs.json
@@ -903,6 +903,7 @@
"l/fr/developers/extend/apps/logic/overview",
"l/fr/developers/extend/apps/logic/logic-functions",
"l/fr/developers/extend/apps/logic/key-value-store",
+ "l/fr/developers/extend/apps/logic/background-jobs",
"l/fr/developers/extend/apps/logic/skills-and-agents",
"l/fr/developers/extend/apps/logic/connections"
]
@@ -1369,6 +1370,7 @@
"l/ar/developers/extend/apps/logic/overview",
"l/ar/developers/extend/apps/logic/logic-functions",
"l/ar/developers/extend/apps/logic/key-value-store",
+ "l/ar/developers/extend/apps/logic/background-jobs",
"l/ar/developers/extend/apps/logic/skills-and-agents",
"l/ar/developers/extend/apps/logic/connections"
]
@@ -1835,6 +1837,7 @@
"l/cs/developers/extend/apps/logic/overview",
"l/cs/developers/extend/apps/logic/logic-functions",
"l/cs/developers/extend/apps/logic/key-value-store",
+ "l/cs/developers/extend/apps/logic/background-jobs",
"l/cs/developers/extend/apps/logic/skills-and-agents",
"l/cs/developers/extend/apps/logic/connections"
]
@@ -2301,6 +2304,7 @@
"l/de/developers/extend/apps/logic/overview",
"l/de/developers/extend/apps/logic/logic-functions",
"l/de/developers/extend/apps/logic/key-value-store",
+ "l/de/developers/extend/apps/logic/background-jobs",
"l/de/developers/extend/apps/logic/skills-and-agents",
"l/de/developers/extend/apps/logic/connections"
]
@@ -2767,6 +2771,7 @@
"l/es/developers/extend/apps/logic/overview",
"l/es/developers/extend/apps/logic/logic-functions",
"l/es/developers/extend/apps/logic/key-value-store",
+ "l/es/developers/extend/apps/logic/background-jobs",
"l/es/developers/extend/apps/logic/skills-and-agents",
"l/es/developers/extend/apps/logic/connections"
]
@@ -3233,6 +3238,7 @@
"l/it/developers/extend/apps/logic/overview",
"l/it/developers/extend/apps/logic/logic-functions",
"l/it/developers/extend/apps/logic/key-value-store",
+ "l/it/developers/extend/apps/logic/background-jobs",
"l/it/developers/extend/apps/logic/skills-and-agents",
"l/it/developers/extend/apps/logic/connections"
]
@@ -3699,6 +3705,7 @@
"l/ja/developers/extend/apps/logic/overview",
"l/ja/developers/extend/apps/logic/logic-functions",
"l/ja/developers/extend/apps/logic/key-value-store",
+ "l/ja/developers/extend/apps/logic/background-jobs",
"l/ja/developers/extend/apps/logic/skills-and-agents",
"l/ja/developers/extend/apps/logic/connections"
]
@@ -4165,6 +4172,7 @@
"l/ko/developers/extend/apps/logic/overview",
"l/ko/developers/extend/apps/logic/logic-functions",
"l/ko/developers/extend/apps/logic/key-value-store",
+ "l/ko/developers/extend/apps/logic/background-jobs",
"l/ko/developers/extend/apps/logic/skills-and-agents",
"l/ko/developers/extend/apps/logic/connections"
]
@@ -5097,6 +5105,7 @@
"l/ro/developers/extend/apps/logic/overview",
"l/ro/developers/extend/apps/logic/logic-functions",
"l/ro/developers/extend/apps/logic/key-value-store",
+ "l/ro/developers/extend/apps/logic/background-jobs",
"l/ro/developers/extend/apps/logic/skills-and-agents",
"l/ro/developers/extend/apps/logic/connections"
]
@@ -5996,6 +6005,7 @@
"l/tr/developers/extend/apps/logic/overview",
"l/tr/developers/extend/apps/logic/logic-functions",
"l/tr/developers/extend/apps/logic/key-value-store",
+ "l/tr/developers/extend/apps/logic/background-jobs",
"l/tr/developers/extend/apps/logic/skills-and-agents",
"l/tr/developers/extend/apps/logic/connections"
]
diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/logic/background-jobs.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/logic/background-jobs.mdx
new file mode 100644
index 0000000000..7491f5088d
--- /dev/null
+++ b/packages/twenty-docs/l/ar/developers/extend/apps/logic/background-jobs.mdx
@@ -0,0 +1,165 @@
+---
+title: المهام الخلفية
+description: قم بإحالة العمل الطويل الأمد أو المقيّد بالمعدل إلى عمّال Twenty عن طريق وضع تشغيل دالة منطقية أخرى في قائمة الانتظار بدلًا من تنفيذ كل شيء ضمن نفس السياق.
+icon: layer-group
+---
+
+تشغيل الدالة المنطقية محدّد بحده الأقصى `timeoutSeconds` (900 ثانية كحد أقصى). أي شيء لا يمكنه أن ينتهي ضمن تلك المهلة الزمنية — مثل إعادة المزامنة الكاملة، أو التوزيع على مستوى كل سجل، أو واجهة برمجة تطبيقات لطرف ثالث تفرض حدودًا على المعدل — يجب تقسيمه إلى عمليات تشغيل أصغر.
+
+تقوم `enqueueJob` بذلك بالضبط: فهي تطلب من عمّال Twenty تشغيل واحدة من دوال المنطق في تطبيقك لاحقًا، في عملية مستقلة، وبمهلة زمنية خاصة بها. يعود المستدعي فورًا.
+
+```text
+ ┌─────────────────┐ enqueueJob(...) ┌──────────────┐ ┌────────────────────┐
+ │ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
+ │ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
+ └─────────────────┘ └──────────────┘ └────────────────────┘
+```
+
+## إضافة تشغيل إلى قائمة الانتظار (Enqueue)
+
+قم باستيراد `enqueueJob` من `twenty-sdk/logic-function` ووجّهه إلى `universalIdentifier` الخاص بوظيفة المنطق التي تريد تشغيلها.
+
+```ts src/logic-functions/sync-all-contacts.ts
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+});
+```
+
+تستقبل الدالة الهدف `payload` كوسيط للمعالج الخاص بها، تمامًا مثل أي مشغّل (trigger) آخر. يجب أن تنتمي إلى **نفس التطبيق** مثل الدالة المستدعية — تمت إضافة دالة في تطبيق آخر إلى قائمة الانتظار سيتم رفضها برسالة `Logic function not found`.
+
+
+تُرجِع `enqueueJob` فور قبول المهمة، وليس عند تشغيلها. لا تُرجِع ناتج الدالة الهدف — اطلب من الدالة الهدف أن تكتب ما تُنتجه في [مخزن المفاتيح والقيم](/l/ar/developers/extend/apps/logic/key-value-store) أو في سجل مساحة العمل إذا كنت بحاجة إلى قراءته لاحقًا.
+
+
+## خيارات المهمة
+
+| الخيار | الإعداد الافتراضي | النطاق | ماذا يفعل |
+| ------------ | ----------------- | ------------------------ | ----------------------------------------------------------------------------------------------------- |
+| `retryLimit` | `0` | `0`–`10` | محاولات إضافية إذا رمى التشغيل استثناءً. لا تقم بزيادة هذه القيمة إلا للمعالجات الآمنة للتشغيل مرتين. |
+| `delayMs` | `0` | `0`–`604800000` (7 أيام) | انتظر هذه المدة قبل أن يصبح التشغيل مؤهّلًا للتنفيذ. |
+
+```ts
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+ retryLimit: 3,
+ delayMs: 60_000,
+});
+```
+
+
+**أولوية التنفيذ غير قابلة للضبط بعد.** عمليات المهام في قائمة الانتظار تعمل دائمًا بأدنى أولوية، بحيث لا يتأخر عمل المنصة خلف مهام التطبيق. سيتم توفير إمكانية التحكّم في الأولوية قريبًا.
+
+
+يرث التشغيل المُضاف إلى قائمة الانتظار المستخدم الفعلي (acting user) الخاص بالدالة التي أضافته، لذلك يعمل بنفس الأذونات.
+
+## طريقة الاستخدام: التمرير عبر مزامنة طويلة
+
+النمط الكلاسيكي هو دالة تضيف *نفسها* إلى قائمة الانتظار مع المؤشر (cursor) التالي. يقوم كل تشغيل بتنفيذ صفحة واحدة من العمل ضمن مهلة التنفيذ الخاصة به، وتتوقّف السلسلة عندما لا يبقى شيء.
+
+```ts src/logic-functions/sync-contacts-page.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
+
+const handler = async (params: { cursor?: string }) => {
+ const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
+
+ await importContacts(contacts);
+
+ if (nextCursor) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
+ payload: { cursor: nextCursor },
+ delayMs: 2_000,
+ });
+ }
+
+ return { imported: contacts.length, done: !nextCursor };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: SYNC_CONTACTS_PAGE,
+ name: 'sync-contacts-page',
+ timeoutSeconds: 120,
+ handler,
+});
+```
+
+## توزيع متشعّب لكل سجل
+
+عندما يكون العمل بطبيعته لكل عنصر، أضِف مهمة واحدة لكل عنصر إلى قائمة الانتظار ودع العمال (workers) يعالجونها بالتوازي بدلًا من استخدام حلقة داخلية.
+
+```ts
+const companies = await listCompaniesToEnrich();
+
+await Promise.all(
+ companies.map((company) =>
+ enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANY,
+ payload: { companyId: company.id },
+ retryLimit: 2,
+ }),
+ ),
+);
+```
+
+## ممارسات جيدة للعمل طويل الأمد
+
+قاعدتان تغطيان تقريبًا كل مهمة طويلة: **استخدم الاستدعاء الذاتي (recursion) بدلًا من الحلقات (looping)**، و**عالِج جزءًا محدود الحجم في كل تشغيل**.
+
+التشغيل الذي يحاول تنفيذ كل شيء دفعة واحدة هو نمط الفشل — يصل إلى مهلة التنفيذ، ومع إعادة المحاولة يبدأ كل شيء من الصفر. بدلًا من ذلك، اضبط حجم الجزء بحيث ينتهي بشكل مريح ضمن `timeoutSeconds`، ثم خزّن موقعك الحالي، وأضِف التشغيل التالي إلى قائمة الانتظار.
+
+```ts src/logic-functions/enrich-companies-batch.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob, kv } from 'twenty-sdk/logic-function';
+
+const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
+const CHUNK_SIZE = 50;
+
+const handler = async (params: { offset?: number }) => {
+ const offset = params.offset ?? 0;
+ const companies = await listCompaniesToEnrich({
+ offset,
+ limit: CHUNK_SIZE,
+ });
+
+ for (const company of companies) {
+ await enrichCompany(company);
+ }
+
+ await kv.set('enrich:progress', { offset: offset + companies.length });
+
+ if (companies.length === CHUNK_SIZE) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
+ payload: { offset: offset + CHUNK_SIZE },
+ });
+ }
+
+ return { processed: companies.length, done: companies.length < CHUNK_SIZE };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: ENRICH_COMPANIES_BATCH,
+ name: 'enrich-companies-batch',
+ timeoutSeconds: 300,
+ handler,
+});
+```
+
+ما الذي يجعل هذا متماسكًا:
+
+* **اضبط حجم الجزء بناءً على أبطأ عنصر، وليس على المتوسّط.** يجب أن يتناسب حاصل ضرب `CHUNK_SIZE × worst-case item time` ضمن `timeoutSeconds` مع هامش احتياطي، وإلا فإن نهاية الجزء ستُفقَد عندما يتم إيقاف التشغيل بسبب انتهاء المهلة.
+* **اجعل شرط الإنهاء صريحًا.** استخدم الاستدعاء الذاتي فقط عندما يعود جزء كامل. سلسلة تتوقّف على أساس "عدم وجود نتائج" فقط ستستمر إلى الأبد إذا أعاد المصدر صفحة قصيرة في منتصف الطريق.
+* **ثبّت التقدّم قبل إضافة التشغيل التالي إلى قائمة الانتظار،** بحيث يُعاد تشغيل الحلقة الفاشلة من آخر جزء مكتمل بدلًا من البداية.
+* **اجعل كل جزء قابلًا للتكرار دون آثار جانبية (idempotent).** يجب ألّا يؤدّي إعادة معالجة جزء واحد بعد إعادة المحاولة إلى كتابة مزدوجة — اربط عمليات الكتابة بالسجل أو المعرّف الخارجي الذي تعالجه.
+* **فضّل سلسلة مجزّأة على توزيع واحد ضخم (giant fan-out)** عندما يتعامل العمل مع طرف ثالث محدود المعدّل (rate-limited): سلسلة مع `delayMs` تنظّم نفسها، في حين أن آلاف المهام المضافة إلى قائمة الانتظار دفعة واحدة تصبح جميعها مؤهّلة فورًا.
+
+
+عمليات إعادة المحاولة تعيد تشغيل المعالج بالكامل. حافظ على أن تكون المعالجات في قائمة الانتظار قابلة للتكرار دون آثار جانبية (idempotent) قبل ضبط `retryLimit` على قيمة أعلى من `0`.
+
diff --git a/packages/twenty-docs/l/ar/developers/extend/apps/logic/overview.mdx b/packages/twenty-docs/l/ar/developers/extend/apps/logic/overview.mdx
index f62f92f085..99245b01e2 100644
--- a/packages/twenty-docs/l/ar/developers/extend/apps/logic/overview.mdx
+++ b/packages/twenty-docs/l/ar/developers/extend/apps/logic/overview.mdx
@@ -37,6 +37,9 @@ icon: bolt
الاحتفاظ بالحالة بين تشغيلات دالة المنطق — ذاكرات التخزين المؤقت، والمؤشرات، والمطالبات عبر مساحات العمل.
+
+ ضع في قائمة الانتظار تشغيل دالة منطقية على العمال لتجاوز مهلة التشغيل لكل عملية.
+
## لمحة عن أنواع المشغلات
diff --git a/packages/twenty-docs/l/ar/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/ar/developers/self-host/capabilities/upgrade-guide.mdx
index 2563549e59..e913a3ecaf 100644
--- a/packages/twenty-docs/l/ar/developers/self-host/capabilities/upgrade-guide.mdx
+++ b/packages/twenty-docs/l/ar/developers/self-host/capabilities/upgrade-guide.mdx
@@ -25,11 +25,11 @@ cat databases_backup.sql | docker exec -i {db_container_name_or_id} psql -U {pos
يقوم الخادم بتشغيل جميع عمليات ترحيل الترقية المطلوبة تلقائيًا عند بدء التشغيل. لا حاجة إلى أي أمر يدوي.
-## الترقيات عبر الإصدارات (v1.22+)
+## الترقيات عبر الإصدارات (v1.23+)
-بدءًا من **v1.22**، يدعم Twenty الترقيات عبر الإصدارات. يمكنك الانتقال مباشرةً من أي إصدار مدعوم إلى أحدث إصدار دون المرور بكل إصدار وسيط.
+بدءًا من **v1.23**، يدعم Twenty الترقيات عبر الإصدارات. يمكنك الانتقال مباشرةً من أي إصدار مدعوم إلى أحدث إصدار دون المرور بكل إصدار وسيط.
-على سبيل المثال، الترقية من v1.22 مباشرةً إلى v2.0 مدعومة بالكامل.
+على سبيل المثال، الترقية من v1.23 مباشرةً إلى v2.0 مدعومة بالكامل.
## الترقية إلى v2.5+ — غلاف التشفير للبيانات الساكنة (at-rest)
@@ -97,6 +97,6 @@ docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status -
يعرض هذا فقط مساحات العمل المتأخرة أو التي فشلت، مع رسالة الخطأ لكل حالة فشل.
-## قبل v1.22
+## قبل v1.23
-إذا كان مثيلك أقدم من v1.22، فيجب عليك الترقية تدريجيًا عبر كل إصدار رئيسي موسوم (من v1.6 إلى v1.7، ثم من v1.7 إلى v1.8، وهكذا) حتى تصل إلى v1.22. ومن هناك، يمكنك الانتقال مباشرةً إلى أحدث إصدار.
+إذا كان مثيلك أقدم من v1.23، فيجب عليك الترقية تدريجيًا عبر كل إصدار رئيسي موسوم (من v1.6 إلى v1.7، ثم من v1.7 إلى v1.8، وهكذا) حتى تصل إلى v1.23. ومن هناك، يمكنك الانتقال مباشرةً إلى أحدث إصدار.
diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/logic/background-jobs.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/logic/background-jobs.mdx
new file mode 100644
index 0000000000..85c109bef3
--- /dev/null
+++ b/packages/twenty-docs/l/cs/developers/extend/apps/logic/background-jobs.mdx
@@ -0,0 +1,165 @@
+---
+title: Úlohy na pozadí
+description: Předejte dlouhotrvající nebo rychlostně omezenou práci Twenty workerům tak, že místo provádění všeho přímo vkládáte do fronty další spuštění logické funkce.
+icon: layer-group
+---
+
+Běh logické funkce je omezen svým `timeoutSeconds` (maximálně 900 sekund). Cokoli, co se nedokáže dokončit v tomto časovém okně — úplná resynchronizace, fan-out pro jednotlivé záznamy, služba třetí strany, která vás omezuje rychlostí — musí být rozděleno do menších běhů.
+
+`enqueueJob` dělá přesně to: požádá Twenty workery, aby jednu z logických funkcí vaší aplikace spustili později, ve vlastním procesu a s vlastním časovým limitem. Volající se vrátí okamžitě.
+
+```text
+ ┌─────────────────┐ enqueueJob(...) ┌──────────────┐ ┌────────────────────┐
+ │ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
+ │ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
+ └─────────────────┘ └──────────────┘ └────────────────────┘
+```
+
+## Zařazení spuštění do fronty
+
+Importujte `enqueueJob` z `twenty-sdk/logic-function` a nasměrujte ho na `universalIdentifier` logické funkce, kterou chcete spustit.
+
+```ts src/logic-functions/sync-all-contacts.ts
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+});
+```
+
+Cílová funkce přijímá `payload` jako argument svého handleru, úplně stejně jako jakýkoli jiný spouštěč. Musí patřit do **stejné aplikace** jako volající – zařazení funkce jiné aplikace je odmítnuto s chybou `Logic function not found`.
+
+
+`enqueueJob` se vrátí, jakmile je úloha přijata, ne až když je dokončena. Nevrací výsledek cíle – nechte cíl zapsat, co vytváří, do [úložiště klíč–hodnota](/l/cs/developers/extend/apps/logic/key-value-store) nebo do záznamu v pracovním prostoru, pokud to potřebujete znovu přečíst.
+
+
+## Možnosti úlohy
+
+| Možnost | Výchozí | Rozsah | K čemu slouží |
+| ------------ | ------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------- |
+| `retryLimit` | `0` | `0`–`10` | Další pokusy, pokud spuštění vyvolá výjimku. Zvyšujte tuto hodnotu jen u handlerů, které je bezpečné spustit dvakrát. |
+| `delayMs` | `0` | `0`–`604800000` (7 dní) | Po tuto dobu se čeká, než se spuštění stane způsobilým. |
+
+```ts
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+ retryLimit: 3,
+ delayMs: 60_000,
+});
+```
+
+
+**Prioritu zatím nelze konfigurovat.** Zařazené úlohy jsou vždy spuštěny s nejnižší prioritou, takže práce platformy není nikdy zdržována úlohami aplikací. Možnost řízení priority brzy přibude.
+
+
+Zařazené spuštění dědí jednajícího uživatele funkce, která ho zařadila, takže pracuje se stejnými oprávněními.
+
+## Použití: stránkování dlouhé synchronizace
+
+Klasický tvar je funkce, která do fronty zařazuje *samu sebe* s dalším kurzorem. Každé spuštění zpracuje jednu stránku práce s velkou rezervou vůči svému vlastnímu časovému limitu a řetězec se zastaví, když už nic nezbývá.
+
+```ts src/logic-functions/sync-contacts-page.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
+
+const handler = async (params: { cursor?: string }) => {
+ const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
+
+ await importContacts(contacts);
+
+ if (nextCursor) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
+ payload: { cursor: nextCursor },
+ delayMs: 2_000,
+ });
+ }
+
+ return { imported: contacts.length, done: !nextCursor };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: SYNC_CONTACTS_PAGE,
+ name: 'sync-contacts-page',
+ timeoutSeconds: 120,
+ handler,
+});
+```
+
+## Rozvětvení po záznamech
+
+Když je práce přirozeně po položkách, zařaďte do fronty jednu úlohu na položku a nechte workery zpracovávat je paralelně místo smyčky inline.
+
+```ts
+const companies = await listCompaniesToEnrich();
+
+await Promise.all(
+ companies.map((company) =>
+ enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANY,
+ payload: { companyId: company.id },
+ retryLimit: 2,
+ }),
+ ),
+);
+```
+
+## Doporučené postupy pro dlouhotrvající práci
+
+Dvě pravidla pokryjí téměř každou dlouhou úlohu: **rekurze místo smyčky** a **zpracování omezeného bloku na jedno spuštění**.
+
+Spuštění, které se snaží udělat všechno, je způsob selhání – narazí na časový limit a při opakování začne celé znovu od nuly. Místo toho nastavte velikost jednoho bloku tak, aby se pohodlně vešel do `timeoutSeconds`, uložte si svou pozici a zařaďte do fronty další spuštění.
+
+```ts src/logic-functions/enrich-companies-batch.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob, kv } from 'twenty-sdk/logic-function';
+
+const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
+const CHUNK_SIZE = 50;
+
+const handler = async (params: { offset?: number }) => {
+ const offset = params.offset ?? 0;
+ const companies = await listCompaniesToEnrich({
+ offset,
+ limit: CHUNK_SIZE,
+ });
+
+ for (const company of companies) {
+ await enrichCompany(company);
+ }
+
+ await kv.set('enrich:progress', { offset: offset + companies.length });
+
+ if (companies.length === CHUNK_SIZE) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
+ payload: { offset: offset + CHUNK_SIZE },
+ });
+ }
+
+ return { processed: companies.length, done: companies.length < CHUNK_SIZE };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: ENRICH_COMPANIES_BATCH,
+ name: 'enrich-companies-batch',
+ timeoutSeconds: 300,
+ handler,
+});
+```
+
+Co to drží pohromadě:
+
+* **Velikost bloku určete podle nejpomalejší položky, ne podle průměru.** `CHUNK_SIZE × worst-case item time` se musí vejít do `timeoutSeconds` s rezervou, jinak se konec bloku ztratí, když je spuštění utnuto.
+* **Udělejte ukončovací podmínku explicitní.** Rekurzi provádějte jen tehdy, když se vrátil plný blok. Řetězec, který se zastavuje jen na základě „žádné výsledky“, poběží donekonečna, pokud zdroj někdy uprostřed vrátí krátkou stránku.
+* **Uložte průběh před zařazením dalšího spuštění,** takže se neúspěšný článek řetězu znovu spustí od posledního dokončeného bloku místo od začátku.
+* **Udržujte každý blok idempotentní.** Znovuzpracování jednoho bloku po opakování nesmí vést k dvojím zápisům – své zápisy važte na záznam nebo externí ID, které zpracováváte.
+* **Preferujte řetězec po blocích před jedním obřím rozvětvením**, když práce zasahuje třetí stranu s omezením rychlosti: řetězec s `delayMs` sám sebe dávkuje, zatímco tisíce úloh zařazených najednou se stanou způsobilými okamžitě.
+
+
+Opakované pokusy znovu spustí celý handler. Než nastavíte `retryLimit` nad `0`, udržujte zařazené handlery idempotentní.
+
diff --git a/packages/twenty-docs/l/cs/developers/extend/apps/logic/overview.mdx b/packages/twenty-docs/l/cs/developers/extend/apps/logic/overview.mdx
index de4f9bef69..18f2895959 100644
--- a/packages/twenty-docs/l/cs/developers/extend/apps/logic/overview.mdx
+++ b/packages/twenty-docs/l/cs/developers/extend/apps/logic/overview.mdx
@@ -37,6 +37,9 @@ icon: bolt
Udržujte stav mezi spuštěními logických funkcí – cache, kurzory a nároky napříč pracovními prostory.
+
+ Zařaďte spuštění logické funkce do fronty na workery, abyste obešli časový limit na jedno spuštění.
+
## Přehled typů spouštěčů
diff --git a/packages/twenty-docs/l/cs/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/cs/developers/self-host/capabilities/upgrade-guide.mdx
index 0bc8cd6f05..2ef284be5d 100644
--- a/packages/twenty-docs/l/cs/developers/self-host/capabilities/upgrade-guide.mdx
+++ b/packages/twenty-docs/l/cs/developers/self-host/capabilities/upgrade-guide.mdx
@@ -25,11 +25,11 @@ Pokud používáte Docker Compose, postupujte takto:
Server při startu automaticky spustí všechny požadované migrační kroky upgradu. Není potřeba žádný ruční příkaz.
-## Upgrade napříč verzemi (v1.22+)
+## Upgrade napříč verzemi (v1.23+)
-Počínaje **v1.22** podporuje Twenty upgrade napříč verzemi. Můžete přejít přímo z jakékoli podporované verze na nejnovější vydání, aniž byste museli procházet každou meziverzí.
+Počínaje **v1.23** podporuje Twenty upgrade napříč verzemi. Můžete přejít přímo z jakékoli podporované verze na nejnovější vydání, aniž byste museli procházet každou meziverzí.
-Například přechod z v1.22 přímo na v2.0 je plně podporován.
+Například přechod z v1.23 přímo na v2.0 je plně podporován.
## Aktualizace na v2.5+ — obálka pro šifrování dat v klidu
@@ -97,6 +97,6 @@ docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status -
Tímto se zobrazí pouze pracovní prostory, které jsou pozadu nebo selhaly, spolu s chybovou zprávou pro každé selhání.
-## Před v1.22
+## Před v1.23
-Pokud je vaše instance starší než v1.22, musíte upgradovat postupně přes každou hlavní označenou verzi (z v1.6 na v1.7, poté z v1.7 na v1.8 atd.), dokud nedosáhnete v1.22. Odtud můžete přejít přímo na nejnovější verzi.
+Pokud je vaše instance starší než v1.23, musíte upgradovat postupně přes každou hlavní označenou verzi (z v1.6 na v1.7, poté z v1.7 na v1.8 atd.), dokud nedosáhnete v1.23. Odtud můžete přejít přímo na nejnovější verzi.
diff --git a/packages/twenty-docs/l/de/developers/extend/apps/logic/background-jobs.mdx b/packages/twenty-docs/l/de/developers/extend/apps/logic/background-jobs.mdx
new file mode 100644
index 0000000000..24014e04ff
--- /dev/null
+++ b/packages/twenty-docs/l/de/developers/extend/apps/logic/background-jobs.mdx
@@ -0,0 +1,165 @@
+---
+title: Hintergrundjobs
+description: Übergib lang laufende oder rate-limitierte Arbeiten an die Twenty-Worker, indem du einen weiteren Lauf einer Logikfunktion in die Warteschlange stellst, statt alles inline auszuführen.
+icon: layer-group
+---
+
+Ein Logikfunktionslauf ist durch seine `timeoutSeconds` begrenzt (maximal 900 Sekunden). Alles, was in diesem Zeitfenster nicht abgeschlossen werden kann – ein vollständiger Re-Sync, ein Fan-out pro Datensatz, eine Drittanbieter-API, die dich rate-limitiert – muss in kleinere Läufe aufgeteilt werden.
+
+`enqueueJob` macht genau das: Es weist die Twenty-Worker an, eine der Logikfunktionen deiner App später, in einem eigenen Prozess, mit eigenem Timeout-Budget auszuführen. Der Aufrufer kehrt sofort zurück.
+
+```text
+ ┌─────────────────┐ enqueueJob(...) ┌──────────────┐ ┌────────────────────┐
+ │ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
+ │ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
+ └─────────────────┘ └──────────────┘ └────────────────────┘
+```
+
+## Einen Lauf in die Warteschlange stellen
+
+Importiere `enqueueJob` aus `twenty-sdk/logic-function` und gib den `universalIdentifier` der Logikfunktion an, die du ausführen möchtest.
+
+```ts src/logic-functions/sync-all-contacts.ts
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+});
+```
+
+Die Zielfunktion erhält `payload` als Handler-Argument, genau wie jeder andere Trigger. Sie muss zur **gleichen Applikation** gehören wie der Aufrufer — das Einreihen der Funktion einer anderen App wird mit `Logic function not found` abgelehnt.
+
+
+`enqueueJob` kehrt zurück, sobald der Job akzeptiert wurde, nicht wenn er ausgeführt wurde. Sie gibt nicht das Ergebnis des Ziels zurück — lasse das Ziel das Ergebnis in den [Key-Value-Speicher](/l/de/developers/extend/apps/logic/key-value-store) oder in einen Workspace-Datensatz schreiben, wenn du es wieder auslesen musst.
+
+
+## Job-Optionen
+
+| Option | Standard | Bereich | Was es tut |
+| ------------ | -------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
+| `retryLimit` | `0` | `0`–`10` | Zusätzliche Versuche, falls der Lauf eine Exception auslöst. Erhöhe diesen Wert nur für Handler, die sicher zweimal ausgeführt werden können. |
+| `delayMs` | `0` | `0`–`604800000` (7 Tage) | So lange warten, bevor der Lauf zur Ausführung ansteht. |
+
+```ts
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+ retryLimit: 3,
+ delayMs: 60_000,
+});
+```
+
+
+**Priorität ist noch nicht konfigurierbar.** Eingereihte Jobs laufen immer mit der niedrigsten Priorität, sodass Plattformarbeit niemals hinter Applikationsjobs verzögert wird. Steuerung der Priorität kommt bald.
+
+
+Der eingereihte Lauf erbt den agierenden Benutzer der Funktion, die ihn eingereiht hat, sodass er mit denselben Berechtigungen agiert.
+
+## Verwendung: eine lange Synchronisation seitenweise verarbeiten
+
+Die klassische Form ist eine Funktion, die *sich selbst* mit dem nächsten Cursor in die Warteschlange stellt. Jeder Lauf erledigt eine Seite Arbeit deutlich innerhalb seines eigenen Timeouts, und die Kette endet, wenn nichts mehr übrig ist.
+
+```ts src/logic-functions/sync-contacts-page.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
+
+const handler = async (params: { cursor?: string }) => {
+ const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
+
+ await importContacts(contacts);
+
+ if (nextCursor) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
+ payload: { cursor: nextCursor },
+ delayMs: 2_000,
+ });
+ }
+
+ return { imported: contacts.length, done: !nextCursor };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: SYNC_CONTACTS_PAGE,
+ name: 'sync-contacts-page',
+ timeoutSeconds: 120,
+ handler,
+});
+```
+
+## Aufteilung pro Datensatz
+
+Wenn die Arbeit natürlich pro Element anfällt, reihe einen Job pro Element ein und lasse die Worker sie parallel verarbeiten, anstatt inline zu iterieren.
+
+```ts
+const companies = await listCompaniesToEnrich();
+
+await Promise.all(
+ companies.map((company) =>
+ enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANY,
+ payload: { companyId: company.id },
+ retryLimit: 2,
+ }),
+ ),
+);
+```
+
+## Bewährte Vorgehensweisen für langlaufende Arbeit
+
+Zwei Regeln decken fast jeden langen Job ab: **rekursiv statt in einer Schleife arbeiten** und **pro Lauf einen begrenzten Chunk verarbeiten**.
+
+Ein Lauf, der versucht, alles zu erledigen, ist der Fehlerfall — er erreicht das Timeout, und bei einem Retry startet er den gesamten Vorgang erneut von vorn. Bestimme stattdessen die Größe eines Chunks so, dass er bequem innerhalb von `timeoutSeconds` fertig wird, speichere deine Position und reihe den nächsten Lauf ein.
+
+```ts src/logic-functions/enrich-companies-batch.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob, kv } from 'twenty-sdk/logic-function';
+
+const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
+const CHUNK_SIZE = 50;
+
+const handler = async (params: { offset?: number }) => {
+ const offset = params.offset ?? 0;
+ const companies = await listCompaniesToEnrich({
+ offset,
+ limit: CHUNK_SIZE,
+ });
+
+ for (const company of companies) {
+ await enrichCompany(company);
+ }
+
+ await kv.set('enrich:progress', { offset: offset + companies.length });
+
+ if (companies.length === CHUNK_SIZE) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
+ payload: { offset: offset + CHUNK_SIZE },
+ });
+ }
+
+ return { processed: companies.length, done: companies.length < CHUNK_SIZE };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: ENRICH_COMPANIES_BATCH,
+ name: 'enrich-companies-batch',
+ timeoutSeconds: 300,
+ handler,
+});
+```
+
+Warum das funktioniert:
+
+* **Lege die Chunk-Größe anhand des langsamsten Elements fest, nicht anhand des Durchschnitts.** `CHUNK_SIZE × Worst-Case-Elementzeit` muss mit ausreichendem Puffer in `timeoutSeconds` passen, sonst geht das Ende eines Chunks verloren, wenn der Lauf abgeschnitten wird.
+* **Mache die Abbruchbedingung explizit.** Rekursiere nur, solange ein voller Chunk zurückgekommen ist. Eine Kette, die nur bei „keine Ergebnisse“ anhält, läuft für immer weiter, wenn die Quelle unterwegs einmal eine verkürzte Seite zurückgibt.
+* **Persistiere den Fortschritt, bevor du den nächsten Lauf einreihst,** damit ein fehlgeschlagener Link beim letzten abgeschlossenen Chunk und nicht am Anfang neu startet.
+* **Halte jeden Chunk idempotent.** Die erneute Verarbeitung eines Chunks nach einem Retry darf nicht zu doppelten Schreibvorgängen führen — führe Schlüsselschreibvorgänge auf dem Datensatz oder der externen ID aus, die du verarbeitest.
+* **Bevorzuge eine verkettete Chunk-Verarbeitung gegenüber einem riesigen Fan-out**, wenn die Arbeit auf einen rate-limitierten Drittanbieter trifft: Eine Kette mit `delayMs` taktet sich selbst, wohingegen Tausende auf einmal eingereihte Jobs sofort zur Ausführung anstehen.
+
+
+Retries führen den gesamten Handler erneut aus. Halte eingereihte Handler idempotent, bevor du `retryLimit` über `0` setzt.
+
diff --git a/packages/twenty-docs/l/de/developers/extend/apps/logic/overview.mdx b/packages/twenty-docs/l/de/developers/extend/apps/logic/overview.mdx
index 2c23072fbd..97c9ec5491 100644
--- a/packages/twenty-docs/l/de/developers/extend/apps/logic/overview.mdx
+++ b/packages/twenty-docs/l/de/developers/extend/apps/logic/overview.mdx
@@ -37,6 +37,9 @@ Die **Logikschicht** einer Twenty-App ist der Code, der *ausgeführt wird* – s
Zustand zwischen Ausführungen von Logikfunktionen beibehalten — Caches, Cursor und arbeitsbereichsübergreifende Claims.
+
+ Stellen Sie einen Lauf einer Logikfunktion in die Warteschlange der Worker, um das Timeout pro Ausführung zu umgehen.
+
## Auslösertypen im Überblick
diff --git a/packages/twenty-docs/l/de/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/de/developers/self-host/capabilities/upgrade-guide.mdx
index a41f011834..eed8aa903c 100644
--- a/packages/twenty-docs/l/de/developers/self-host/capabilities/upgrade-guide.mdx
+++ b/packages/twenty-docs/l/de/developers/self-host/capabilities/upgrade-guide.mdx
@@ -25,11 +25,11 @@ Wenn Sie Docker Compose verwenden, befolgen Sie diese Schritte:
Der Server führt beim Start alle erforderlichen Upgrade-Migrationen automatisch aus. Kein manueller Befehl erforderlich.
-## Versionsübergreifende Upgrades (v1.22+)
+## Versionsübergreifende Upgrades (v1.23+)
-Ab **v1.22** unterstützt Twenty versionsübergreifende Upgrades. Sie können direkt von jeder unterstützten Version auf die neueste Version wechseln, ohne jede Zwischenversion durchlaufen zu müssen.
+Ab **v1.23** unterstützt Twenty versionsübergreifende Upgrades. Sie können direkt von jeder unterstützten Version auf die neueste Version wechseln, ohne jede Zwischenversion durchlaufen zu müssen.
-Zum Beispiel wird ein Upgrade direkt von v1.22 auf v2.0 vollständig unterstützt.
+Zum Beispiel wird ein Upgrade direkt von v1.23 auf v2.0 vollständig unterstützt.
## Upgrade auf v2.5+ – Verschlüsselungsumschlag für ruhende Daten
@@ -97,6 +97,6 @@ docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status -
Dies zeigt nur Workspaces, die zurückliegen oder fehlgeschlagen sind, zusammen mit der Fehlermeldung für jeden Fehlschlag.
-## Vor v1.22
+## Vor v1.23
-Wenn Ihre Instanz älter als v1.22 ist, müssen Sie schrittweise über jede größere getaggte Version ein Upgrade durchführen (von v1.6 auf v1.7, dann von v1.7 auf v1.8 usw.), bis Sie v1.22 erreichen. Danach können Sie direkt auf die neueste Version wechseln.
+Wenn Ihre Instanz älter als v1.23 ist, müssen Sie schrittweise über jede größere getaggte Version ein Upgrade durchführen (von v1.6 auf v1.7, dann von v1.7 auf v1.8 usw.), bis Sie v1.23 erreichen. Danach können Sie direkt auf die neueste Version wechseln.
diff --git a/packages/twenty-docs/l/es/developers/extend/apps/logic/background-jobs.mdx b/packages/twenty-docs/l/es/developers/extend/apps/logic/background-jobs.mdx
new file mode 100644
index 0000000000..7a0a2b6584
--- /dev/null
+++ b/packages/twenty-docs/l/es/developers/extend/apps/logic/background-jobs.mdx
@@ -0,0 +1,165 @@
+---
+title: Trabajos en segundo plano
+description: Puede delegar el trabajo prolongado o sujeto a limitaciones de tasa a los workers de Twenty encolando otra ejecución de una función lógica en lugar de hacerlo todo en el mismo flujo.
+icon: layer-group
+---
+
+La ejecución de una función lógica está limitada por su `timeoutSeconds` (900 segundos como máximo). Cualquier cosa que no pueda terminar en esa ventana — una resincronización completa, una distribución por registro, una API de terceros que impone límites de tasa — debe dividirse en ejecuciones más pequeñas.
+
+`enqueueJob` hace exactamente eso: solicita a los workers de Twenty que ejecuten más tarde una de las funciones lógicas de su aplicación, en su propio proceso, con su propio presupuesto de tiempo de espera. La llamada devuelve de inmediato.
+
+```text
+ ┌─────────────────┐ enqueueJob(...) ┌──────────────┐ ┌────────────────────┐
+ │ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
+ │ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
+ └─────────────────┘ └──────────────┘ └────────────────────┘
+```
+
+## Poner en cola una ejecución
+
+Importa `enqueueJob` desde `twenty-sdk/logic-function` y apúntalo al `universalIdentifier` de la función lógica que quieres ejecutar.
+
+```ts src/logic-functions/sync-all-contacts.ts
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+});
+```
+
+La función de destino recibe `payload` como su argumento de manejador, exactamente igual que cualquier otro disparador. Debe pertenecer a la **misma aplicación** que la que la llama: poner en cola la función de otra aplicación se rechaza con `Logic function not found`.
+
+
+`enqueueJob` devuelve tan pronto como se acepta el trabajo, no cuando ya se ha ejecutado. No devuelve el resultado del destino: haz que el destino escriba lo que produce en el [almacenamiento de pares clave-valor](/l/es/developers/extend/apps/logic/key-value-store) o en un registro del espacio de trabajo si necesitas leerlo después.
+
+
+## Opciones del trabajo
+
+| Opción | Predeterminado | Rango | Qué hace |
+| ------------ | -------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
+| `retryLimit` | `0` | `0`–`10` | Intentos adicionales si la ejecución produce una excepción. Solo aumenta este valor para manejadores que sea seguro ejecutar dos veces. |
+| `delayMs` | `0` | `0`–`604800000` (7 días) | Espera este tiempo antes de que la ejecución sea elegible. |
+
+```ts
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+ retryLimit: 3,
+ delayMs: 60_000,
+});
+```
+
+
+**La prioridad todavía no es configurable.** Los trabajos encolados siempre se ejecutan con la prioridad más baja, para que el trabajo de la plataforma nunca se retrase por detrás de los trabajos de las aplicaciones. El control sobre la prioridad llegará pronto.
+
+
+La ejecución encolada hereda el usuario en acción de la función que la puso en cola, por lo que actúa con los mismos permisos.
+
+## Úsalo: recorre por páginas una sincronización larga
+
+La forma clásica es una función que se pone en cola *a sí misma* con el cursor siguiente. Cada ejecución hace una página de trabajo bien dentro de su propio tiempo de espera, y la cadena se detiene cuando no queda nada.
+
+```ts src/logic-functions/sync-contacts-page.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
+
+const handler = async (params: { cursor?: string }) => {
+ const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
+
+ await importContacts(contacts);
+
+ if (nextCursor) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
+ payload: { cursor: nextCursor },
+ delayMs: 2_000,
+ });
+ }
+
+ return { imported: contacts.length, done: !nextCursor };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: SYNC_CONTACTS_PAGE,
+ name: 'sync-contacts-page',
+ timeoutSeconds: 120,
+ handler,
+});
+```
+
+## Dividir por registro
+
+Cuando el trabajo es de forma natural por elemento, pone en cola un trabajo por elemento y deja que los workers los procesen en paralelo en lugar de iterar en línea.
+
+```ts
+const companies = await listCompaniesToEnrich();
+
+await Promise.all(
+ companies.map((company) =>
+ enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANY,
+ payload: { companyId: company.id },
+ retryLimit: 2,
+ }),
+ ),
+);
+```
+
+## Buenas prácticas para trabajo de larga duración
+
+Dos reglas cubren casi cualquier trabajo largo: **usa recursión en lugar de bucles** y **procesa un bloque acotado por ejecución**.
+
+Una ejecución que intenta hacerlo todo es la forma en que falla: alcanza el tiempo de espera y, con un reintento, vuelve a empezar todo desde cero. En su lugar, dimensiona un bloque para que termine con holgura dentro de `timeoutSeconds`, guarda tu posición y pon en cola la siguiente ejecución.
+
+```ts src/logic-functions/enrich-companies-batch.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob, kv } from 'twenty-sdk/logic-function';
+
+const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
+const CHUNK_SIZE = 50;
+
+const handler = async (params: { offset?: number }) => {
+ const offset = params.offset ?? 0;
+ const companies = await listCompaniesToEnrich({
+ offset,
+ limit: CHUNK_SIZE,
+ });
+
+ for (const company of companies) {
+ await enrichCompany(company);
+ }
+
+ await kv.set('enrich:progress', { offset: offset + companies.length });
+
+ if (companies.length === CHUNK_SIZE) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
+ payload: { offset: offset + CHUNK_SIZE },
+ });
+ }
+
+ return { processed: companies.length, done: companies.length < CHUNK_SIZE };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: ENRICH_COMPANIES_BATCH,
+ name: 'enrich-companies-batch',
+ timeoutSeconds: 300,
+ handler,
+});
+```
+
+Por qué esto se mantiene bien:
+
+* **Dimensiona el bloque a partir del elemento más lento, no del promedio.** `CHUNK_SIZE × worst-case item time` tiene que caber en `timeoutSeconds` con margen de sobra, o la parte final de un bloque se pierde cuando se corta la ejecución.
+* **Haz que la condición de terminación sea explícita.** Haz recursión solo mientras haya vuelto un bloque completo. Una cadena que se detiene solo con "sin resultados" seguirá ejecutándose para siempre si la fuente alguna vez devuelve una página corta a mitad de camino.
+* **Guarda el progreso antes de poner en cola la siguiente ejecución**, de modo que un eslabón fallido se reinicie desde el último bloque completado en lugar de desde el principio.
+* **Mantén cada bloque idempotente.** Volver a procesar un bloque después de un reintento no debe escribir dos veces: haz escrituras clave en el registro o en el id externo que estés procesando.
+* **Prefiere una cadena por bloques en lugar de una expansión masiva** cuando el trabajo golpea a un tercero con límite de velocidad: una cadena con `delayMs` se autorregula, mientras que miles de trabajos encolados a la vez se vuelven elegibles inmediatamente.
+
+
+Los reintentos vuelven a ejecutar todo el manejador. Mantén los manejadores encolados idempotentes antes de establecer `retryLimit` por encima de `0`.
+
diff --git a/packages/twenty-docs/l/es/developers/extend/apps/logic/overview.mdx b/packages/twenty-docs/l/es/developers/extend/apps/logic/overview.mdx
index 6a7dc0b5c4..dc30eab818 100644
--- a/packages/twenty-docs/l/es/developers/extend/apps/logic/overview.mdx
+++ b/packages/twenty-docs/l/es/developers/extend/apps/logic/overview.mdx
@@ -37,6 +37,9 @@ La **capa de lógica** de una app de Twenty es el código que *se ejecuta*: cont
Conserva el estado entre ejecuciones de funciones lógicas — cachés, cursores y declaraciones entre espacios de trabajo.
+
+ Coloca en la cola la ejecución de una función de lógica en los workers para superar el tiempo de espera por ejecución.
+
## Tipos de disparadores de un vistazo
diff --git a/packages/twenty-docs/l/es/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/es/developers/self-host/capabilities/upgrade-guide.mdx
index 3841aeb83f..384d310d3e 100644
--- a/packages/twenty-docs/l/es/developers/self-host/capabilities/upgrade-guide.mdx
+++ b/packages/twenty-docs/l/es/developers/self-host/capabilities/upgrade-guide.mdx
@@ -25,11 +25,11 @@ Si usa Docker Compose, siga estos pasos:
El servidor ejecuta automáticamente todas las migraciones de actualización necesarias al iniciarse. No se requiere ningún comando manual.
-## Actualizaciones entre versiones (v1.22+)
+## Actualizaciones entre versiones (v1.23+)
-A partir de la **v1.22**, Twenty admite actualizaciones entre versiones. Puede actualizar directamente desde cualquier versión compatible a la última versión sin tener que pasar por cada versión intermedia.
+A partir de la **v1.23**, Twenty admite actualizaciones entre versiones. Puede actualizar directamente desde cualquier versión compatible a la última versión sin tener que pasar por cada versión intermedia.
-Por ejemplo, actualizar de la v1.22 directamente a la v2.0 está totalmente admitido.
+Por ejemplo, actualizar de la v1.23 directamente a la v2.0 está totalmente admitido.
## Actualización a v2.5+ — envoltura de cifrado de datos en reposo
@@ -97,6 +97,6 @@ docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status -
Esto muestra únicamente los espacios de trabajo que están desactualizados o han fallado, junto con el mensaje de error de cada fallo.
-## Antes de la v1.22
+## Antes de la v1.23
-Si su instancia es anterior a la v1.22, debe actualizar de forma incremental a través de cada versión principal etiquetada (de la v1.6 a la v1.7, luego de la v1.7 a la v1.8, y así sucesivamente) hasta llegar a la v1.22. A partir de ahí, puede actualizar directamente a la última versión.
+Si su instancia es anterior a la v1.23, debe actualizar de forma incremental a través de cada versión principal etiquetada (de la v1.6 a la v1.7, luego de la v1.7 a la v1.8, y así sucesivamente) hasta llegar a la v1.23. A partir de ahí, puede actualizar directamente a la última versión.
diff --git a/packages/twenty-docs/l/fr/developers/extend/apps/logic/background-jobs.mdx b/packages/twenty-docs/l/fr/developers/extend/apps/logic/background-jobs.mdx
new file mode 100644
index 0000000000..20bc02325d
--- /dev/null
+++ b/packages/twenty-docs/l/fr/developers/extend/apps/logic/background-jobs.mdx
@@ -0,0 +1,165 @@
+---
+title: Tâches en arrière-plan
+description: Confiez les travaux longs ou soumis à des limitations de débit aux workers de Twenty en mettant en file d’attente une autre exécution de fonction logique au lieu de tout faire en ligne.
+icon: layer-group
+---
+
+Une exécution de fonction logique est limitée par son `timeoutSeconds` (900 secondes maximum). Tout ce qui ne peut pas se terminer dans cette fenêtre — une resynchronisation complète, une diffusion par enregistrement, une API tierce qui vous applique des limitations de débit — doit être découpé en exécutions plus petites.
+
+`enqueueJob` fait exactement cela : il demande aux workers de Twenty d’exécuter plus tard l’une des fonctions logiques de votre application, dans son propre processus, avec son propre budget de délai d’expiration. La fonction appelante retourne immédiatement.
+
+```text
+ ┌─────────────────┐ enqueueJob(...) ┌──────────────┐ ┌────────────────────┐
+ │ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
+ │ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
+ └─────────────────┘ └──────────────┘ └────────────────────┘
+```
+
+## Mettre en file d’attente une exécution
+
+Importez `enqueueJob` depuis `twenty-sdk/logic-function` et pointez-le vers le `universalIdentifier` de la fonction logique que vous voulez exécuter.
+
+```ts src/logic-functions/sync-all-contacts.ts
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+});
+```
+
+La fonction cible reçoit `payload` comme argument de son gestionnaire, exactement comme pour tout autre déclencheur. Elle doit appartenir à la **même application** que l’appelant — la mise en file d’attente de la fonction d’une autre application est rejetée avec `Logic function not found`.
+
+
+`enqueueJob` renvoie dès que le job est accepté, et non pas lorsqu’il a été exécuté. Il ne renvoie pas le résultat de la cible — faites en sorte que la cible écrive ce qu’elle produit dans le [key-value store](/l/fr/developers/extend/apps/logic/key-value-store) ou dans un enregistrement d’espace de travail si vous devez le lire à nouveau.
+
+
+## Options du job
+
+| Option | Par défaut | Plage | Ce que cela fait |
+| ------------ | ---------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `retryLimit` | `0` | `0`–`10` | Tentatives supplémentaires si l’exécution lève une exception. N’augmentez cette valeur que pour les gestionnaires qui peuvent être exécutés deux fois en toute sécurité. |
+| `delayMs` | `0` | `0`–`604800000` (7 jours) | Attendez ce délai avant que l’exécution devienne éligible. |
+
+```ts
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+ retryLimit: 3,
+ delayMs: 60_000,
+});
+```
+
+
+**La priorité n’est pas encore configurable.** Les jobs mis en file d’attente s’exécutent toujours avec la priorité la plus basse, de sorte que le travail de la plateforme n’est jamais retardé derrière les jobs des applications. Le contrôle de la priorité arrive bientôt.
+
+
+L’exécution mise en file d’attente hérite de l’utilisateur exécutant la fonction qui l’a mise en file d’attente, elle agit donc avec les mêmes autorisations.
+
+## Utilisation : paginer une longue synchronisation
+
+La forme classique est une fonction qui met *elle-même* en file d’attente la prochaine exécution avec le curseur suivant. Chaque exécution traite une page de travail bien à l’intérieur de son propre délai d’expiration, et la chaîne s’arrête lorsqu’il ne reste plus rien.
+
+```ts src/logic-functions/sync-contacts-page.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
+
+const handler = async (params: { cursor?: string }) => {
+ const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
+
+ await importContacts(contacts);
+
+ if (nextCursor) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
+ payload: { cursor: nextCursor },
+ delayMs: 2_000,
+ });
+ }
+
+ return { imported: contacts.length, done: !nextCursor };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: SYNC_CONTACTS_PAGE,
+ name: 'sync-contacts-page',
+ timeoutSeconds: 120,
+ handler,
+});
+```
+
+## Répartition par enregistrement
+
+Quand le travail est naturellement par élément, mettez en file d’attente un job par élément et laissez les workers les traiter en parallèle au lieu de boucler en ligne.
+
+```ts
+const companies = await listCompaniesToEnrich();
+
+await Promise.all(
+ companies.map((company) =>
+ enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANY,
+ payload: { companyId: company.id },
+ retryLimit: 2,
+ }),
+ ),
+);
+```
+
+## Bonnes pratiques pour les tâches de longue durée
+
+Deux règles couvrent presque toutes les longues tâches : **utilisez la récursion au lieu de boucles** et **traitez un bloc borné par exécution**.
+
+Une exécution qui essaie de tout faire est un mode d’échec — elle atteint le délai d’expiration, et avec une nouvelle tentative elle recommence tout depuis zéro. Au lieu de cela, dimensionnez un bloc de manière à ce qu’il se termine confortablement dans `timeoutSeconds`, conservez votre position et mettez en file d’attente l’exécution suivante.
+
+```ts src/logic-functions/enrich-companies-batch.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob, kv } from 'twenty-sdk/logic-function';
+
+const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
+const CHUNK_SIZE = 50;
+
+const handler = async (params: { offset?: number }) => {
+ const offset = params.offset ?? 0;
+ const companies = await listCompaniesToEnrich({
+ offset,
+ limit: CHUNK_SIZE,
+ });
+
+ for (const company of companies) {
+ await enrichCompany(company);
+ }
+
+ await kv.set('enrich:progress', { offset: offset + companies.length });
+
+ if (companies.length === CHUNK_SIZE) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
+ payload: { offset: offset + CHUNK_SIZE },
+ });
+ }
+
+ return { processed: companies.length, done: companies.length < CHUNK_SIZE };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: ENRICH_COMPANIES_BATCH,
+ name: 'enrich-companies-batch',
+ timeoutSeconds: 300,
+ handler,
+});
+```
+
+Ce qui rend cette approche robuste :
+
+* **Dimensionnez le bloc à partir de l’élément le plus lent, pas de la moyenne.** `CHUNK_SIZE × worst-case item time` doit tenir dans `timeoutSeconds` avec une marge de sécurité, sinon la fin d’un bloc est perdue lorsque l’exécution est interrompue.
+* **Rendez la condition de terminaison explicite.** Utilisez la récursion uniquement lorsqu’un bloc complet est revenu. Une chaîne qui s’arrête uniquement sur "no results" continuera indéfiniment si la source renvoie un jour une page courte en cours de route.
+* **Conservez la progression avant de mettre en file d’attente l’exécution suivante,** afin qu’un maillon ayant échoué redémarre au dernier bloc terminé plutôt qu’au début.
+* **Gardez chaque bloc idempotent.** Le retraitement d’un bloc après une nouvelle tentative ne doit pas provoquer une double écriture — indexez les écritures sur l’enregistrement ou l’identifiant externe que vous traitez.
+* **Préférez une chaîne par blocs à un énorme déploiement parallèle** lorsque le travail touche un tiers soumis à des limitations de débit : une chaîne avec `delayMs` se régule elle-même, alors que des milliers de jobs mis en file d’attente d’un coup deviennent tous éligibles immédiatement.
+
+
+Les nouvelles tentatives réexécutent tout le gestionnaire. Gardez les gestionnaires mis en file d’attente idempotents avant de définir `retryLimit` au-dessus de `0`.
+
diff --git a/packages/twenty-docs/l/fr/developers/extend/apps/logic/overview.mdx b/packages/twenty-docs/l/fr/developers/extend/apps/logic/overview.mdx
index dfc9354220..cc1cad30ff 100644
--- a/packages/twenty-docs/l/fr/developers/extend/apps/logic/overview.mdx
+++ b/packages/twenty-docs/l/fr/developers/extend/apps/logic/overview.mdx
@@ -37,6 +37,9 @@ La **couche logique** d’une application Twenty est le code qui *s’exécute*
Conservez l’état entre les exécutions de fonctions logiques — caches, curseurs et revendications inter-espaces de travail.
+
+ Mettez en file d’attente l’exécution d’une fonction logique sur les workers pour dépasser la limite de temps par exécution.
+
## Aperçu des types de déclencheurs
diff --git a/packages/twenty-docs/l/fr/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/fr/developers/self-host/capabilities/upgrade-guide.mdx
index 32691cded6..a088e2e3a7 100644
--- a/packages/twenty-docs/l/fr/developers/self-host/capabilities/upgrade-guide.mdx
+++ b/packages/twenty-docs/l/fr/developers/self-host/capabilities/upgrade-guide.mdx
@@ -25,11 +25,11 @@ Si vous utilisez Docker Compose, suivez ces étapes :
Le serveur exécute automatiquement au démarrage toutes les migrations de mise à niveau requises. Aucune commande manuelle n'est nécessaire.
-## Mises à niveau entre versions (v1.22+)
+## Mises à niveau entre versions (v1.23+)
-À partir de **v1.22**, Twenty prend en charge les mises à niveau entre versions. Vous pouvez passer directement de n'importe quelle version prise en charge à la dernière version sans devoir passer par chaque version intermédiaire.
+À partir de **v1.23**, Twenty prend en charge les mises à niveau entre versions. Vous pouvez passer directement de n'importe quelle version prise en charge à la dernière version sans devoir passer par chaque version intermédiaire.
-Par exemple, la mise à niveau de la v1.22 directement vers la v2.0 est entièrement prise en charge.
+Par exemple, la mise à niveau de la v1.23 directement vers la v2.0 est entièrement prise en charge.
## Mise à niveau vers la v2.5+ — enveloppe de chiffrement au repos
@@ -97,6 +97,6 @@ docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status -
Cela affiche uniquement les espaces de travail en retard ou en échec, ainsi que le message d'erreur pour chaque échec.
-## Avant la v1.22
+## Avant la v1.23
-Si votre instance est antérieure à la v1.22, vous devez effectuer une mise à niveau progressive à travers chaque version majeure marquée (de v1.6 à v1.7, puis de v1.7 à v1.8, et ainsi de suite) jusqu'à atteindre la v1.22. À partir de là, vous pouvez passer directement à la dernière version.
+Si votre instance est antérieure à la v1.23, vous devez effectuer une mise à niveau progressive à travers chaque version majeure marquée (de v1.6 à v1.7, puis de v1.7 à v1.8, et ainsi de suite) jusqu'à atteindre la v1.23. À partir de là, vous pouvez passer directement à la dernière version.
diff --git a/packages/twenty-docs/l/it/developers/extend/apps/logic/background-jobs.mdx b/packages/twenty-docs/l/it/developers/extend/apps/logic/background-jobs.mdx
new file mode 100644
index 0000000000..00481553ef
--- /dev/null
+++ b/packages/twenty-docs/l/it/developers/extend/apps/logic/background-jobs.mdx
@@ -0,0 +1,165 @@
+---
+title: Job in background
+description: Affida il lavoro lungo o soggetto a limitazioni di frequenza ai worker di Twenty accodando un'altra esecuzione di una logic function invece di fare tutto in linea.
+icon: layer-group
+---
+
+L'esecuzione di una logic function è limitata dal suo `timeoutSeconds` (massimo 900 secondi). Qualsiasi attività che non può essere completata in tale intervallo — una risincronizzazione completa, un fan-out per record, un'API di terze parti che applica limitazioni di frequenza — deve essere suddivisa in esecuzioni più piccole.
+
+`enqueueJob` fa esattamente questo: chiede ai worker di Twenty di eseguire in un secondo momento una delle logic function della tua app, in un proprio processo, con il proprio budget di timeout. Il chiamante restituisce immediatamente.
+
+```text
+ ┌─────────────────┐ enqueueJob(...) ┌──────────────┐ ┌────────────────────┐
+ │ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
+ │ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
+ └─────────────────┘ └──────────────┘ └────────────────────┘
+```
+
+## Inserire in coda un'esecuzione
+
+Importa `enqueueJob` da `twenty-sdk/logic-function` e puntalo all'`universalIdentifier` della logic function che vuoi eseguire.
+
+```ts src/logic-functions/sync-all-contacts.ts
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+});
+```
+
+La funzione di destinazione riceve `payload` come argomento del suo handler, esattamente come qualsiasi altro trigger. Deve appartenere alla **stessa applicazione** del chiamante: l'inserimento in coda della funzione di un'altra app viene rifiutato con `Logic function not found`.
+
+
+`enqueueJob` restituisce non appena il job è stato accettato, non quando è stato eseguito. Non restituisce il risultato della destinazione: fai scrivere alla destinazione ciò che produce nel [key-value store](/l/it/developers/extend/apps/logic/key-value-store) o in un record dello spazio di lavoro se hai bisogno di leggerlo di nuovo.
+
+
+## Opzioni del job
+
+| Opzione | Predefinito | Intervallo | Cosa fa |
+| ------------ | ----------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `retryLimit` | `0` | `0`–`10` | Tentativi extra se l'esecuzione genera un'eccezione. Aumenta questo valore solo per gli handler che possono essere eseguiti due volte in sicurezza. |
+| `delayMs` | `0` | `0`–`604800000` (7 giorni) | Attendi questo intervallo prima che l'esecuzione diventi idonea. |
+
+```ts
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+ retryLimit: 3,
+ delayMs: 60_000,
+});
+```
+
+
+**La priorità non è ancora configurabile.** I job in coda vengono sempre eseguiti alla priorità più bassa, così il lavoro della piattaforma non viene mai ritardato rispetto ai job dell'applicazione. Il controllo sulla priorità sarà disponibile a breve.
+
+
+L'esecuzione in coda eredita l'utente attivo della funzione che l'ha messa in coda, quindi agisce con le stesse autorizzazioni.
+
+## Usalo per scorrere una lunga sincronizzazione a pagine
+
+La forma classica è una funzione che mette in coda *se stessa* con il cursore successivo. Ogni esecuzione gestisce una pagina di lavoro ben all'interno del proprio timeout, e la catena si interrompe quando non resta più nulla.
+
+```ts src/logic-functions/sync-contacts-page.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
+
+const handler = async (params: { cursor?: string }) => {
+ const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
+
+ await importContacts(contacts);
+
+ if (nextCursor) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
+ payload: { cursor: nextCursor },
+ delayMs: 2_000,
+ });
+ }
+
+ return { imported: contacts.length, done: !nextCursor };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: SYNC_CONTACTS_PAGE,
+ name: 'sync-contacts-page',
+ timeoutSeconds: 120,
+ handler,
+});
+```
+
+## Suddivisione per record
+
+Quando il lavoro è naturalmente per elemento, metti in coda un job per elemento e lascia che i worker li elaborino in parallelo invece di ciclare in linea.
+
+```ts
+const companies = await listCompaniesToEnrich();
+
+await Promise.all(
+ companies.map((company) =>
+ enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANY,
+ payload: { companyId: company.id },
+ retryLimit: 2,
+ }),
+ ),
+);
+```
+
+## Buone pratiche per il lavoro di lunga durata
+
+Due regole coprono quasi ogni job lungo: **usa la ricorsione invece del ciclo** e **elabora un blocco limitato per esecuzione**.
+
+Un'esecuzione che prova a fare tutto è una modalità di errore: raggiunge il timeout e, con un nuovo tentativo, ricomincia tutto da zero. Invece, dimensiona un blocco in modo che finisca comodamente entro `timeoutSeconds`, conserva la tua posizione e metti in coda l'esecuzione successiva.
+
+```ts src/logic-functions/enrich-companies-batch.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob, kv } from 'twenty-sdk/logic-function';
+
+const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
+const CHUNK_SIZE = 50;
+
+const handler = async (params: { offset?: number }) => {
+ const offset = params.offset ?? 0;
+ const companies = await listCompaniesToEnrich({
+ offset,
+ limit: CHUNK_SIZE,
+ });
+
+ for (const company of companies) {
+ await enrichCompany(company);
+ }
+
+ await kv.set('enrich:progress', { offset: offset + companies.length });
+
+ if (companies.length === CHUNK_SIZE) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
+ payload: { offset: offset + CHUNK_SIZE },
+ });
+ }
+
+ return { processed: companies.length, done: companies.length < CHUNK_SIZE };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: ENRICH_COMPANIES_BATCH,
+ name: 'enrich-companies-batch',
+ timeoutSeconds: 300,
+ handler,
+});
+```
+
+Cosa rende tutto questo solido:
+
+* **Dimensiona il blocco a partire dall'elemento più lento, non dalla media.** `CHUNK_SIZE × worst-case item time` deve rientrare in `timeoutSeconds` con un certo margine, altrimenti la parte finale di un blocco va persa quando l'esecuzione viene interrotta.
+* **Rendi esplicita la condizione di terminazione.** Usa la ricorsione solo finché torna un blocco completo. Una catena che si interrompe solo su "nessun risultato" continuerà all'infinito se la fonte restituisce mai una pagina corta a metà percorso.
+* **Conserva i progressi prima di mettere in coda l'esecuzione successiva,** così un anello fallito riparte dall'ultimo blocco completato invece che dall'inizio.
+* **Mantieni ogni blocco idempotente.** Rielaborare un blocco dopo un ritentativo non deve causare scritture duplicate: usa chiavi di scrittura sul record o sull'id esterno che stai elaborando.
+* **Preferisci una catena a blocchi a un'unica enorme suddivisione parallela** quando il lavoro colpisce una terza parte con limitazione di velocità: una catena con `delayMs` si autoregola, mentre migliaia di job messi in coda in una volta sola diventano tutti idonei immediatamente.
+
+
+I ritentativi rieseguono l'intero handler. Mantieni gli handler in coda idempotenti prima di impostare `retryLimit` sopra `0`.
+
diff --git a/packages/twenty-docs/l/it/developers/extend/apps/logic/overview.mdx b/packages/twenty-docs/l/it/developers/extend/apps/logic/overview.mdx
index 64c38304e0..fc7d265fc6 100644
--- a/packages/twenty-docs/l/it/developers/extend/apps/logic/overview.mdx
+++ b/packages/twenty-docs/l/it/developers/extend/apps/logic/overview.mdx
@@ -37,6 +37,9 @@ Il **livello logico** di un'app Twenty è il codice che *viene eseguito* — han
Mantieni lo stato tra le esecuzioni delle funzioni di logica — cache, cursori e asserzioni tra spazi di lavoro.
+
+ Metti in coda l’esecuzione di una funzione di logica sui worker per superare il timeout per esecuzione.
+
## Tipi di trigger in sintesi
diff --git a/packages/twenty-docs/l/it/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/it/developers/self-host/capabilities/upgrade-guide.mdx
index 9300235f0d..ede8001ba7 100644
--- a/packages/twenty-docs/l/it/developers/self-host/capabilities/upgrade-guide.mdx
+++ b/packages/twenty-docs/l/it/developers/self-host/capabilities/upgrade-guide.mdx
@@ -25,11 +25,11 @@ Se utilizzi Docker Compose, segui questi passaggi:
All'avvio, il server esegue automaticamente tutte le migrazioni di aggiornamento necessarie. Non è necessario alcun comando manuale.
-## Aggiornamenti tra versioni (v1.22+)
+## Aggiornamenti tra versioni (v1.23+)
-A partire da **v1.22**, Twenty supporta gli aggiornamenti tra versioni. Puoi passare direttamente da qualsiasi versione supportata all'ultima versione senza dover attraversare ogni versione intermedia.
+A partire da **v1.23**, Twenty supporta gli aggiornamenti tra versioni. Puoi passare direttamente da qualsiasi versione supportata all'ultima versione senza dover attraversare ogni versione intermedia.
-Ad esempio, l'aggiornamento da v1.22 direttamente a v2.0 è pienamente supportato.
+Ad esempio, l'aggiornamento da v1.23 direttamente a v2.0 è pienamente supportato.
## Aggiornamento alla v2.5+ — envelope di crittografia dei dati a riposo
@@ -97,6 +97,6 @@ docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status -
Mostra solo gli spazi di lavoro arretrati o non riusciti, insieme al messaggio di errore per ciascun errore.
-## Prima della v1.22
+## Prima della v1.23
-Se la tua istanza è precedente alla v1.22, devi eseguire l'aggiornamento in modo incrementale attraverso ogni versione maggiore contrassegnata (da v1.6 a v1.7, poi da v1.7 a v1.8 e così via) fino a raggiungere la v1.22. Da lì, puoi passare direttamente all'ultima versione.
+Se la tua istanza è precedente alla v1.23, devi eseguire l'aggiornamento in modo incrementale attraverso ogni versione maggiore contrassegnata (da v1.6 a v1.7, poi da v1.7 a v1.8 e così via) fino a raggiungere la v1.23. Da lì, puoi passare direttamente all'ultima versione.
diff --git a/packages/twenty-docs/l/ja/developers/extend/apps/logic/background-jobs.mdx b/packages/twenty-docs/l/ja/developers/extend/apps/logic/background-jobs.mdx
new file mode 100644
index 0000000000..16dc47b832
--- /dev/null
+++ b/packages/twenty-docs/l/ja/developers/extend/apps/logic/background-jobs.mdx
@@ -0,0 +1,165 @@
+---
+title: バックグラウンドジョブ
+description: 長時間かかる処理やレート制限される処理は、すべてをインラインで実行するのではなく、別のロジック関数の実行をキューに追加して Twenty のワーカーに渡してください。
+icon: layer-group
+---
+
+ロジック関数の実行は、その `timeoutSeconds`(最大 900 秒)によって制限されます。 その時間内に完了できない処理 — フル再同期、レコードごとのファンアウト、レート制限を課してくるサードパーティ API など — は、より小さい実行単位に分割する必要があります。
+
+`enqueueJob` はまさにそれを行います。Twenty のワーカーに対して、あなたのアプリのロジック関数の 1 つを、独立したプロセスと独自のタイムアウト枠で後から実行するよう依頼します。 呼び出し元はすぐに戻ります。
+
+```text
+ ┌─────────────────┐ enqueueJob(...) ┌──────────────┐ ┌────────────────────┐
+ │ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
+ │ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
+ └─────────────────┘ └──────────────┘ └────────────────────┘
+```
+
+## 実行をエンキューする
+
+`twenty-sdk/logic-function` から `enqueueJob` をインポートし、実行したいロジック関数の `universalIdentifier` を指定します。
+
+```ts src/logic-functions/sync-all-contacts.ts
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+});
+```
+
+対象の関数は、他のトリガーとまったく同様に、ハンドラーの引数として `payload` を受け取ります。 対象のロジック関数は呼び出し元と**同じアプリケーション**に属している必要があります。別のアプリの関数をエンキューしようとすると、`Logic function not found` で拒否されます。
+
+
+`enqueueJob` は、ジョブが受け付けられた時点ですぐに戻り、ジョブが実行されたタイミングでは戻りません。 対象の結果は返しません。結果を後から読み戻す必要がある場合は、対象側で [key-value store](/l/ja/developers/extend/apps/logic/key-value-store) かワークスペースレコードに出力を書き込ませてください。
+
+
+## ジョブオプション
+
+| オプション | デフォルト | 範囲 | 機能 |
+| ------------ | ----- | ------------------------ | ---------------------------------------------------------- |
+| `retryLimit` | `0` | `0`–`10` | 実行が例外をスローした場合の追加試行回数。 2 回実行しても安全なハンドラーに対してのみ、この値を増やしてください。 |
+| `delayMs` | `0` | `0`–`604800000` (7 days) | この時間が経過すると、その実行は実行可能になります。 |
+
+```ts
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+ retryLimit: 3,
+ delayMs: 60_000,
+});
+```
+
+
+**優先度はまだ設定できません。** エンキューされたジョブは常に最も低い優先度で実行されるため、プラットフォームの処理がアプリケーションジョブによって遅延することはありません。 優先度を制御できる機能は近日中に提供予定です。
+
+
+キューに入れられた実行は、それをエンキューした関数の実行ユーザーを引き継ぐため、同じ権限で動作します。
+
+## 使いどころ: 長い同期処理をページングする
+
+典型的なパターンは、次のカーソルを指定して*自分自身*をエンキューする関数です。 各実行は、自身のタイムアウト内に十分収まる 1 ページ分の処理だけを行い、処理対象がなくなったところでチェーンが停止します。
+
+```ts src/logic-functions/sync-contacts-page.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
+
+const handler = async (params: { cursor?: string }) => {
+ const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
+
+ await importContacts(contacts);
+
+ if (nextCursor) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
+ payload: { cursor: nextCursor },
+ delayMs: 2_000,
+ });
+ }
+
+ return { imported: contacts.length, done: !nextCursor };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: SYNC_CONTACTS_PAGE,
+ name: 'sync-contacts-page',
+ timeoutSeconds: 120,
+ handler,
+});
+```
+
+## レコードごとにファンアウトする
+
+処理単位が自然にアイテムごとになる場合は、アイテムごとに 1 つのジョブをエンキューし、インラインでループするのではなく、ワーカーに並列で処理させます。
+
+```ts
+const companies = await listCompaniesToEnrich();
+
+await Promise.all(
+ companies.map((company) =>
+ enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANY,
+ payload: { companyId: company.id },
+ retryLimit: 2,
+ }),
+ ),
+);
+```
+
+## 長時間実行タスクのためのベストプラクティス
+
+ほとんどの長時間ジョブには、次の 2 つのルールで対応できます。**ループの代わりに再帰させること**、そして **1 回の実行で処理するチャンクを制限すること**。
+
+すべてを 1 回の実行で片付けようとするのは失敗パターンです。タイムアウトに達し、リトライがかかると処理全体をまた最初からやり直すことになります。 代わりに、1 つのチャンクを `timeoutSeconds` 内に余裕をもって完了するサイズにし、現在位置を永続化してから、次の実行をエンキューします。
+
+```ts src/logic-functions/enrich-companies-batch.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob, kv } from 'twenty-sdk/logic-function';
+
+const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
+const CHUNK_SIZE = 50;
+
+const handler = async (params: { offset?: number }) => {
+ const offset = params.offset ?? 0;
+ const companies = await listCompaniesToEnrich({
+ offset,
+ limit: CHUNK_SIZE,
+ });
+
+ for (const company of companies) {
+ await enrichCompany(company);
+ }
+
+ await kv.set('enrich:progress', { offset: offset + companies.length });
+
+ if (companies.length === CHUNK_SIZE) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
+ payload: { offset: offset + CHUNK_SIZE },
+ });
+ }
+
+ return { processed: companies.length, done: companies.length < CHUNK_SIZE };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: ENRICH_COMPANIES_BATCH,
+ name: 'enrich-companies-batch',
+ timeoutSeconds: 300,
+ handler,
+});
+```
+
+これが成立する理由:
+
+* **チャンクのサイズは平均ではなく最も遅いアイテムに合わせましょう。** `CHUNK_SIZE × 最悪ケースのアイテム処理時間` が、余裕をもって `timeoutSeconds` に収まる必要があります。そうでないと、実行が打ち切られた際にチャンクの末尾が失われます。
+* **終了条件を明示的にします。** チャンクがフルで返ってきた間だけ再帰させます。 「結果が 0 件」のみで停止するチェーンは、途中で短いページが返されることがあるソースに対しては、永久に動き続けてしまいます。
+* **次の実行をエンキューする前に進捗を永続化**しておきます。そうすることで、どこかのリンクが失敗しても、最初からではなく最後に完了したチャンクから再開できます。
+* **各チャンクはべき等に保ちます。** リトライ後に 1 つのチャンクを再処理しても二重書き込みにならないように、処理対象のレコードや外部 ID をキーにして書き込みを行ってください。
+* **レート制限のあるサードパーティを相手にする場合は、巨大な 1 回のファンアウトよりもチャンク化したチェーンを優先**します。`delayMs` を設定したチェーンは自分でペース配分しますが、何千ものジョブを一度にエンキューすると、すべてが即座にキュー対象になってしまいます。
+
+
+リトライではハンドラー全体が再実行されます。 `retryLimit` を `0` より大きく設定する前に、エンキューされるハンドラーをべき等に保ってください。
+
diff --git a/packages/twenty-docs/l/ja/developers/extend/apps/logic/overview.mdx b/packages/twenty-docs/l/ja/developers/extend/apps/logic/overview.mdx
index 479a38f5de..d7355ad2bc 100644
--- a/packages/twenty-docs/l/ja/developers/extend/apps/logic/overview.mdx
+++ b/packages/twenty-docs/l/ja/developers/extend/apps/logic/overview.mdx
@@ -37,6 +37,9 @@ Twenty アプリの **ロジックレイヤー** は、*実行される* コー
ロジック関数の実行間で状態を永続化します ― キャッシュ、カーソル、ワークスペース間のクレーム。
+
+ ワーカー上でロジック関数の実行をキューに入れて、実行ごとのタイムアウト制限を回避します。
+
## トリガータイプの概要
diff --git a/packages/twenty-docs/l/ja/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/ja/developers/self-host/capabilities/upgrade-guide.mdx
index 86015b781e..6fd7cc649f 100644
--- a/packages/twenty-docs/l/ja/developers/self-host/capabilities/upgrade-guide.mdx
+++ b/packages/twenty-docs/l/ja/developers/self-host/capabilities/upgrade-guide.mdx
@@ -25,11 +25,11 @@ Docker Composeを使用している場合、次の手順に従います:
サーバーは起動時に、必要なアップグレード用のマイグレーションを自動的に実行します。 手動のコマンドは不要です。
-## クロスバージョンのアップグレード (v1.22+)
+## クロスバージョンのアップグレード (v1.23+)
-**v1.22** 以降、Twenty はクロスバージョンのアップグレードをサポートします。 サポートされている任意のバージョンから、中間バージョンを段階的に経ることなく、最新リリースへ直接移行できます。
+**v1.23** 以降、Twenty はクロスバージョンのアップグレードをサポートします。 サポートされている任意のバージョンから、中間バージョンを段階的に経ることなく、最新リリースへ直接移行できます。
-たとえば、v1.22 から v2.0 へ直接アップグレードすることが完全にサポートされています。
+たとえば、v1.23 から v2.0 へ直接アップグレードすることが完全にサポートされています。
## v2.5 以降へのアップグレード — 保存データ暗号化エンベロープ
@@ -97,6 +97,6 @@ docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status -
これにより、遅れている、または失敗しているワークスペースのみが表示され、各失敗のエラーメッセージも併せて表示されます。
-## v1.22 以前
+## v1.23 以前
-インスタンスが v1.22 より前の場合は、v1.22 に到達するまで、各メジャーのタグ付きバージョンを順に(v1.6 から v1.7、次に v1.7 から v1.8、…)段階的にアップグレードする必要があります。 そこからは、最新バージョンに直接アップグレードできます。
+インスタンスが v1.23 より前の場合は、v1.23 に到達するまで、各メジャーのタグ付きバージョンを順に(v1.6 から v1.7、次に v1.7 から v1.8、…)段階的にアップグレードする必要があります。 そこからは、最新バージョンに直接アップグレードできます。
diff --git a/packages/twenty-docs/l/ko/developers/extend/apps/logic/background-jobs.mdx b/packages/twenty-docs/l/ko/developers/extend/apps/logic/background-jobs.mdx
new file mode 100644
index 0000000000..8470cfacb2
--- /dev/null
+++ b/packages/twenty-docs/l/ko/developers/extend/apps/logic/background-jobs.mdx
@@ -0,0 +1,165 @@
+---
+title: 백그라운드 작업
+description: 긴 시간이 걸리거나 속도 제한이 걸린 작업은 모두 인라인으로 처리하지 말고, 또 다른 로직 함수 실행을 큐에 넣어 Twenty 워커에 위임하세요.
+icon: layer-group
+---
+
+로직 함수 실행은 `timeoutSeconds`(최대 900초)로 제한됩니다. 해당 시간 안에 끝나지 않는 모든 작업(전체 재동기화, 레코드별 팬아웃, 속도 제한이 걸린 서드파티 API 등)은 더 작은 실행 단위로 나눠야 합니다.
+
+`enqueueJob`은 바로 그 일을 합니다. Twenty 워커에게 나중에 별도 프로세스와 별도의 타임아웃 한도 내에서 앱의 로직 함수 중 하나를 실행해 달라고 요청합니다. 호출은 즉시 반환됩니다.
+
+```text
+ ┌─────────────────┐ enqueueJob(...) ┌──────────────┐ ┌────────────────────┐
+ │ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
+ │ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
+ └─────────────────┘ └──────────────┘ └────────────────────┘
+```
+
+## 실행 대기열에 추가하기
+
+`twenty-sdk/logic-function`에서 `enqueueJob`을 가져와서, 실행하려는 로직 함수의 `universalIdentifier`를 가리키도록 설정하세요.
+
+```ts src/logic-functions/sync-all-contacts.ts
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+});
+```
+
+대상 함수는 다른 트리거와 정확히 동일하게, 핸들러 인자로 `payload`를 전달받습니다. 대상 함수는 호출자와 **동일한 애플리케이션**에 속해야 합니다. 다른 앱의 함수를 대기열에 추가하려고 하면 `Logic function not found` 오류와 함께 거부됩니다.
+
+
+`enqueueJob`은 작업이 실행을 마쳤을 때가 아니라, 작업이 수락되는 즉시 반환됩니다. 대상 함수의 결과를 반환하지는 않습니다. 결과를 다시 읽어와야 한다면, 대상 함수가 생성한 내용을 [키-값 스토어](/l/ko/developers/extend/apps/logic/key-value-store) 또는 워크스페이스 레코드에 기록하도록 하세요.
+
+
+## 작업 옵션
+
+| 옵션 | 기본값 | 범위 | 하는 일 |
+| ------------ | --- | ------------------------ | -------------------------------------------------------------- |
+| `retryLimit` | `0` | `0`–`10` | 실행이 예외를 던졌을 때의 추가 재시도 횟수입니다. 두 번 실행해도 안전한 핸들러에 대해서만 이 값을 올리세요. |
+| `delayMs` | `0` | `0`–`604800000` (7 days) | 실행이 가능해지기 전까지 이만큼 대기합니다. |
+
+```ts
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+ retryLimit: 3,
+ delayMs: 60_000,
+});
+```
+
+
+**우선순위는 아직 설정할 수 없습니다.** 대기열에 추가된 작업은 항상 가장 낮은 우선순위로 실행되므로, 플랫폼 작업이 애플리케이션 작업보다 뒤로 밀려 지연되는 일은 없습니다. 우선순위를 제어하는 기능은 곧 제공될 예정입니다.
+
+
+대기열에 추가된 실행은 이를 대기열에 추가한 함수의 실행 사용자를 상속하므로, 동일한 권한으로 동작합니다.
+
+## 사용 예: 긴 동기화를 페이지 단위로 처리하기
+
+전형적인 형태는, 다음 커서를 사용해 *자기 자신*을 다시 대기열에 추가하는 함수입니다. 각 실행은 자신의 타임아웃 안에서 한 페이지 분량의 작업만 처리하고, 더 이상 처리할 것이 없으면 이 체인은 멈춥니다.
+
+```ts src/logic-functions/sync-contacts-page.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
+
+const handler = async (params: { cursor?: string }) => {
+ const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
+
+ await importContacts(contacts);
+
+ if (nextCursor) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
+ payload: { cursor: nextCursor },
+ delayMs: 2_000,
+ });
+ }
+
+ return { imported: contacts.length, done: !nextCursor };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: SYNC_CONTACTS_PAGE,
+ name: 'sync-contacts-page',
+ timeoutSeconds: 120,
+ handler,
+});
+```
+
+## 레코드별로 팬아웃하기
+
+작업이 본질적으로 항목 단위일 때는, 항목마다 하나의 작업을 대기열에 추가하고, 워커들이 인라인 루프를 도는 대신 병렬로 처리하도록 하세요.
+
+```ts
+const companies = await listCompaniesToEnrich();
+
+await Promise.all(
+ companies.map((company) =>
+ enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANY,
+ payload: { companyId: company.id },
+ retryLimit: 2,
+ }),
+ ),
+);
+```
+
+## 장시간 실행 작업을 위한 모범 사례
+
+대부분의 장시간 작업은 두 가지 규칙으로 다룰 수 있습니다: **루프 대신 재귀를 사용**하고, **실행마다 한정된 크기의 청크를 처리**하세요.
+
+한 번에 모든 것을 처리하려는 실행은 실패 패턴입니다. 타임아웃에 걸리고, 재시도가 발생하면 전체 작업을 다시 처음부터 시작합니다. 대신, 하나의 청크가 `timeoutSeconds` 안에 여유 있게 끝나도록 크기를 잡고, 현재 위치를 저장한 뒤 다음 실행을 대기열에 추가하세요.
+
+```ts src/logic-functions/enrich-companies-batch.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob, kv } from 'twenty-sdk/logic-function';
+
+const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
+const CHUNK_SIZE = 50;
+
+const handler = async (params: { offset?: number }) => {
+ const offset = params.offset ?? 0;
+ const companies = await listCompaniesToEnrich({
+ offset,
+ limit: CHUNK_SIZE,
+ });
+
+ for (const company of companies) {
+ await enrichCompany(company);
+ }
+
+ await kv.set('enrich:progress', { offset: offset + companies.length });
+
+ if (companies.length === CHUNK_SIZE) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
+ payload: { offset: offset + CHUNK_SIZE },
+ });
+ }
+
+ return { processed: companies.length, done: companies.length < CHUNK_SIZE };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: ENRICH_COMPANIES_BATCH,
+ name: 'enrich-companies-batch',
+ timeoutSeconds: 300,
+ handler,
+});
+```
+
+이 접근이 유효한 이유:
+
+* **청크 크기는 평균이 아니라 가장 느린 항목을 기준으로 정하세요.** `CHUNK_SIZE × 최악의 항목 처리 시간`은 여유를 두고 `timeoutSeconds` 안에 들어와야 합니다. 그렇지 않으면 실행이 중단될 때 청크의 끝부분이 유실됩니다.
+* **종료 조건을 명시적으로 만드세요.** 가득 찬 청크가 반환되었을 때만 재귀적으로 호출하세요. "결과 없음"만을 기준으로 멈추는 체인은, 소스가 중간에 짧은 페이지를 한 번이라도 반환하면 영원히 계속될 수 있습니다.
+* **다음 실행을 대기열에 추가하기 전에 진행 상태를 저장**해서, 실패한 링크가 처음부터가 아니라 마지막으로 완료된 청크부터 다시 시작하도록 하세요.
+* **각 청크는 멱등성을 유지하세요.** 재시도 후에 하나의 청크를 다시 처리하더라도 중복 기록이 발생하지 않도록, 처리 중인 레코드나 외부 ID를 기준으로 쓰기를 수행해야 합니다.
+* **거대한 한 번의 팬아웃보다 청크 단위 체인을 우선적으로 사용**하세요. 작업이 호출 한도(rate limit)가 있는 서드파티에 도달하는 경우, `delayMs`가 있는 체인은 스스로 속도를 조절하지만, 수천 개의 작업을 한 번에 대기열에 추가하면 모두 즉시 실행 가능 상태가 됩니다.
+
+
+재시도가 발생하면 전체 핸들러가 다시 실행됩니다. `retryLimit`을 `0`보다 크게 설정하기 전에, 대기열에 추가되는 핸들러가 멱등성을 갖추도록 유지하세요.
+
diff --git a/packages/twenty-docs/l/ko/developers/extend/apps/logic/overview.mdx b/packages/twenty-docs/l/ko/developers/extend/apps/logic/overview.mdx
index bb2faacc06..8a1031e321 100644
--- a/packages/twenty-docs/l/ko/developers/extend/apps/logic/overview.mdx
+++ b/packages/twenty-docs/l/ko/developers/extend/apps/logic/overview.mdx
@@ -37,6 +37,9 @@ Twenty 앱의 **로직 계층**은 *실행되는* 코드로, HTTP 요청, 크론
로직 함수 실행 사이에 상태를 유지합니다 — 캐시, 커서, 그리고 워크스페이스 간 클레임.
+
+ 작업자에서 논리 함수를 실행하도록 대기열에 넣어, 실행당 제한 시간을 우회하세요.
+
## 한눈에 보는 트리거 유형
diff --git a/packages/twenty-docs/l/ko/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/ko/developers/self-host/capabilities/upgrade-guide.mdx
index 196bd44b4b..2b9a377bac 100644
--- a/packages/twenty-docs/l/ko/developers/self-host/capabilities/upgrade-guide.mdx
+++ b/packages/twenty-docs/l/ko/developers/self-host/capabilities/upgrade-guide.mdx
@@ -25,11 +25,11 @@ Docker Compose를 사용하는 경우 다음 단계를 따르십시오:
서버는 시작 시 필요한 모든 업그레이드 마이그레이션을 자동으로 실행합니다. 수동 명령은 필요하지 않습니다.
-## 교차 버전 업그레이드(v1.22+)
+## 교차 버전 업그레이드(v1.23+)
-**v1.22**부터 Twenty는 교차 버전 업그레이드를 지원합니다. 지원되는 어떤 버전에서든 각 중간 버전을 거치지 않고 최신 릴리스로 바로 이동할 수 있습니다.
+**v1.23**부터 Twenty는 교차 버전 업그레이드를 지원합니다. 지원되는 어떤 버전에서든 각 중간 버전을 거치지 않고 최신 릴리스로 바로 이동할 수 있습니다.
-예를 들어, v1.22에서 v2.0으로 바로 업그레이드하는 것이 완전히 지원됩니다.
+예를 들어, v1.23에서 v2.0으로 바로 업그레이드하는 것이 완전히 지원됩니다.
## v2.5+로 업그레이드 — 저장 상태 암호화 엔벨로프
@@ -97,6 +97,6 @@ docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status -
이는 뒤처졌거나 실패한 워크스페이스만 표시하고 각 실패에 대한 오류 메시지도 함께 표시합니다.
-## v1.22 이전
+## v1.23 이전
-인스턴스가 v1.22보다 오래된 경우, v1.22에 도달할 때까지 각 주요 태그 버전(v1.6에서 v1.7, 이어서 v1.7에서 v1.8 등)을 순차적으로 업그레이드해야 합니다. 그 이후에는 최신 버전으로 바로 업그레이드할 수 있습니다.
+인스턴스가 v1.23보다 오래된 경우, v1.23에 도달할 때까지 각 주요 태그 버전(v1.6에서 v1.7, 이어서 v1.7에서 v1.8 등)을 순차적으로 업그레이드해야 합니다. 그 이후에는 최신 버전으로 바로 업그레이드할 수 있습니다.
diff --git a/packages/twenty-docs/l/ro/developers/extend/apps/logic/background-jobs.mdx b/packages/twenty-docs/l/ro/developers/extend/apps/logic/background-jobs.mdx
new file mode 100644
index 0000000000..0b97d4cc8e
--- /dev/null
+++ b/packages/twenty-docs/l/ro/developers/extend/apps/logic/background-jobs.mdx
@@ -0,0 +1,165 @@
+---
+title: Sarcini în fundal
+description: Predă munca de durată sau supusă limitării ratei către lucrătorii Twenty, punând în coadă o altă execuție a unei funcții logice, în loc să rulezi totul inline.
+icon: layer-group
+---
+
+O rulare a unei funcții de logică este limitată de `timeoutSeconds` (maximum 900 de secunde). Orice lucru care nu poate fi finalizat în acel interval — o resincronizare completă, un fan-out per înregistrare, un API de la o terță parte care aplică limitare de rată — trebuie împărțit în execuții mai mici.
+
+`enqueueJob` face exact asta: le cere lucrătorilor Twenty să ruleze una dintre funcțiile de logică ale aplicației tale mai târziu, în propriul său proces, cu propriul său buget de timeout. Apelantul returnează imediat.
+
+```text
+ ┌─────────────────┐ enqueueJob(...) ┌──────────────┐ ┌────────────────────┐
+ │ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
+ │ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
+ └─────────────────┘ └──────────────┘ └────────────────────┘
+```
+
+## Pune în coadă o rulare
+
+Importă `enqueueJob` din `twenty-sdk/logic-function` și indică-l către `universalIdentifier` al funcției logice pe care vrei să o rulezi.
+
+```ts src/logic-functions/sync-all-contacts.ts
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+});
+```
+
+Funcția țintă primește `payload` ca argument al handlerului, exact ca orice alt declanșator. Ea trebuie să aparțină **aceleiași aplicații** ca apelantul — punerea în coadă a funcției altei aplicații este respinsă cu `Logic function not found`.
+
+
+`enqueueJob` revine imediat ce jobul este acceptat, nu când a fost rulat. Nu întoarce rezultatul țintei — fă ca ținta să scrie ce produce în [magazinul cheie-valoare](/l/ro/developers/extend/apps/logic/key-value-store) sau într-o înregistrare din spațiul de lucru dacă trebuie să îl citești ulterior.
+
+
+## Opțiuni job
+
+| Opțiune | Implicit | Interval | Ce face |
+| ------------ | -------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
+| `retryLimit` | `0` | `0`–`10` | Încercări suplimentare dacă rularea aruncă o eroare. Crește această valoare numai pentru handleri care sunt siguri de rulat de două ori. |
+| `delayMs` | `0` | `0`–`604800000` (7 zile) | Așteaptă atât timp înainte ca rularea să devină eligibilă. |
+
+```ts
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+ retryLimit: 3,
+ delayMs: 60_000,
+});
+```
+
+
+**Prioritatea nu este încă configurabilă.** Joburile puse în coadă rulează întotdeauna cu cea mai joasă prioritate, astfel încât activitatea platformei să nu fie niciodată întârziată de joburile aplicației. Controlul asupra priorității va fi disponibil în curând.
+
+
+Rularea pusă în coadă moștenește utilizatorul activ al funcției care a pus-o în coadă, astfel încât acționează cu aceleași permisiuni.
+
+## Folosește-l: parcurge pe pagini o sincronizare lungă
+
+Forma clasică este o funcție care pune în coadă *ea însăși* cu următorul cursor. Fiecare rulare procesează o pagină de lucru bine în interiorul propriului timeout, iar lanțul se oprește când nu mai rămâne nimic.
+
+```ts src/logic-functions/sync-contacts-page.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
+
+const handler = async (params: { cursor?: string }) => {
+ const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
+
+ await importContacts(contacts);
+
+ if (nextCursor) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
+ payload: { cursor: nextCursor },
+ delayMs: 2_000,
+ });
+ }
+
+ return { imported: contacts.length, done: !nextCursor };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: SYNC_CONTACTS_PAGE,
+ name: 'sync-contacts-page',
+ timeoutSeconds: 120,
+ handler,
+});
+```
+
+## Dispersare pe înregistrare
+
+Când munca este în mod natural pe element, pune în coadă un job per element și lasă workerii să le proceseze în paralel în loc să iterezi inline.
+
+```ts
+const companies = await listCompaniesToEnrich();
+
+await Promise.all(
+ companies.map((company) =>
+ enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANY,
+ payload: { companyId: company.id },
+ retryLimit: 2,
+ }),
+ ),
+);
+```
+
+## Bune practici pentru muncă de lungă durată
+
+Două reguli acoperă aproape orice job lung: folosește recursie în locul buclelor și procesează un segment limitat per rulare.
+
+O rulare care încearcă să facă totul este modul de eșec — atinge timeout-ul, iar la o reîncercare începe din nou totul de la zero. În schimb, dimensionează un segment astfel încât să se termine confortabil în interiorul `timeoutSeconds`, persistă-ți poziția și pune în coadă următoarea rulare.
+
+```ts src/logic-functions/enrich-companies-batch.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob, kv } from 'twenty-sdk/logic-function';
+
+const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
+const CHUNK_SIZE = 50;
+
+const handler = async (params: { offset?: number }) => {
+ const offset = params.offset ?? 0;
+ const companies = await listCompaniesToEnrich({
+ offset,
+ limit: CHUNK_SIZE,
+ });
+
+ for (const company of companies) {
+ await enrichCompany(company);
+ }
+
+ await kv.set('enrich:progress', { offset: offset + companies.length });
+
+ if (companies.length === CHUNK_SIZE) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
+ payload: { offset: offset + CHUNK_SIZE },
+ });
+ }
+
+ return { processed: companies.length, done: companies.length < CHUNK_SIZE };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: ENRICH_COMPANIES_BATCH,
+ name: 'enrich-companies-batch',
+ timeoutSeconds: 300,
+ handler,
+});
+```
+
+Ce îl face robust:
+
+* **Dimensionează segmentul pornind de la cel mai lent element, nu de la medie.** `CHUNK_SIZE × worst-case item time` trebuie să încapă în `timeoutSeconds` cu marjă, altfel „coada” unui segment se pierde când rularea este întreruptă.
+* **Fă condiția de terminare explicită.** Apelează recursiv doar cât timp a revenit un segment complet. Un lanț care se oprește doar pe baza „niciun rezultat” va continua la nesfârșit dacă sursa returnează vreodată o pagină scurtă la mijloc.
+* **Păstrează progresul înainte de a pune în coadă următoarea rulare**, astfel încât o verigă eșuată să repornească de la ultimul segment finalizat în loc de la început.
+* **Păstrează fiecare segment idempotent.** Reprocesarea unui segment după o reîncercare nu trebuie să ducă la scrieri duble — leagă scrierile de înregistrarea sau ID-ul extern pe care îl procesezi.
+* **Preferă un lanț segmentat în locul unei dispersări uriașe** atunci când munca lovește o terță parte cu limită de rată: un lanț cu `delayMs` își dozează singur ritmul, în timp ce mii de joburi puse în coadă deodată devin toate eligibile imediat.
+
+
+Reîncercările rulează din nou întregul handler. Păstrează handlerii puși în coadă idempotenți înainte de a seta `retryLimit` peste `0`.
+
diff --git a/packages/twenty-docs/l/ro/developers/extend/apps/logic/overview.mdx b/packages/twenty-docs/l/ro/developers/extend/apps/logic/overview.mdx
index 431f78f684..799a5f0b49 100644
--- a/packages/twenty-docs/l/ro/developers/extend/apps/logic/overview.mdx
+++ b/packages/twenty-docs/l/ro/developers/extend/apps/logic/overview.mdx
@@ -37,6 +37,9 @@ icon: bolt
Păstrează starea între execuțiile funcțiilor de logică — cache-uri, cursoare și revendicări între spații de lucru.
+
+ Pune în coadă o execuție de funcție logic pe workeri pentru a depăși limita de timp per rulare.
+
## Tipuri de declanșatoare, dintr-o privire
diff --git a/packages/twenty-docs/l/ro/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/ro/developers/self-host/capabilities/upgrade-guide.mdx
index 632284bf3d..e17b7bc5ee 100644
--- a/packages/twenty-docs/l/ro/developers/self-host/capabilities/upgrade-guide.mdx
+++ b/packages/twenty-docs/l/ro/developers/self-host/capabilities/upgrade-guide.mdx
@@ -25,11 +25,11 @@ Dacă utilizați Docker Compose, urmați acești pași:
Serverul rulează automat toate migrațiile de actualizare necesare la pornire. Nu este necesară nicio comandă manuală.
-## Actualizări între versiuni (v1.22+)
+## Actualizări între versiuni (v1.23+)
-Începând cu **v1.22**, Twenty acceptă actualizări între versiuni. Puteți trece direct de la orice versiune acceptată la cea mai recentă versiune, fără a parcurge fiecare versiune intermediară.
+Începând cu **v1.23**, Twenty suportă actualizări între versiuni. Puteți trece direct de la orice versiune acceptată la cea mai recentă versiune, fără a parcurge fiecare versiune intermediară.
-De exemplu, actualizarea de la v1.22 direct la v2.0 este pe deplin acceptată.
+De exemplu, actualizarea de la v1.23 direct la v2.0 este pe deplin suportată.
## Actualizare la v2.5+ — înveliș de criptare pentru date în repaus
@@ -97,6 +97,6 @@ docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status -
Aceasta afișează doar spațiile de lucru care sunt rămase în urmă sau au eșuat, împreună cu mesajul de eroare pentru fiecare eșec.
-## Înainte de v1.22
+## Înainte de v1.23
-Dacă instanța dvs. este mai veche decât v1.22, trebuie să actualizați incremental prin fiecare versiune majoră etichetată (de la v1.6 la v1.7, apoi de la v1.7 la v1.8 și așa mai departe) până ajungeți la v1.22. De acolo, puteți sări direct la cea mai recentă versiune.
+Dacă instanța dvs. este mai veche decât v1.23, trebuie să actualizați incremental prin fiecare versiune majoră etichetată (de la v1.6 la v1.7, apoi de la v1.7 la v1.8 și așa mai departe) până ajungeți la v1.23. De acolo, puteți sări direct la cea mai recentă versiune.
diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/logic/background-jobs.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/logic/background-jobs.mdx
new file mode 100644
index 0000000000..2aeff76738
--- /dev/null
+++ b/packages/twenty-docs/l/tr/developers/extend/apps/logic/background-jobs.mdx
@@ -0,0 +1,165 @@
+---
+title: Arka Plan İşleri
+description: Uzun süren veya hız sınırına takılan işleri, her şeyi satır içi yapmak yerine başka bir mantık fonksiyonu çalıştırmasını kuyruğa alarak Twenty işleyicilerine devredin.
+icon: layer-group
+---
+
+Bir mantık fonksiyonu çalıştırması, `timeoutSeconds` değeriyle sınırlandırılır (en fazla 900 saniye). Bu sürede tamamlanamayan her şey — tam bir yeniden eşitleme, kayıt başına dağıtım, sizi hız sınırına takan üçüncü taraf bir API — daha küçük çalıştırmalara bölünmek zorundadır.
+
+`enqueueJob` tam olarak bunu yapar: Twenty işleyicilerinden, uygulamanızın mantık fonksiyonlarından birini daha sonra, kendi işleminde ve kendi zaman aşımı bütçesiyle çalıştırmasını ister. Çağıran hemen döner.
+
+```text
+ ┌─────────────────┐ enqueueJob(...) ┌──────────────┐ ┌────────────────────┐
+ │ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
+ │ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
+ └─────────────────┘ └──────────────┘ └────────────────────┘
+```
+
+## Bir çalıştırmayı kuyruğa al
+
+`enqueueJob` fonksiyonunu `twenty-sdk/logic-function` içinden içe aktarın ve çalıştırmak istediğiniz mantık fonksiyonunun `universalIdentifier` değerini işaret edin.
+
+```ts src/logic-functions/sync-all-contacts.ts
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+});
+```
+
+Hedef fonksiyon, tıpkı diğer tetikleyicilerde olduğu gibi işleyici argümanı olarak `payload` alır. Çağrıyı yapanla **aynı uygulamaya** ait olmalıdır — başka bir uygulamanın fonksiyonunu kuyruğa alma girişimi `Logic function not found` hatasıyla reddedilir.
+
+
+`enqueueJob`, iş kabul edilir edilmez döner; işin çalışması bitince değil. Hedefin sonucunu döndürmez — çıktısını daha sonra okumak istiyorsanız, hedefin ürettiği veriyi [anahtar-değer deposuna](/l/tr/developers/extend/apps/logic/key-value-store) veya bir çalışma alanı kaydına yazmasını sağlayın.
+
+
+## İş seçenekleri
+
+| Seçenek | Varsayılan | Aralık | Ne yapar |
+| ------------ | ---------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
+| `retryLimit` | `0` | `0`–`10` | Çalıştırma bir hata fırlatırsa ekstra deneme sayısı. Bunu yalnızca iki kez çalıştırılması güvenli olan işleyiciler için artırın. |
+| `delayMs` | `0` | `0`–`604800000` (7 gün) | Çalıştırma uygun hale gelmeden önce bu kadar süre bekleyin. |
+
+```ts
+await enqueueJob({
+ logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
+ payload: { page: 1 },
+ retryLimit: 3,
+ delayMs: 60_000,
+});
+```
+
+
+**Öncelik henüz yapılandırılamıyor.** Kuyruğa alınan işler her zaman en düşük öncelikte çalışır, bu nedenle platform işleri asla uygulama işlerinin arkasında gecikmez. Öncelik üzerinde denetim yakında geliyor.
+
+
+Kuyruğa alınan çalıştırma, onu kuyruğa alan fonksiyonun etkin kullanıcı bilgisini devralır; böylece aynı izinlerle hareket eder.
+
+## Kullanım örneği: uzun bir eşitleme boyunca sayfalama yapma
+
+Klasik biçim, bir sonraki imleçle *kendini* kuyruğa alan bir fonksiyondur. Her çalıştırma, kendi zaman aşımı süresinin oldukça altında bir sayfa işi tamamlar ve hiçbir şey kalmadığında zincir durur.
+
+```ts src/logic-functions/sync-contacts-page.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob } from 'twenty-sdk/logic-function';
+
+const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
+
+const handler = async (params: { cursor?: string }) => {
+ const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
+
+ await importContacts(contacts);
+
+ if (nextCursor) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
+ payload: { cursor: nextCursor },
+ delayMs: 2_000,
+ });
+ }
+
+ return { imported: contacts.length, done: !nextCursor };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: SYNC_CONTACTS_PAGE,
+ name: 'sync-contacts-page',
+ timeoutSeconds: 120,
+ handler,
+});
+```
+
+## Kayıt başına fan-out
+
+İş doğal olarak öğe bazındaysa, her öğe için bir iş kuyruğa alın ve döngüyü satır içi çalıştırmak yerine çalışanların bunları paralel olarak işlemesine izin verin.
+
+```ts
+const companies = await listCompaniesToEnrich();
+
+await Promise.all(
+ companies.map((company) =>
+ enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANY,
+ payload: { companyId: company.id },
+ retryLimit: 2,
+ }),
+ ),
+);
+```
+
+## Uzun süre çalışan işler için iyi uygulamalar
+
+Hemen hemen tüm uzun işleri kapsayan iki kural: **döngü kurmak yerine özyineleme (recurse) kullanın** ve **her çalıştırmada sınırlı büyüklükte bir parçayı işleyin**.
+
+Her şeyi tek seferde yapmaya çalışan bir çalıştırma, hata senaryosudur — zaman aşımına takılır ve yeniden denemede her şeyi baştan başlatır. Bunun yerine, bir parçayı `timeoutSeconds` süresinin içinde rahatça bitecek şekilde boyutlandırın, konumunuzu kalıcı hale getirin ve sonraki çalıştırmayı kuyruğa alın.
+
+```ts src/logic-functions/enrich-companies-batch.ts
+import { defineLogicFunction } from 'twenty-sdk/define';
+import { enqueueJob, kv } from 'twenty-sdk/logic-function';
+
+const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
+const CHUNK_SIZE = 50;
+
+const handler = async (params: { offset?: number }) => {
+ const offset = params.offset ?? 0;
+ const companies = await listCompaniesToEnrich({
+ offset,
+ limit: CHUNK_SIZE,
+ });
+
+ for (const company of companies) {
+ await enrichCompany(company);
+ }
+
+ await kv.set('enrich:progress', { offset: offset + companies.length });
+
+ if (companies.length === CHUNK_SIZE) {
+ await enqueueJob({
+ logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
+ payload: { offset: offset + CHUNK_SIZE },
+ });
+ }
+
+ return { processed: companies.length, done: companies.length < CHUNK_SIZE };
+};
+
+export default defineLogicFunction({
+ universalIdentifier: ENRICH_COMPANIES_BATCH,
+ name: 'enrich-companies-batch',
+ timeoutSeconds: 300,
+ handler,
+});
+```
+
+Bunu işe yarar kılanlar:
+
+* **Parça boyutunu ortalamaya değil, en yavaş öğeye göre belirleyin.** `CHUNK_SIZE × en kötü durum öğe süresi` değeri, pay bırakacak şekilde `timeoutSeconds` içine sığmalıdır, yoksa parça kuyruğunun sonundakiler çalıştırma kesildiğinde kaybolur.
+* **Sonlandırma koşulunu açık hale getirin.** Yalnızca tam dolu bir parça döndüğü sürece özyineleme yapın. Yalnızca "sonuç yok" durumunda duran bir zincir, kaynak ortada bir yerde kısa bir sayfa döndürürse sonsuza dek devam eder.
+* **Sonraki çalıştırmayı kuyruğa almadan önce ilerlemeyi kalıcı hale getirin,** böylece başarısız olan bir halka baştan başlamak yerine son tamamlanan parçadan yeniden başlar.
+* **Her parçayı idempotent tutun.** Bir yeniden denemeden sonra aynı parçanın yeniden işlenmesi, iki kez yazma yapmamalıdır — yazma işlemlerini, işlediğiniz kayıt veya harici kimlik üzerinde anahtarlandırın.
+* **Oran sınırlamalı bir üçüncü tarafla çalışırken, tek bir dev fan-out yerine parçalara bölünmüş bir zinciri tercih edin;** `delayMs` içeren bir zincir kendi hızını ayarlar, oysa binlerce işi bir kerede kuyruğa almak, hepsinin aynı anda uygun hale gelmesine neden olur.
+
+
+Yeniden denemeler tüm işleyiciyi yeniden çalıştırır. `retryLimit` değerini `0`’ın üzerine çıkarmadan önce kuyruğa alınan işleyicileri idempotent tutun.
+
diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/logic/overview.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/logic/overview.mdx
index 143667b8f5..d5f7ede2b0 100644
--- a/packages/twenty-docs/l/tr/developers/extend/apps/logic/overview.mdx
+++ b/packages/twenty-docs/l/tr/developers/extend/apps/logic/overview.mdx
@@ -37,6 +37,9 @@ Bir Twenty uygulamasının **mantık katmanı**, *çalışan* koddur — HTTP is
Mantık fonksiyonu çalıştırmaları arasında durumu kalıcı hale getirin — önbellekler, imleçler ve çalışma alanları arası beyanlar.
+
+ Zaman aşımı sınırını aşmak için işçilerde bir mantık fonksiyonu çalıştırmasını kuyruğa alın.
+
## Tetikleyici türlerine genel bakış
diff --git a/packages/twenty-docs/l/tr/developers/self-host/capabilities/upgrade-guide.mdx b/packages/twenty-docs/l/tr/developers/self-host/capabilities/upgrade-guide.mdx
index 3d0f5e2861..93ee0ae703 100644
--- a/packages/twenty-docs/l/tr/developers/self-host/capabilities/upgrade-guide.mdx
+++ b/packages/twenty-docs/l/tr/developers/self-host/capabilities/upgrade-guide.mdx
@@ -25,11 +25,11 @@ Docker Compose kullanıyorsanız, aşağıdaki adımları izleyin:
Sunucu, başlatma sırasında gerekli tüm yükseltme migrasyonlarını otomatik olarak çalıştırır. Manuel bir komut gerekmez.
-## Sürümler arası yükseltmeler (v1.22+)
+## Sürümler arası yükseltmeler (v1.23+)
-Twenty, **v1.22** sürümünden itibaren sürümler arası yükseltmeleri destekler. Desteklenen herhangi bir sürümden, ara sürümlerin her birine tek tek geçmeden doğrudan en son sürüme atlayabilirsiniz.
+Twenty, **v1.23** sürümünden itibaren sürümler arası yükseltmeleri destekler. Desteklenen herhangi bir sürümden, ara sürümlerin her birine tek tek geçmeden doğrudan en son sürüme atlayabilirsiniz.
-Örneğin, v1.22'den doğrudan v2.0'a yükseltme tamamen desteklenir.
+Örneğin, v1.23'ten doğrudan v2.0'a yükseltme tamamen desteklenir.
## v2.5+ sürümüne yükseltme — bekleme hâlindeki şifreleme zarfı
@@ -97,6 +97,6 @@ docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status -
Bu, yalnızca geride kalan veya başarısız olan çalışma alanlarını ve her bir başarısızlık için hata iletisini gösterir.
-## v1.22'den önce
+## v1.23'ten önce
-Örneğiniz v1.22'den eskiyse, v1.22'ye ulaşana kadar her ana etiketli sürümden (v1.6'dan v1.7'ye, ardından v1.7'den v1.8'e vb.) kademeli olarak yükseltmeniz gerekir. Bundan sonra, doğrudan en son sürüme geçebilirsiniz.
+Örneğiniz v1.23'ten eskiyse, v1.23'e ulaşana kadar her ana etiketli sürümden (v1.6'dan v1.7'ye, ardından v1.7'den v1.8'e vb.) kademeli olarak yükseltmeniz gerekir. Bundan sonra, doğrudan en son sürüme geçebilirsiniz.