@open-mercato/core 0.8.1-develop.7275.1.f772b944d4 → 0.8.1-develop.7294.1.0ef99fc518
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/components/AclEditor.js +21 -3
- package/dist/modules/auth/components/AclEditor.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +6 -4
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js +6 -4
- package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +6 -4
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js +5 -3
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +6 -4
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +6 -4
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +9 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +4 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/lookups.js +7 -3
- package/dist/modules/customers/components/calendar/editor/lookups.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +5 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -2
- package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +8 -5
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +10 -7
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/fieldConfig.js +9 -0
- package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/index.js +3 -1
- package/dist/modules/customers/components/detail/schedule/index.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +11 -4
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +17 -9
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/data/validators.js +12 -7
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/lib/assignableStaff.js +103 -22
- package/dist/modules/customers/lib/assignableStaff.js.map +2 -2
- package/dist/modules/customers/setup.js +10 -2
- package/dist/modules/customers/setup.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +5 -0
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +5 -0
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +29 -5
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +48 -11
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
- package/dist/modules/data_sync/data/validators.js +13 -2
- package/dist/modules/data_sync/data/validators.js.map +2 -2
- package/dist/modules/data_sync/lib/schedule-value.js +30 -0
- package/dist/modules/data_sync/lib/schedule-value.js.map +7 -0
- package/dist/modules/eudr/data/validators.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +3 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +3 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +2 -1
- package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +3 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +3 -4
- package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +2 -2
- package/dist/modules/portal/lib/navigation.js +3 -4
- package/dist/modules/portal/lib/navigation.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +79 -14
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/page.js +122 -86
- package/dist/modules/staff/backend/staff/time-tracking/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +38 -17
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +45 -18
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +2 -2
- package/dist/modules/staff/extension-points.js +16 -0
- package/dist/modules/staff/extension-points.js.map +2 -2
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +3 -4
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +2 -2
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +3 -4
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +14 -13
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +60 -41
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js +2 -2
- package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/ai/draft-reply/route.js +3 -4
- package/dist/modules/warranty_claims/api/ai/draft-reply/route.js.map +3 -3
- package/dist/modules/warranty_claims/api/ai/suggest/route.js +3 -3
- package/dist/modules/warranty_claims/api/ai/suggest/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/assign/route.js +3 -3
- package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js +2 -2
- package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/entitlement/route.js +2 -2
- package/dist/modules/warranty_claims/api/entitlement/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/events/route.js +4 -4
- package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/external/claims/route.js +3 -3
- package/dist/modules/warranty_claims/api/external/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js +2 -2
- package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/replacement-order/route.js +2 -2
- package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js +2 -2
- package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/risk/route.js +2 -2
- package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/sales-return/route.js +2 -2
- package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/settings-general/route.js +4 -4
- package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/stats/route.js +2 -2
- package/dist/modules/warranty_claims/api/stats/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/submit/route.js +3 -3
- package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/transition/route.js +3 -3
- package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js +3 -3
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +2 -2
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
- package/dist/modules/warranty_claims/backend/page.js +4 -2
- package/dist/modules/warranty_claims/backend/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js +9 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js +4 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +3 -4
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js +3 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -1
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
- package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js +2 -2
- package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js.map +2 -2
- package/dist/modules/workflows/lib/event-trigger-service.js +56 -1
- package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/api/users/acl/route.ts +4 -3
- package/src/modules/auth/components/AclEditor.tsx +38 -2
- package/src/modules/auth/i18n/de.json +5 -1
- package/src/modules/auth/i18n/en.json +5 -1
- package/src/modules/auth/i18n/es.json +5 -1
- package/src/modules/auth/i18n/ko.json +5 -1
- package/src/modules/auth/i18n/pl.json +5 -1
- package/src/modules/customers/backend/customers/companies/page.tsx +6 -4
- package/src/modules/customers/backend/customers/deals/map/components/DealsMapView.tsx +6 -4
- package/src/modules/customers/backend/customers/deals/page.tsx +6 -4
- package/src/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.tsx +5 -3
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +6 -4
- package/src/modules/customers/backend/customers/people/page.tsx +6 -4
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +9 -2
- package/src/modules/customers/components/calendar/editor/hooks.ts +4 -2
- package/src/modules/customers/components/calendar/editor/lookups.ts +12 -4
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +4 -1
- package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -2
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +15 -7
- package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +16 -7
- package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +4 -2
- package/src/modules/customers/components/detail/schedule/fieldConfig.ts +15 -0
- package/src/modules/customers/components/detail/schedule/index.ts +1 -1
- package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +16 -9
- package/src/modules/customers/components/formConfig.tsx +35 -12
- package/src/modules/customers/data/validators.ts +14 -8
- package/src/modules/customers/i18n/de.json +1 -0
- package/src/modules/customers/i18n/en.json +1 -0
- package/src/modules/customers/i18n/es.json +1 -0
- package/src/modules/customers/i18n/ko.json +1 -0
- package/src/modules/customers/i18n/pl.json +1 -0
- package/src/modules/customers/lib/assignableStaff.ts +172 -34
- package/src/modules/customers/setup.ts +8 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +5 -0
- package/src/modules/data_sync/api/schedules/route.ts +5 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +48 -4
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +61 -2
- package/src/modules/data_sync/data/validators.ts +26 -2
- package/src/modules/data_sync/i18n/de.json +2 -0
- package/src/modules/data_sync/i18n/en.json +2 -0
- package/src/modules/data_sync/i18n/es.json +2 -0
- package/src/modules/data_sync/i18n/ko.json +2 -0
- package/src/modules/data_sync/i18n/pl.json +2 -0
- package/src/modules/data_sync/lib/schedule-value.ts +45 -0
- package/src/modules/eudr/data/validators.ts +3 -4
- package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +5 -4
- package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +3 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +2 -1
- package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +5 -5
- package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +5 -4
- package/src/modules/portal/i18n/de.json +1 -0
- package/src/modules/portal/i18n/en.json +1 -0
- package/src/modules/portal/i18n/es.json +1 -0
- package/src/modules/portal/i18n/ko.json +1 -0
- package/src/modules/portal/i18n/pl.json +1 -0
- package/src/modules/portal/lib/navigation.ts +8 -3
- package/src/modules/query_index/lib/engine.ts +111 -16
- package/src/modules/staff/backend/staff/time-tracking/page.tsx +161 -130
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +55 -43
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +61 -38
- package/src/modules/staff/extension-points.ts +16 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +3 -4
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +3 -4
- package/src/modules/staff/i18n/de.json +3 -0
- package/src/modules/staff/i18n/en.json +3 -0
- package/src/modules/staff/i18n/es.json +3 -0
- package/src/modules/staff/i18n/ko.json +3 -0
- package/src/modules/staff/i18n/pl.json +3 -0
- package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +32 -29
- package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +89 -79
- package/src/modules/warranty_claims/api/ai/assess/route.ts +2 -2
- package/src/modules/warranty_claims/api/ai/draft-reply/route.ts +2 -3
- package/src/modules/warranty_claims/api/ai/suggest/route.ts +3 -3
- package/src/modules/warranty_claims/api/assign/route.ts +3 -3
- package/src/modules/warranty_claims/api/credit-memo/route.ts +2 -2
- package/src/modules/warranty_claims/api/entitlement/route.ts +2 -2
- package/src/modules/warranty_claims/api/events/route.ts +4 -4
- package/src/modules/warranty_claims/api/external/claims/route.ts +3 -3
- package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/claims/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/events/route.ts +2 -2
- package/src/modules/warranty_claims/api/receiving/route.ts +2 -2
- package/src/modules/warranty_claims/api/replacement-order/route.ts +2 -2
- package/src/modules/warranty_claims/api/return-label/route.ts +2 -2
- package/src/modules/warranty_claims/api/risk/route.ts +2 -2
- package/src/modules/warranty_claims/api/sales-return/route.ts +2 -2
- package/src/modules/warranty_claims/api/settings-general/route.ts +4 -4
- package/src/modules/warranty_claims/api/stats/route.ts +2 -2
- package/src/modules/warranty_claims/api/submit/route.ts +3 -3
- package/src/modules/warranty_claims/api/transition/route.ts +3 -3
- package/src/modules/warranty_claims/api/vendor-recovery/route.ts +3 -3
- package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +2 -2
- package/src/modules/warranty_claims/backend/page.tsx +4 -2
- package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.tsx +9 -2
- package/src/modules/warranty_claims/backend/warranty_claims/settings/page.tsx +4 -2
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +5 -4
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.tsx +5 -2
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +5 -2
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -1
- package/src/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.tsx +2 -2
- package/src/modules/workflows/i18n/de.json +1 -1
- package/src/modules/workflows/i18n/en.json +1 -1
- package/src/modules/workflows/i18n/es.json +1 -1
- package/src/modules/workflows/i18n/ko.json +1 -1
- package/src/modules/workflows/i18n/pl.json +1 -1
- package/src/modules/workflows/lib/event-trigger-service.ts +139 -0
|
@@ -640,6 +640,134 @@ export async function findMatchingTriggers(
|
|
|
640
640
|
// Trigger Processing
|
|
641
641
|
// ============================================================================
|
|
642
642
|
|
|
643
|
+
// Open debounce windows for triggers configured with `debounceMs`, keyed by
|
|
644
|
+
// tenant/org/trigger/entity and holding the timestamp each window closes at.
|
|
645
|
+
// Parked on globalThis for the same reason as the trigger cache above: the
|
|
646
|
+
// compiled module can be loaded under two import roots (a Next.js server chunk
|
|
647
|
+
// vs. a worker), and a module-local Map would debounce each copy separately —
|
|
648
|
+
// halving the effective window whenever both copies see the same event stream.
|
|
649
|
+
// The state is still per process: a deployment running several app or worker
|
|
650
|
+
// processes debounces independently in each one, so the guard bounds trigger
|
|
651
|
+
// storms per process rather than cluster-wide.
|
|
652
|
+
const GLOBAL_TRIGGER_DEBOUNCE_KEY = '__openMercatoWorkflowTriggerDebounce__'
|
|
653
|
+
|
|
654
|
+
// Cap on tracked keys so a long-lived worker cannot grow the Map without bound;
|
|
655
|
+
// mirrors the prune step in query_index's `markAutoReindexScheduled`.
|
|
656
|
+
const TRIGGER_DEBOUNCE_MAX_KEYS = 5000
|
|
657
|
+
|
|
658
|
+
type GlobalWithTriggerDebounce = typeof globalThis & {
|
|
659
|
+
[GLOBAL_TRIGGER_DEBOUNCE_KEY]?: Map<string, number>
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function getTriggerDebounceState(): Map<string, number> {
|
|
663
|
+
const globalScope = globalThis as GlobalWithTriggerDebounce
|
|
664
|
+
if (!globalScope[GLOBAL_TRIGGER_DEBOUNCE_KEY]) {
|
|
665
|
+
globalScope[GLOBAL_TRIGGER_DEBOUNCE_KEY] = new Map<string, number>()
|
|
666
|
+
}
|
|
667
|
+
return globalScope[GLOBAL_TRIGGER_DEBOUNCE_KEY]
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Entries store their own expiry rather than their start, so pruning never has
|
|
671
|
+
// to assume one shared window length: `debounceMs` is per trigger and ranges
|
|
672
|
+
// from 0 to the validator's one-hour ceiling, and expiring a one-hour window
|
|
673
|
+
// because a 100 ms trigger happened to run the prune would silently reopen it.
|
|
674
|
+
function pruneTriggerDebounceState(state: Map<string, number>, now: number): void {
|
|
675
|
+
if (state.size < TRIGGER_DEBOUNCE_MAX_KEYS) return
|
|
676
|
+
|
|
677
|
+
for (const [key, expiresAt] of state) {
|
|
678
|
+
if (expiresAt <= now) state.delete(key)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (state.size < TRIGGER_DEBOUNCE_MAX_KEYS) return
|
|
682
|
+
|
|
683
|
+
const oldestKey = state.keys().next().value
|
|
684
|
+
if (oldestKey !== undefined) state.delete(oldestKey)
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Resolve the debounce window a trigger/event pair shares, or `null` when the
|
|
689
|
+
* trigger has no debounce configured.
|
|
690
|
+
*
|
|
691
|
+
* The key includes the event payload's `id` so the window is per trigger *and*
|
|
692
|
+
* per entity — rapid updates to two different records still start two
|
|
693
|
+
* workflows. Tenant and organization are part of the key because code-defined
|
|
694
|
+
* triggers reuse one id across every tenant.
|
|
695
|
+
*/
|
|
696
|
+
function resolveTriggerDebounceWindow(
|
|
697
|
+
trigger: UnifiedTrigger,
|
|
698
|
+
payload: Record<string, unknown>,
|
|
699
|
+
): { key: string; debounceMs: number } | null {
|
|
700
|
+
const debounceMs = trigger.config?.debounceMs
|
|
701
|
+
|
|
702
|
+
if (!debounceMs || debounceMs <= 0) return null
|
|
703
|
+
|
|
704
|
+
// Accept numeric ids too: falling back to the shared `*` bucket would collapse
|
|
705
|
+
// every record under one key and suppress workflows for unrelated entities.
|
|
706
|
+
const rawPayloadId = payload?.id
|
|
707
|
+
const payloadId =
|
|
708
|
+
typeof rawPayloadId === 'string' ? rawPayloadId
|
|
709
|
+
: typeof rawPayloadId === 'number' ? String(rawPayloadId)
|
|
710
|
+
: null
|
|
711
|
+
|
|
712
|
+
return { key: `${trigger.tenantId}:${trigger.organizationId}:${trigger.id}:${payloadId ?? '*'}`, debounceMs }
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Check whether this event falls inside the trigger's debounce window, opening
|
|
717
|
+
* a fresh window when it does not.
|
|
718
|
+
*
|
|
719
|
+
* Leading-edge semantics: the first event fires the workflow and opens the
|
|
720
|
+
* window; repeats inside it are dropped. This matches the two other
|
|
721
|
+
* `debounceMs` implementations in the codebase (`markAutoReindexScheduled` in
|
|
722
|
+
* query_index, `NotificationDispatcher.shouldDebounce` in the UI package) and
|
|
723
|
+
* needs no timer that would have to survive a process restart.
|
|
724
|
+
*
|
|
725
|
+
* The window is opened here rather than after the workflow starts so that
|
|
726
|
+
* nothing awaits between reading and writing it — two events racing through
|
|
727
|
+
* `processEventTriggers` must not both observe an empty window. An attempt that
|
|
728
|
+
* does not go on to start a workflow closes its window again via
|
|
729
|
+
* `releaseTriggerDebounceWindow`, so only a real start keeps one open.
|
|
730
|
+
*/
|
|
731
|
+
function shouldDebounceTrigger(trigger: UnifiedTrigger, payload: Record<string, unknown>): boolean {
|
|
732
|
+
const debounceWindow = resolveTriggerDebounceWindow(trigger, payload)
|
|
733
|
+
|
|
734
|
+
if (!debounceWindow) return false
|
|
735
|
+
|
|
736
|
+
const state = getTriggerDebounceState()
|
|
737
|
+
const now = Date.now()
|
|
738
|
+
const expiresAt = state.get(debounceWindow.key)
|
|
739
|
+
|
|
740
|
+
if (expiresAt !== undefined && now < expiresAt) return true
|
|
741
|
+
|
|
742
|
+
pruneTriggerDebounceState(state, now)
|
|
743
|
+
state.delete(debounceWindow.key)
|
|
744
|
+
state.set(debounceWindow.key, now + debounceWindow.debounceMs)
|
|
745
|
+
return false
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Close a window `shouldDebounceTrigger` opened for an event that never started
|
|
750
|
+
* a workflow — the concurrency limit blocked it, or mapping/starting threw.
|
|
751
|
+
* Leaving it open would let one blocked or failed attempt suppress every later
|
|
752
|
+
* event for the same record until the window elapsed, which at the validator's
|
|
753
|
+
* one-hour ceiling means an hour of silently dropped starts.
|
|
754
|
+
*/
|
|
755
|
+
function releaseTriggerDebounceWindow(trigger: UnifiedTrigger, payload: Record<string, unknown>): void {
|
|
756
|
+
const debounceWindow = resolveTriggerDebounceWindow(trigger, payload)
|
|
757
|
+
|
|
758
|
+
if (!debounceWindow) return
|
|
759
|
+
|
|
760
|
+
getTriggerDebounceState().delete(debounceWindow.key)
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Clear all debounce windows. Test-only seam — production code relies on the
|
|
765
|
+
* windows expiring on their own.
|
|
766
|
+
*/
|
|
767
|
+
export function resetTriggerDebounceState(): void {
|
|
768
|
+
getTriggerDebounceState().clear()
|
|
769
|
+
}
|
|
770
|
+
|
|
643
771
|
/**
|
|
644
772
|
* Check if max concurrent instances limit is reached.
|
|
645
773
|
*/
|
|
@@ -687,11 +815,20 @@ export async function processEventTriggers(
|
|
|
687
815
|
|
|
688
816
|
// Process each trigger (definitions already validated during loading)
|
|
689
817
|
for (const trigger of triggers) {
|
|
818
|
+
let startedWorkflow = false
|
|
690
819
|
try {
|
|
820
|
+
// Check debounce window before any DB work
|
|
821
|
+
if (shouldDebounceTrigger(trigger, context.payload)) {
|
|
822
|
+
logger.debug('Skipping trigger: inside debounce window', { triggerId: trigger.id, triggerName: trigger.name, debounceMs: trigger.config?.debounceMs })
|
|
823
|
+
result.skipped++
|
|
824
|
+
continue
|
|
825
|
+
}
|
|
826
|
+
|
|
691
827
|
// Check concurrency limit
|
|
692
828
|
const canStart = await checkConcurrencyLimit(em, trigger)
|
|
693
829
|
if (!canStart) {
|
|
694
830
|
logger.debug('Skipping trigger: max concurrent instances reached', { triggerId: trigger.id, triggerName: trigger.name })
|
|
831
|
+
releaseTriggerDebounceWindow(trigger, context.payload)
|
|
695
832
|
result.skipped++
|
|
696
833
|
continue
|
|
697
834
|
}
|
|
@@ -741,6 +878,7 @@ export async function processEventTriggers(
|
|
|
741
878
|
organizationId: context.organizationId,
|
|
742
879
|
})
|
|
743
880
|
|
|
881
|
+
startedWorkflow = true
|
|
744
882
|
result.triggered++
|
|
745
883
|
result.instances.push({
|
|
746
884
|
triggerId: trigger.id,
|
|
@@ -760,6 +898,7 @@ export async function processEventTriggers(
|
|
|
760
898
|
} catch (error) {
|
|
761
899
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
762
900
|
logger.error('Error processing trigger', { triggerId: trigger.id, triggerName: trigger.name, err: error })
|
|
901
|
+
if (!startedWorkflow) releaseTriggerDebounceWindow(trigger, context.payload)
|
|
763
902
|
result.errors.push({
|
|
764
903
|
triggerId: trigger.id,
|
|
765
904
|
error: errorMessage,
|