@open-mercato/core 0.4.2-canary-d0a025141f → 0.4.2-canary-3efa759f5c

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.
Files changed (158) hide show
  1. package/dist/generated/entities.ids.generated.js +0 -1
  2. package/dist/generated/entities.ids.generated.js.map +2 -2
  3. package/dist/generated/entity-fields-registry.js +0 -2
  4. package/dist/generated/entity-fields-registry.js.map +2 -2
  5. package/dist/modules/business_rules/data/validators.js +0 -34
  6. package/dist/modules/business_rules/data/validators.js.map +2 -2
  7. package/dist/modules/business_rules/index.js +1 -21
  8. package/dist/modules/business_rules/index.js.map +2 -2
  9. package/dist/modules/business_rules/lib/rule-engine.js +1 -182
  10. package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
  11. package/dist/modules/sales/acl.js +0 -1
  12. package/dist/modules/sales/acl.js.map +2 -2
  13. package/dist/modules/sales/backend/sales/documents/[id]/page.js +0 -12
  14. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  15. package/dist/modules/sales/commands/documents.js +0 -62
  16. package/dist/modules/sales/commands/documents.js.map +2 -2
  17. package/dist/modules/sales/lib/dictionaries.js +0 -3
  18. package/dist/modules/sales/lib/dictionaries.js.map +2 -2
  19. package/dist/modules/workflows/acl.js +0 -2
  20. package/dist/modules/workflows/acl.js.map +2 -2
  21. package/dist/modules/workflows/api/instances/route.js +6 -18
  22. package/dist/modules/workflows/api/instances/route.js.map +2 -2
  23. package/dist/modules/workflows/api/tasks/route.js +1 -6
  24. package/dist/modules/workflows/api/tasks/route.js.map +2 -2
  25. package/dist/modules/workflows/backend/definitions/[id]/page.js +1 -9
  26. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  27. package/dist/modules/workflows/backend/definitions/[id]/page.meta.js +1 -1
  28. package/dist/modules/workflows/backend/definitions/[id]/page.meta.js.map +2 -2
  29. package/dist/modules/workflows/backend/definitions/create/page.js +15 -24
  30. package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
  31. package/dist/modules/workflows/backend/definitions/create/page.meta.js +1 -1
  32. package/dist/modules/workflows/backend/definitions/create/page.meta.js.map +2 -2
  33. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +132 -150
  34. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  35. package/dist/modules/workflows/backend/definitions/visual-editor/page.meta.js +1 -1
  36. package/dist/modules/workflows/backend/definitions/visual-editor/page.meta.js.map +2 -2
  37. package/dist/modules/workflows/backend/events/[id]/page.js +1 -1
  38. package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
  39. package/dist/modules/workflows/backend/events/[id]/page.meta.js +2 -2
  40. package/dist/modules/workflows/backend/events/[id]/page.meta.js.map +2 -2
  41. package/dist/modules/workflows/backend/instances/[id]/page.meta.js +2 -2
  42. package/dist/modules/workflows/backend/instances/[id]/page.meta.js.map +2 -2
  43. package/dist/modules/workflows/backend/tasks/[id]/page.js +1 -1
  44. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  45. package/dist/modules/workflows/backend/tasks/[id]/page.meta.js +2 -2
  46. package/dist/modules/workflows/backend/tasks/[id]/page.meta.js.map +2 -2
  47. package/dist/modules/workflows/backend/tasks/page.js +6 -5
  48. package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
  49. package/dist/modules/workflows/cli.js +3 -81
  50. package/dist/modules/workflows/cli.js.map +3 -3
  51. package/dist/modules/workflows/data/entities.js +1 -64
  52. package/dist/modules/workflows/data/entities.js.map +2 -2
  53. package/dist/modules/workflows/data/validators.js +0 -115
  54. package/dist/modules/workflows/data/validators.js.map +2 -2
  55. package/dist/modules/workflows/examples/checkout-demo-definition.json +5 -1
  56. package/dist/modules/workflows/lib/activity-executor.js +13 -75
  57. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  58. package/dist/modules/workflows/lib/graph-utils.js +2 -71
  59. package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
  60. package/dist/modules/workflows/lib/seeds.js +5 -22
  61. package/dist/modules/workflows/lib/seeds.js.map +2 -2
  62. package/dist/modules/workflows/lib/start-validator.js +23 -33
  63. package/dist/modules/workflows/lib/start-validator.js.map +2 -2
  64. package/dist/modules/workflows/lib/transition-handler.js +45 -157
  65. package/dist/modules/workflows/lib/transition-handler.js.map +3 -3
  66. package/generated/entities.ids.generated.ts +0 -1
  67. package/generated/entity-fields-registry.ts +0 -2
  68. package/package.json +2 -2
  69. package/src/modules/business_rules/data/validators.ts +0 -40
  70. package/src/modules/business_rules/index.ts +0 -25
  71. package/src/modules/business_rules/lib/rule-engine.ts +1 -281
  72. package/src/modules/sales/acl.ts +0 -1
  73. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +0 -16
  74. package/src/modules/sales/commands/documents.ts +1 -74
  75. package/src/modules/sales/lib/dictionaries.ts +0 -3
  76. package/src/modules/workflows/acl.ts +0 -2
  77. package/src/modules/workflows/api/__tests__/instances.route.test.ts +2 -5
  78. package/src/modules/workflows/api/instances/route.ts +7 -21
  79. package/src/modules/workflows/api/tasks/route.ts +1 -7
  80. package/src/modules/workflows/backend/definitions/[id]/page.meta.ts +1 -1
  81. package/src/modules/workflows/backend/definitions/[id]/page.tsx +0 -9
  82. package/src/modules/workflows/backend/definitions/create/page.meta.ts +1 -1
  83. package/src/modules/workflows/backend/definitions/create/page.tsx +0 -9
  84. package/src/modules/workflows/backend/definitions/visual-editor/page.meta.ts +1 -1
  85. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +3 -21
  86. package/src/modules/workflows/backend/events/[id]/page.meta.ts +2 -2
  87. package/src/modules/workflows/backend/events/[id]/page.tsx +1 -1
  88. package/src/modules/workflows/backend/instances/[id]/page.meta.ts +2 -2
  89. package/src/modules/workflows/backend/tasks/[id]/page.meta.ts +2 -2
  90. package/src/modules/workflows/backend/tasks/[id]/page.tsx +1 -1
  91. package/src/modules/workflows/backend/tasks/page.tsx +6 -5
  92. package/src/modules/workflows/cli.ts +0 -111
  93. package/src/modules/workflows/data/entities.ts +0 -124
  94. package/src/modules/workflows/data/validators.ts +0 -138
  95. package/src/modules/workflows/examples/checkout-demo-definition.json +5 -1
  96. package/src/modules/workflows/i18n/en.json +0 -71
  97. package/src/modules/workflows/lib/__tests__/activity-executor.test.ts +36 -43
  98. package/src/modules/workflows/lib/__tests__/transition-handler.test.ts +90 -170
  99. package/src/modules/workflows/lib/activity-executor.ts +16 -129
  100. package/src/modules/workflows/lib/graph-utils.ts +2 -117
  101. package/src/modules/workflows/lib/seeds.ts +8 -34
  102. package/src/modules/workflows/lib/start-validator.ts +28 -38
  103. package/src/modules/workflows/lib/transition-handler.ts +55 -208
  104. package/dist/generated/entities/workflow_event_trigger/index.js +0 -33
  105. package/dist/generated/entities/workflow_event_trigger/index.js.map +0 -7
  106. package/dist/modules/auth/events.js +0 -30
  107. package/dist/modules/auth/events.js.map +0 -7
  108. package/dist/modules/business_rules/api/execute/[ruleId]/route.js +0 -145
  109. package/dist/modules/business_rules/api/execute/[ruleId]/route.js.map +0 -7
  110. package/dist/modules/catalog/events.js +0 -34
  111. package/dist/modules/catalog/events.js.map +0 -7
  112. package/dist/modules/customers/events.js +0 -49
  113. package/dist/modules/customers/events.js.map +0 -7
  114. package/dist/modules/directory/events.js +0 -23
  115. package/dist/modules/directory/events.js.map +0 -7
  116. package/dist/modules/sales/events.js +0 -63
  117. package/dist/modules/sales/events.js.map +0 -7
  118. package/dist/modules/sales/lib/frontend/documentDataEvents.js +0 -25
  119. package/dist/modules/sales/lib/frontend/documentDataEvents.js.map +0 -7
  120. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +0 -481
  121. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +0 -7
  122. package/dist/modules/workflows/components/EventTriggersEditor.js +0 -553
  123. package/dist/modules/workflows/components/EventTriggersEditor.js.map +0 -7
  124. package/dist/modules/workflows/events.js +0 -38
  125. package/dist/modules/workflows/events.js.map +0 -7
  126. package/dist/modules/workflows/examples/order-approval-definition.json +0 -257
  127. package/dist/modules/workflows/examples/order-approval-guard-rules.json +0 -32
  128. package/dist/modules/workflows/lib/event-trigger-service.js +0 -308
  129. package/dist/modules/workflows/lib/event-trigger-service.js.map +0 -7
  130. package/dist/modules/workflows/migrations/Migration20260123143500.js +0 -36
  131. package/dist/modules/workflows/migrations/Migration20260123143500.js.map +0 -7
  132. package/dist/modules/workflows/subscribers/event-trigger.js +0 -78
  133. package/dist/modules/workflows/subscribers/event-trigger.js.map +0 -7
  134. package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js +0 -323
  135. package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js.map +0 -7
  136. package/dist/modules/workflows/widgets/injection/order-approval/widget.js +0 -17
  137. package/dist/modules/workflows/widgets/injection/order-approval/widget.js.map +0 -7
  138. package/dist/modules/workflows/widgets/injection-table.js +0 -19
  139. package/dist/modules/workflows/widgets/injection-table.js.map +0 -7
  140. package/generated/entities/workflow_event_trigger/index.ts +0 -15
  141. package/src/modules/auth/events.ts +0 -39
  142. package/src/modules/business_rules/api/execute/[ruleId]/route.ts +0 -163
  143. package/src/modules/catalog/events.ts +0 -45
  144. package/src/modules/customers/events.ts +0 -63
  145. package/src/modules/directory/events.ts +0 -31
  146. package/src/modules/sales/events.ts +0 -82
  147. package/src/modules/sales/lib/frontend/documentDataEvents.ts +0 -28
  148. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +0 -581
  149. package/src/modules/workflows/components/EventTriggersEditor.tsx +0 -664
  150. package/src/modules/workflows/events.ts +0 -49
  151. package/src/modules/workflows/examples/order-approval-definition.json +0 -257
  152. package/src/modules/workflows/examples/order-approval-guard-rules.json +0 -32
  153. package/src/modules/workflows/lib/event-trigger-service.ts +0 -557
  154. package/src/modules/workflows/migrations/Migration20260123143500.ts +0 -38
  155. package/src/modules/workflows/subscribers/event-trigger.ts +0 -109
  156. package/src/modules/workflows/widgets/injection/order-approval/widget.client.tsx +0 -446
  157. package/src/modules/workflows/widgets/injection/order-approval/widget.ts +0 -16
  158. package/src/modules/workflows/widgets/injection-table.ts +0 -21
@@ -1,557 +0,0 @@
1
- /**
2
- * Workflows Module - Event Trigger Service
3
- *
4
- * Core service for evaluating and processing workflow event triggers.
5
- * Handles pattern matching, filter evaluation, context mapping, and workflow starting.
6
- */
7
-
8
- import type { EntityManager } from '@mikro-orm/core'
9
- import type { AwilixContainer } from 'awilix'
10
- import type { CacheService } from '@open-mercato/cache'
11
- import {
12
- WorkflowEventTrigger,
13
- WorkflowDefinition,
14
- WorkflowInstance,
15
- type TriggerFilterCondition,
16
- type TriggerContextMapping,
17
- type WorkflowEventTriggerConfig,
18
- type WorkflowDefinitionTrigger,
19
- } from '../data/entities'
20
- import { startWorkflow, executeWorkflow } from './workflow-executor'
21
-
22
- // ============================================================================
23
- // Types
24
- // ============================================================================
25
-
26
- export interface EventTriggerContext {
27
- eventName: string
28
- payload: Record<string, unknown>
29
- tenantId: string
30
- organizationId: string
31
- }
32
-
33
- export interface ProcessTriggersResult {
34
- triggered: number
35
- skipped: number
36
- errors: Array<{ triggerId: string; error: string }>
37
- instances: Array<{ triggerId: string; instanceId: string }>
38
- }
39
-
40
- /**
41
- * Unified trigger interface for both legacy (entity) and embedded (definition) triggers
42
- */
43
- export interface UnifiedTrigger {
44
- id: string // For legacy: entity ID, for embedded: `${definitionId}:${triggerId}`
45
- triggerId: string
46
- name: string
47
- description?: string | null
48
- eventPattern: string
49
- config: WorkflowEventTriggerConfig | null
50
- enabled: boolean
51
- priority: number
52
- workflowDefinitionId: string
53
- workflowId: string
54
- workflowVersion: number
55
- source: 'legacy' | 'embedded'
56
- tenantId: string
57
- organizationId: string
58
- }
59
-
60
- interface CachedTriggers {
61
- triggers: UnifiedTrigger[]
62
- cachedAt: number
63
- }
64
-
65
- // Cache TTL: 5 minutes
66
- const TRIGGER_CACHE_TTL = 5 * 60 * 1000
67
-
68
- // ============================================================================
69
- // Pattern Matching
70
- // ============================================================================
71
-
72
- /**
73
- * Match an event name against a pattern.
74
- *
75
- * Supports:
76
- * - Exact match: `customers.people.created`
77
- * - Wildcard `*` matches single segment: `customers.*` matches `customers.people` but not `customers.people.created`
78
- * - Global wildcard: `*` alone matches all events
79
- */
80
- export function matchEventPattern(eventName: string, pattern: string): boolean {
81
- // Global wildcard matches all events
82
- if (pattern === '*') return true
83
-
84
- // Exact match
85
- if (pattern === eventName) return true
86
-
87
- // No wildcards in pattern means we need exact match, which already failed
88
- if (!pattern.includes('*')) return false
89
-
90
- // Convert pattern to regex:
91
- // - Escape regex special chars (except *)
92
- // - Replace * with [^.]+ (match one or more non-dot chars)
93
- const regexPattern = pattern
94
- .replace(/[.+^${}()|[\]\\]/g, '\\$&')
95
- .replace(/\*/g, '[^.]+')
96
- const regex = new RegExp(`^${regexPattern}$`)
97
- return regex.test(eventName)
98
- }
99
-
100
- // ============================================================================
101
- // Filter Evaluation
102
- // ============================================================================
103
-
104
- /**
105
- * Get a nested value from an object using dot notation.
106
- */
107
- function getNestedValue(obj: unknown, path: string): unknown {
108
- if (obj === null || obj === undefined) return undefined
109
-
110
- const parts = path.split('.')
111
- let current: unknown = obj
112
-
113
- for (const part of parts) {
114
- if (current === null || current === undefined) return undefined
115
- if (typeof current !== 'object') return undefined
116
- current = (current as Record<string, unknown>)[part]
117
- }
118
-
119
- return current
120
- }
121
-
122
- /**
123
- * Evaluate a single filter condition against the event payload.
124
- */
125
- function evaluateCondition(condition: TriggerFilterCondition, payload: Record<string, unknown>): boolean {
126
- const value = getNestedValue(payload, condition.field)
127
- const expected = condition.value
128
-
129
- switch (condition.operator) {
130
- case 'eq':
131
- return value === expected
132
-
133
- case 'neq':
134
- return value !== expected
135
-
136
- case 'gt':
137
- return typeof value === 'number' && typeof expected === 'number' && value > expected
138
-
139
- case 'gte':
140
- return typeof value === 'number' && typeof expected === 'number' && value >= expected
141
-
142
- case 'lt':
143
- return typeof value === 'number' && typeof expected === 'number' && value < expected
144
-
145
- case 'lte':
146
- return typeof value === 'number' && typeof expected === 'number' && value <= expected
147
-
148
- case 'contains':
149
- if (typeof value === 'string' && typeof expected === 'string') {
150
- return value.includes(expected)
151
- }
152
- if (Array.isArray(value)) {
153
- return value.includes(expected)
154
- }
155
- return false
156
-
157
- case 'startsWith':
158
- return typeof value === 'string' && typeof expected === 'string' && value.startsWith(expected)
159
-
160
- case 'endsWith':
161
- return typeof value === 'string' && typeof expected === 'string' && value.endsWith(expected)
162
-
163
- case 'in':
164
- return Array.isArray(expected) && expected.includes(value)
165
-
166
- case 'notIn':
167
- return Array.isArray(expected) && !expected.includes(value)
168
-
169
- case 'exists':
170
- return value !== undefined && value !== null
171
-
172
- case 'notExists':
173
- return value === undefined || value === null
174
-
175
- case 'regex':
176
- if (typeof value !== 'string' || typeof expected !== 'string') return false
177
- try {
178
- const regex = new RegExp(expected)
179
- return regex.test(value)
180
- } catch {
181
- return false
182
- }
183
-
184
- default:
185
- return false
186
- }
187
- }
188
-
189
- /**
190
- * Evaluate all filter conditions against the event payload.
191
- * All conditions must pass (AND logic).
192
- */
193
- export function evaluateFilterConditions(
194
- conditions: TriggerFilterCondition[] | undefined,
195
- payload: Record<string, unknown>
196
- ): boolean {
197
- if (!conditions || conditions.length === 0) return true
198
-
199
- return conditions.every(condition => evaluateCondition(condition, payload))
200
- }
201
-
202
- // ============================================================================
203
- // Context Mapping
204
- // ============================================================================
205
-
206
- /**
207
- * Map event payload to workflow initial context.
208
- */
209
- export function mapEventToContext(
210
- mapping: TriggerContextMapping[] | undefined,
211
- payload: Record<string, unknown>
212
- ): Record<string, unknown> {
213
- const context: Record<string, unknown> = {}
214
-
215
- if (!mapping || mapping.length === 0) return context
216
-
217
- for (const item of mapping) {
218
- const value = getNestedValue(payload, item.sourceExpression)
219
- context[item.targetKey] = value !== undefined ? value : item.defaultValue
220
- }
221
-
222
- return context
223
- }
224
-
225
- // ============================================================================
226
- // Trigger Loading with Caching
227
- // ============================================================================
228
-
229
- // In-memory cache for triggers (per tenant/org)
230
- const triggerCache = new Map<string, CachedTriggers>()
231
-
232
- function getCacheKey(tenantId: string, organizationId: string): string {
233
- return `${tenantId}:${organizationId}`
234
- }
235
-
236
- /**
237
- * Load legacy triggers from WorkflowEventTrigger entity table.
238
- * For backward compatibility with existing triggers.
239
- */
240
- async function loadLegacyTriggers(
241
- em: EntityManager,
242
- tenantId: string,
243
- organizationId: string
244
- ): Promise<UnifiedTrigger[]> {
245
- const legacyTriggers = await em.find(
246
- WorkflowEventTrigger,
247
- {
248
- tenantId,
249
- organizationId,
250
- enabled: true,
251
- deletedAt: null,
252
- },
253
- {
254
- orderBy: { priority: 'DESC', createdAt: 'ASC' },
255
- }
256
- )
257
-
258
- // Get definitions for these triggers to get workflowId
259
- const definitionIds = [...new Set(legacyTriggers.map(t => t.workflowDefinitionId))]
260
- const definitions = definitionIds.length > 0 ? await em.find(WorkflowDefinition, {
261
- id: { $in: definitionIds },
262
- enabled: true,
263
- deletedAt: null,
264
- }) : []
265
- const definitionMap = new Map(definitions.map(d => [d.id, d]))
266
-
267
- return legacyTriggers
268
- .filter(t => definitionMap.has(t.workflowDefinitionId))
269
- .map(t => {
270
- const def = definitionMap.get(t.workflowDefinitionId)!
271
- return {
272
- id: t.id,
273
- triggerId: t.id,
274
- name: t.name,
275
- description: t.description,
276
- eventPattern: t.eventPattern,
277
- config: t.config ?? null,
278
- enabled: t.enabled,
279
- priority: t.priority,
280
- workflowDefinitionId: t.workflowDefinitionId,
281
- workflowId: def.workflowId,
282
- workflowVersion: def.version,
283
- source: 'legacy' as const,
284
- tenantId: t.tenantId,
285
- organizationId: t.organizationId,
286
- }
287
- })
288
- }
289
-
290
- /**
291
- * Load embedded triggers from workflow definitions.
292
- * New triggers are embedded directly in the definition JSONB.
293
- */
294
- async function loadEmbeddedTriggers(
295
- em: EntityManager,
296
- tenantId: string,
297
- organizationId: string
298
- ): Promise<UnifiedTrigger[]> {
299
- // Load all enabled definitions that may have triggers
300
- const definitions = await em.find(
301
- WorkflowDefinition,
302
- {
303
- tenantId,
304
- organizationId,
305
- enabled: true,
306
- deletedAt: null,
307
- }
308
- )
309
-
310
- const triggers: UnifiedTrigger[] = []
311
-
312
- for (const def of definitions) {
313
- const embeddedTriggers = def.definition?.triggers as WorkflowDefinitionTrigger[] | undefined
314
- if (!embeddedTriggers || embeddedTriggers.length === 0) continue
315
-
316
- for (const trigger of embeddedTriggers) {
317
- if (!trigger.enabled) continue
318
-
319
- triggers.push({
320
- id: `${def.id}:${trigger.triggerId}`,
321
- triggerId: trigger.triggerId,
322
- name: trigger.name,
323
- description: trigger.description ?? null,
324
- eventPattern: trigger.eventPattern,
325
- config: trigger.config ?? null,
326
- enabled: trigger.enabled,
327
- priority: trigger.priority,
328
- workflowDefinitionId: def.id,
329
- workflowId: def.workflowId,
330
- workflowVersion: def.version,
331
- source: 'embedded' as const,
332
- tenantId,
333
- organizationId,
334
- })
335
- }
336
- }
337
-
338
- return triggers
339
- }
340
-
341
- /**
342
- * Load all enabled triggers for a tenant/organization with caching.
343
- * Merges both legacy (entity) triggers and embedded (definition) triggers.
344
- */
345
- export async function loadTriggersForTenant(
346
- em: EntityManager,
347
- tenantId: string,
348
- organizationId: string,
349
- cacheService?: CacheService
350
- ): Promise<UnifiedTrigger[]> {
351
- const cacheKey = getCacheKey(tenantId, organizationId)
352
-
353
- // Check in-memory cache
354
- const cached = triggerCache.get(cacheKey)
355
- if (cached && Date.now() - cached.cachedAt < TRIGGER_CACHE_TTL) {
356
- return cached.triggers
357
- }
358
-
359
- // Load from both sources
360
- const [legacyTriggers, embeddedTriggers] = await Promise.all([
361
- loadLegacyTriggers(em, tenantId, organizationId),
362
- loadEmbeddedTriggers(em, tenantId, organizationId),
363
- ])
364
-
365
- // Merge and sort by priority (higher first)
366
- const allTriggers = [...legacyTriggers, ...embeddedTriggers]
367
- .sort((a, b) => b.priority - a.priority)
368
-
369
- // Update cache
370
- triggerCache.set(cacheKey, {
371
- triggers: allTriggers,
372
- cachedAt: Date.now(),
373
- })
374
-
375
- return allTriggers
376
- }
377
-
378
- /**
379
- * Invalidate trigger cache for a tenant/organization.
380
- * Call this when:
381
- * - Legacy triggers are created/updated/deleted
382
- * - Workflow definitions with embedded triggers are created/updated/deleted
383
- */
384
- export function invalidateTriggerCache(tenantId: string, organizationId?: string): void {
385
- if (organizationId) {
386
- // Invalidate specific org
387
- const cacheKey = getCacheKey(tenantId, organizationId)
388
- triggerCache.delete(cacheKey)
389
- } else {
390
- // Invalidate all orgs for tenant
391
- for (const key of triggerCache.keys()) {
392
- if (key.startsWith(`${tenantId}:`)) {
393
- triggerCache.delete(key)
394
- }
395
- }
396
- }
397
- }
398
-
399
- // ============================================================================
400
- // Trigger Matching
401
- // ============================================================================
402
-
403
- /**
404
- * Find all triggers that match an event.
405
- */
406
- export async function findMatchingTriggers(
407
- em: EntityManager,
408
- context: EventTriggerContext
409
- ): Promise<UnifiedTrigger[]> {
410
- const triggers = await loadTriggersForTenant(
411
- em,
412
- context.tenantId,
413
- context.organizationId
414
- )
415
-
416
- return triggers.filter(trigger => {
417
- // Check event pattern
418
- if (!matchEventPattern(context.eventName, trigger.eventPattern)) {
419
- return false
420
- }
421
-
422
- // Check filter conditions
423
- if (!evaluateFilterConditions(trigger.config?.filterConditions, context.payload)) {
424
- return false
425
- }
426
-
427
- return true
428
- })
429
- }
430
-
431
- // ============================================================================
432
- // Trigger Processing
433
- // ============================================================================
434
-
435
- /**
436
- * Check if max concurrent instances limit is reached.
437
- */
438
- async function checkConcurrencyLimit(
439
- em: EntityManager,
440
- trigger: UnifiedTrigger
441
- ): Promise<boolean> {
442
- const maxInstances = trigger.config?.maxConcurrentInstances
443
-
444
- if (!maxInstances) return true // No limit
445
-
446
- // Count running instances for this trigger's workflow definition
447
- const runningCount = await em.count(WorkflowInstance, {
448
- definitionId: trigger.workflowDefinitionId,
449
- status: { $in: ['RUNNING', 'WAITING_FOR_ACTIVITIES'] },
450
- tenantId: trigger.tenantId,
451
- organizationId: trigger.organizationId,
452
- deletedAt: null,
453
- })
454
-
455
- return runningCount < maxInstances
456
- }
457
-
458
- /**
459
- * Process all matching triggers for an event and start workflows.
460
- */
461
- export async function processEventTriggers(
462
- em: EntityManager,
463
- container: AwilixContainer,
464
- context: EventTriggerContext
465
- ): Promise<ProcessTriggersResult> {
466
- const result: ProcessTriggersResult = {
467
- triggered: 0,
468
- skipped: 0,
469
- errors: [],
470
- instances: [],
471
- }
472
-
473
- // Find matching triggers
474
- const triggers = await findMatchingTriggers(em, context)
475
-
476
- if (triggers.length === 0) {
477
- return result
478
- }
479
-
480
- // Process each trigger (definitions already validated during loading)
481
- for (const trigger of triggers) {
482
- try {
483
- // Check concurrency limit
484
- const canStart = await checkConcurrencyLimit(em, trigger)
485
- if (!canStart) {
486
- console.log(`[workflow-trigger] Skipping trigger "${trigger.name}": max concurrent instances reached`)
487
- result.skipped++
488
- continue
489
- }
490
-
491
- // Map event payload to workflow context
492
- const mappedContext = mapEventToContext(trigger.config?.contextMapping, context.payload)
493
-
494
- // Extract entity info from payload for metadata
495
- const payloadId = context.payload?.id as string | undefined
496
- const payloadEntityType = context.payload?.entityType as string | undefined
497
-
498
- // Include event metadata and payload in context
499
- const initialContext = {
500
- // Include raw event payload fields (e.g., id, organizationId, tenantId)
501
- ...context.payload,
502
- // Override with explicit mappings if provided
503
- ...mappedContext,
504
- __trigger: {
505
- triggerId: trigger.id,
506
- triggerName: trigger.name,
507
- eventName: context.eventName,
508
- eventPayload: context.payload,
509
- triggeredAt: new Date().toISOString(),
510
- source: trigger.source,
511
- },
512
- }
513
-
514
- // Start workflow
515
- const instance = await startWorkflow(em, {
516
- workflowId: trigger.workflowId,
517
- version: trigger.workflowVersion,
518
- initialContext,
519
- metadata: {
520
- initiatedBy: `trigger:${trigger.id}`,
521
- // Include entityId and entityType for widget discovery
522
- entityId: payloadId,
523
- entityType: payloadEntityType || trigger.config?.entityType,
524
- labels: {
525
- trigger_id: trigger.id,
526
- trigger_name: trigger.name,
527
- event_name: context.eventName,
528
- trigger_source: trigger.source,
529
- },
530
- },
531
- tenantId: context.tenantId,
532
- organizationId: context.organizationId,
533
- })
534
-
535
- result.triggered++
536
- result.instances.push({
537
- triggerId: trigger.id,
538
- instanceId: instance.id,
539
- })
540
-
541
- // Execute workflow asynchronously (don't wait)
542
- executeWorkflow(em.fork(), container, instance.id).catch(err => {
543
- console.error(`[workflow-trigger] Error executing workflow ${instance.id}:`, err)
544
- })
545
-
546
- } catch (error) {
547
- const errorMessage = error instanceof Error ? error.message : String(error)
548
- console.error(`[workflow-trigger] Error processing trigger "${trigger.name}":`, error)
549
- result.errors.push({
550
- triggerId: trigger.id,
551
- error: errorMessage,
552
- })
553
- }
554
- }
555
-
556
- return result
557
- }
@@ -1,38 +0,0 @@
1
- import { Migration } from '@mikro-orm/migrations';
2
-
3
- export class Migration20260123143500 extends Migration {
4
-
5
- override async up(): Promise<void> {
6
- // Create workflow_event_triggers table
7
- this.addSql(`
8
- create table "workflow_event_triggers" (
9
- "id" uuid not null default gen_random_uuid(),
10
- "name" varchar(255) not null,
11
- "description" text null,
12
- "workflow_definition_id" uuid not null,
13
- "event_pattern" varchar(255) not null,
14
- "config" jsonb null,
15
- "enabled" bool not null default true,
16
- "priority" int4 not null default 0,
17
- "tenant_id" uuid not null,
18
- "organization_id" uuid not null,
19
- "created_by" varchar(255) null,
20
- "updated_by" varchar(255) null,
21
- "created_at" timestamptz(6) not null,
22
- "updated_at" timestamptz(6) not null,
23
- "deleted_at" timestamptz(6) null,
24
- constraint "workflow_event_triggers_pkey" primary key ("id")
25
- );
26
- `);
27
-
28
- // Create indexes
29
- this.addSql(`create index "workflow_event_triggers_event_pattern_idx" on "workflow_event_triggers" ("event_pattern", "enabled");`);
30
- this.addSql(`create index "workflow_event_triggers_definition_idx" on "workflow_event_triggers" ("workflow_definition_id");`);
31
- this.addSql(`create index "workflow_event_triggers_tenant_org_idx" on "workflow_event_triggers" ("tenant_id", "organization_id");`);
32
- this.addSql(`create index "workflow_event_triggers_enabled_priority_idx" on "workflow_event_triggers" ("enabled", "priority");`);
33
- }
34
-
35
- override async down(): Promise<void> {
36
- this.addSql(`drop table if exists "workflow_event_triggers" cascade;`);
37
- }
38
- }
@@ -1,109 +0,0 @@
1
- /**
2
- * Workflows Module - Event Trigger Subscriber
3
- *
4
- * Wildcard subscriber that listens to all events and evaluates
5
- * workflow event triggers. When a matching trigger is found,
6
- * the corresponding workflow is started with mapped context.
7
- */
8
-
9
- import type { EntityManager } from '@mikro-orm/core'
10
- import type { AwilixContainer } from 'awilix'
11
-
12
- export const metadata = {
13
- event: '*', // Subscribe to ALL events
14
- persistent: true, // Ensure reliability
15
- id: 'workflows:event-trigger',
16
- }
17
-
18
- // Events that should never trigger workflows (internal/system events)
19
- const EXCLUDED_EVENT_PREFIXES = [
20
- 'query_index.', // Internal indexing events
21
- 'search.', // Internal search events
22
- 'workflows.', // Workflow internal events (avoid recursion)
23
- 'cache.', // Cache events
24
- 'queue.', // Queue events
25
- ]
26
-
27
- /**
28
- * Check if an event should be excluded from trigger processing.
29
- */
30
- function isExcludedEvent(eventName: string): boolean {
31
- return EXCLUDED_EVENT_PREFIXES.some(prefix => eventName.startsWith(prefix))
32
- }
33
-
34
- export default async function handle(
35
- payload: unknown,
36
- ctx: { resolve: <T = unknown>(name: string) => T; eventName?: string }
37
- ): Promise<void> {
38
- const eventName = ctx.eventName
39
- if (!eventName) {
40
- // Skip if no event name (shouldn't happen, but be safe)
41
- return
42
- }
43
-
44
- // Skip excluded events
45
- if (isExcludedEvent(eventName)) {
46
- return
47
- }
48
-
49
- // Ensure payload is an object
50
- const eventPayload = (payload && typeof payload === 'object' ? payload : {}) as Record<string, unknown>
51
-
52
- // Extract tenant/org from payload
53
- const tenantId = eventPayload?.tenantId as string | undefined
54
- const organizationId = eventPayload?.organizationId as string | undefined
55
-
56
- // Skip events without tenant context
57
- if (!tenantId || !organizationId) {
58
- return
59
- }
60
-
61
- // Get dependencies from container
62
- let em: EntityManager
63
- let container: AwilixContainer
64
-
65
- try {
66
- em = ctx.resolve<EntityManager>('em')
67
- // Create a minimal container wrapper using the resolve function
68
- // This avoids the need to register 'container' as a self-reference in DI
69
- container = {
70
- resolve: ctx.resolve,
71
- // Provide minimal AwilixContainer interface for type compatibility
72
- cradle: new Proxy({}, {
73
- get: (_target, prop: string) => ctx.resolve(prop),
74
- }),
75
- } as unknown as AwilixContainer
76
- } catch (error) {
77
- // DI not available - skip
78
- console.warn(`[workflow-trigger] Cannot resolve dependencies for event "${eventName}":`, error)
79
- return
80
- }
81
-
82
- // Import service dynamically to avoid circular dependencies
83
- const { processEventTriggers } = await import('../lib/event-trigger-service')
84
-
85
- try {
86
- const result = await processEventTriggers(em, container, {
87
- eventName,
88
- payload: eventPayload,
89
- tenantId,
90
- organizationId,
91
- })
92
-
93
- if (result.triggered > 0) {
94
- console.log(
95
- `[workflow-trigger] Triggered ${result.triggered} workflow(s) for "${eventName}"` +
96
- (result.skipped > 0 ? ` (${result.skipped} skipped)` : '') +
97
- (result.errors.length > 0 ? ` (${result.errors.length} errors)` : '')
98
- )
99
- }
100
-
101
- if (result.errors.length > 0) {
102
- for (const err of result.errors) {
103
- console.error(`[workflow-trigger] Trigger ${err.triggerId} failed:`, err.error)
104
- }
105
- }
106
- } catch (error) {
107
- console.error(`[workflow-trigger] Error processing triggers for "${eventName}":`, error)
108
- }
109
- }