@open-mercato/core 0.6.7-develop.6676.1.7dad6df292 → 0.6.7-develop.6685.1.77c0a5591b

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 (71) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/gateway_payment_operation/index.js +2 -0
  3. package/dist/generated/entities/gateway_payment_operation/index.js.map +2 -2
  4. package/dist/generated/entities/gateway_transaction/index.js +2 -0
  5. package/dist/generated/entities/gateway_transaction/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +2 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/payment_gateways/api/capture/route.js +1 -1
  9. package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
  10. package/dist/modules/payment_gateways/data/entities.js +7 -0
  11. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  12. package/dist/modules/payment_gateways/lib/capture-ledger.js +183 -0
  13. package/dist/modules/payment_gateways/lib/capture-ledger.js.map +7 -0
  14. package/dist/modules/payment_gateways/lib/gateway-service.js +70 -8
  15. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  16. package/dist/modules/payment_gateways/lib/payment-operation-idempotency.js +1 -0
  17. package/dist/modules/payment_gateways/lib/payment-operation-idempotency.js.map +2 -2
  18. package/dist/modules/payment_gateways/migrations/Migration20260725101500_payment_gateways.js +16 -0
  19. package/dist/modules/payment_gateways/migrations/Migration20260725101500_payment_gateways.js.map +7 -0
  20. package/dist/modules/query_index/lib/indexer.js +12 -2
  21. package/dist/modules/query_index/lib/indexer.js.map +2 -2
  22. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +5 -2
  23. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  24. package/dist/modules/workflows/components/ActivitiesEditor.js +67 -11
  25. package/dist/modules/workflows/components/ActivitiesEditor.js.map +2 -2
  26. package/dist/modules/workflows/components/TransitionsEditor.js +205 -163
  27. package/dist/modules/workflows/components/TransitionsEditor.js.map +2 -2
  28. package/dist/modules/workflows/data/validators.js +34 -1
  29. package/dist/modules/workflows/data/validators.js.map +3 -3
  30. package/dist/modules/workflows/lib/activity-executor.js +18 -2
  31. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  32. package/dist/modules/workflows/lib/format-validation-error.js +46 -1
  33. package/dist/modules/workflows/lib/format-validation-error.js.map +2 -2
  34. package/generated/entities/gateway_payment_operation/index.ts +1 -0
  35. package/generated/entities/gateway_transaction/index.ts +1 -0
  36. package/generated/entity-fields-registry.ts +2 -0
  37. package/package.json +7 -7
  38. package/src/modules/attachments/i18n/de.json +12 -0
  39. package/src/modules/attachments/i18n/en.json +12 -0
  40. package/src/modules/attachments/i18n/es.json +12 -0
  41. package/src/modules/attachments/i18n/pl.json +12 -0
  42. package/src/modules/auth/i18n/de.json +1 -0
  43. package/src/modules/auth/i18n/en.json +1 -0
  44. package/src/modules/auth/i18n/es.json +1 -0
  45. package/src/modules/auth/i18n/pl.json +1 -0
  46. package/src/modules/catalog/i18n/de.json +18 -0
  47. package/src/modules/catalog/i18n/en.json +18 -0
  48. package/src/modules/catalog/i18n/es.json +18 -0
  49. package/src/modules/catalog/i18n/pl.json +18 -0
  50. package/src/modules/customers/i18n/de.json +44 -0
  51. package/src/modules/customers/i18n/en.json +44 -0
  52. package/src/modules/customers/i18n/es.json +44 -0
  53. package/src/modules/customers/i18n/pl.json +44 -0
  54. package/src/modules/payment_gateways/api/capture/route.ts +1 -1
  55. package/src/modules/payment_gateways/data/entities.ts +8 -2
  56. package/src/modules/payment_gateways/lib/capture-ledger.ts +260 -0
  57. package/src/modules/payment_gateways/lib/gateway-service.ts +83 -10
  58. package/src/modules/payment_gateways/lib/payment-operation-idempotency.ts +1 -0
  59. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +34 -0
  60. package/src/modules/payment_gateways/migrations/Migration20260725101500_payment_gateways.ts +21 -0
  61. package/src/modules/query_index/lib/indexer.ts +25 -2
  62. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +12 -3
  63. package/src/modules/workflows/components/ActivitiesEditor.tsx +96 -12
  64. package/src/modules/workflows/components/TransitionsEditor.tsx +75 -17
  65. package/src/modules/workflows/data/validators.ts +52 -1
  66. package/src/modules/workflows/i18n/de.json +2 -0
  67. package/src/modules/workflows/i18n/en.json +2 -0
  68. package/src/modules/workflows/i18n/es.json +2 -0
  69. package/src/modules/workflows/i18n/pl.json +2 -0
  70. package/src/modules/workflows/lib/activity-executor.ts +38 -3
  71. package/src/modules/workflows/lib/format-validation-error.ts +60 -0
@@ -202,6 +202,20 @@ export const activityRetryPolicySchema = z.object({
202
202
  maxIntervalMs: z.number().int().min(0),
203
203
  })
204
204
 
205
+ /**
206
+ * Config fields each activity executor requires to run (see
207
+ * `lib/activity-executor.ts`). WAIT is handled separately below because it
208
+ * takes "duration" OR "until" rather than a fixed key set.
209
+ */
210
+ const REQUIRED_ACTIVITY_CONFIG_KEYS: Partial<Record<ActivityType, readonly string[]>> = {
211
+ SEND_EMAIL: ['to', 'subject'],
212
+ CALL_API: ['endpoint'],
213
+ CALL_WEBHOOK: ['url'],
214
+ UPDATE_ENTITY: ['commandId', 'input'],
215
+ EMIT_EVENT: ['eventName'],
216
+ EXECUTE_FUNCTION: ['functionName'],
217
+ }
218
+
205
219
  // Activity definition (embedded in transitions)
206
220
  export const activityDefinitionSchema = z.object({
207
221
  activityId: z.string().min(1).max(100).regex(/^[a-z0-9_-]+$/, 'Activity ID must contain only lowercase letters, numbers, hyphens, and underscores'),
@@ -210,12 +224,49 @@ export const activityDefinitionSchema = z.object({
210
224
  config: z.record(z.string(), z.any()),
211
225
  async: z.boolean().default(false).optional(), // For Phase 8.3
212
226
  retryPolicy: activityRetryPolicySchema.optional(),
213
- timeout: z.string().optional(), // ISO 8601 duration
227
+ /**
228
+ * Per-activity timeout in milliseconds. This is what the editor writes and
229
+ * what `executeActivity` reads; it was missing from the schema, so
230
+ * `z.object()` stripped it on save and UI-configured timeouts silently did
231
+ * nothing (#4424).
232
+ */
233
+ timeoutMs: z.number().int().positive().optional(),
234
+ /**
235
+ * @deprecated Use `timeoutMs`. Accepted for definitions already stored with
236
+ * an ISO 8601 duration string; the executor normalizes it to milliseconds.
237
+ */
238
+ timeout: z.string().optional(),
214
239
  compensation: z.object({
215
240
  activityId: z.string().min(1), // ID of compensation activity
216
241
  automatic: z.boolean().default(true).optional() // Auto-trigger on failure
217
242
  }).optional(), // Compensation configuration (Phase 8.2)
218
243
  }).superRefine((activity, ctx) => {
244
+ // Config keys each activity executor requires at runtime. Without this, an
245
+ // activity missing e.g. CALL_API's `endpoint` saved cleanly from the visual
246
+ // editor and only failed once an instance ran it — the "edit silently
247
+ // disappeared / nothing told me why" report in #4232 (and the class of bug
248
+ // #4322 was an instance of). Validating here surfaces the exact missing field
249
+ // at edit time, since both the editor's save path and the API route parse
250
+ // through this schema.
251
+ const requiredConfigKeys = REQUIRED_ACTIVITY_CONFIG_KEYS[activity.activityType]
252
+ if (requiredConfigKeys) {
253
+ const config = activity.config || {}
254
+ for (const key of requiredConfigKeys) {
255
+ const value = config[key]
256
+ const isEmpty =
257
+ value == null ||
258
+ (typeof value === 'string' && value.trim() === '') ||
259
+ (key === 'input' && typeof value !== 'object')
260
+ if (isEmpty) {
261
+ ctx.addIssue({
262
+ code: 'custom',
263
+ path: ['config', key],
264
+ message: `${activity.activityType} activity requires "${key}"`,
265
+ })
266
+ }
267
+ }
268
+ }
269
+
219
270
  if (activity.activityType !== 'WAIT') return
220
271
  const config = activity.config || {}
221
272
  const hasDuration = config.duration != null && config.duration !== ''
@@ -80,6 +80,8 @@
80
80
  "workflows.activities.async": "Asynchron",
81
81
  "workflows.activities.compensation": "Kompensation",
82
82
  "workflows.activities.config": "Konfiguration",
83
+ "workflows.activities.configInvalidJson": "Ungültiges JSON",
84
+ "workflows.activities.configMustBeObject": "Konfiguration muss ein JSON-Objekt sein",
83
85
  "workflows.activities.deleteActivity": "Aktivität löschen",
84
86
  "workflows.activities.editActivity": "Aktivität bearbeiten",
85
87
  "workflows.activities.plural": "Aktivitäten",
@@ -80,6 +80,8 @@
80
80
  "workflows.activities.async": "Asynchronous",
81
81
  "workflows.activities.compensation": "Compensation",
82
82
  "workflows.activities.config": "Configuration",
83
+ "workflows.activities.configInvalidJson": "Invalid JSON",
84
+ "workflows.activities.configMustBeObject": "Config must be a JSON object",
83
85
  "workflows.activities.deleteActivity": "Delete Activity",
84
86
  "workflows.activities.editActivity": "Edit Activity",
85
87
  "workflows.activities.plural": "Activities",
@@ -80,6 +80,8 @@
80
80
  "workflows.activities.async": "Asincrona",
81
81
  "workflows.activities.compensation": "Compensacion",
82
82
  "workflows.activities.config": "Configuracion",
83
+ "workflows.activities.configInvalidJson": "JSON no válido",
84
+ "workflows.activities.configMustBeObject": "La configuración debe ser un objeto JSON",
83
85
  "workflows.activities.deleteActivity": "Eliminar actividad",
84
86
  "workflows.activities.editActivity": "Editar actividad",
85
87
  "workflows.activities.plural": "Actividades",
@@ -80,6 +80,8 @@
80
80
  "workflows.activities.async": "Asynchroniczne",
81
81
  "workflows.activities.compensation": "Kompensacja",
82
82
  "workflows.activities.config": "Konfiguracja",
83
+ "workflows.activities.configInvalidJson": "Nieprawidłowy JSON",
84
+ "workflows.activities.configMustBeObject": "Konfiguracja musi być obiektem JSON",
83
85
  "workflows.activities.deleteActivity": "Usuń Działanie",
84
86
  "workflows.activities.editActivity": "Edytuj Działanie",
85
87
  "workflows.activities.plural": "Działania",
@@ -107,9 +107,42 @@ export interface ActivityDefinition {
107
107
  async?: boolean // Flag to execute activity asynchronously via queue
108
108
  retryPolicy?: RetryPolicy
109
109
  timeoutMs?: number
110
+ /**
111
+ * @deprecated Use `timeoutMs`. Legacy ISO 8601 duration string accepted by
112
+ * the definition schema before #4424; normalized by `resolveActivityTimeoutMs`.
113
+ */
114
+ timeout?: string
110
115
  compensate?: boolean // Flag to execute compensation on failure
111
116
  }
112
117
 
118
+ /**
119
+ * Effective timeout for an activity, in milliseconds.
120
+ *
121
+ * The editor and this executor both speak `timeoutMs`, but the definition
122
+ * schema historically accepted only an ISO 8601 `timeout` string — so stored
123
+ * definitions can carry either. Prefer `timeoutMs`; fall back to parsing
124
+ * `timeout`, ignoring a malformed value rather than throwing mid-execution
125
+ * (an unparseable timeout must not fail an activity that would otherwise
126
+ * succeed). Returns undefined when no usable timeout is configured (#4424).
127
+ */
128
+ export function resolveActivityTimeoutMs(activity: {
129
+ timeoutMs?: number
130
+ timeout?: string
131
+ }): number | undefined {
132
+ if (typeof activity.timeoutMs === 'number' && activity.timeoutMs > 0) {
133
+ return activity.timeoutMs
134
+ }
135
+ if (typeof activity.timeout === 'string' && activity.timeout.trim().length > 0) {
136
+ try {
137
+ const parsed = parseDuration(activity.timeout.trim())
138
+ if (Number.isFinite(parsed) && parsed > 0) return parsed
139
+ } catch {
140
+ return undefined
141
+ }
142
+ }
143
+ return undefined
144
+ }
145
+
113
146
  export interface RetryPolicy {
114
147
  maxAttempts: number
115
148
  initialIntervalMs: number
@@ -332,11 +365,13 @@ export async function executeActivity(
332
365
  try {
333
366
  const startTime = Date.now()
334
367
 
335
- // Execute with timeout if specified
336
- const result = activity.timeoutMs
368
+ // Execute with timeout if specified (timeoutMs, or a legacy ISO 8601
369
+ // `timeout` string normalized to ms — see resolveActivityTimeoutMs).
370
+ const timeoutMs = resolveActivityTimeoutMs(activity)
371
+ const result = timeoutMs
337
372
  ? await executeWithTimeout(
338
373
  () => executeActivityByType(em, container, activity, context),
339
- activity.timeoutMs
374
+ timeoutMs
340
375
  )
341
376
  : await executeActivityByType(em, container, activity, context)
342
377
 
@@ -8,6 +8,66 @@ type ApiErrorBody = {
8
8
  details?: ZodIssueLite[]
9
9
  }
10
10
 
11
+ // Collection segments in a definition path, mapped to the label an operator
12
+ // recognizes from the editor. Indexes are rendered 1-based.
13
+ const COLLECTION_LABELS: Record<string, string> = {
14
+ steps: 'step',
15
+ transitions: 'transition',
16
+ activities: 'activity',
17
+ triggers: 'trigger',
18
+ preConditions: 'pre-condition',
19
+ }
20
+
21
+ /**
22
+ * Turn a raw Zod path into something an operator can act on:
23
+ * `steps.2.activities.0.config.endpoint` → `step 3 › activity 1 › config.endpoint`.
24
+ *
25
+ * The raw dotted path reads as internal JSON and gives no hint which node to
26
+ * open in the visual editor (#4232).
27
+ */
28
+ export function humanizeDefinitionIssuePath(path: ReadonlyArray<PropertyKey>): string {
29
+ if (!path.length) return 'definition'
30
+ const parts: string[] = []
31
+ let pending: string | null = null
32
+
33
+ for (const rawSegment of path) {
34
+ // Zod types issue paths as PropertyKey; symbols can't appear in JSON data
35
+ // but narrow defensively rather than casting.
36
+ const segment = typeof rawSegment === 'symbol' ? rawSegment.toString() : rawSegment
37
+ if (typeof segment === 'number') {
38
+ parts.push(pending ? `${pending} ${segment + 1}` : `#${segment + 1}`)
39
+ pending = null
40
+ continue
41
+ }
42
+ const label = COLLECTION_LABELS[segment]
43
+ if (label) {
44
+ if (pending) parts.push(pending)
45
+ pending = label
46
+ continue
47
+ }
48
+ if (pending) {
49
+ parts.push(pending)
50
+ pending = null
51
+ }
52
+ parts.push(segment)
53
+ }
54
+ if (pending) parts.push(pending)
55
+
56
+ const [head, ...rest] = parts
57
+ if (!rest.length) return head
58
+ // Keep trailing field names dotted (config.endpoint), collections chevroned.
59
+ const grouped: string[] = [head]
60
+ for (const part of rest) {
61
+ const isIndexed = /\s\d+$/.test(part) || part.startsWith('#')
62
+ if (!isIndexed && grouped.length > 0 && !/\s\d+$/.test(grouped[grouped.length - 1]) && !grouped[grouped.length - 1].startsWith('#')) {
63
+ grouped[grouped.length - 1] = `${grouped[grouped.length - 1]}.${part}`
64
+ continue
65
+ }
66
+ grouped.push(part)
67
+ }
68
+ return grouped.join(' › ')
69
+ }
70
+
11
71
  /**
12
72
  * Format an API validation error body into a user-readable message.
13
73
  *