@onlineapps/conn-orch-orchestrator 1.0.42 → 1.0.44

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-orch-orchestrator",
3
- "version": "1.0.42",
3
+ "version": "1.0.44",
4
4
  "description": "Workflow orchestration connector for OA Drive - handles message routing and workflow execution",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -21,14 +21,14 @@
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
23
  "@onlineapps/conn-base-monitoring": "^1.0.0",
24
- "@onlineapps/conn-infra-mq": "1.1.54",
25
- "@onlineapps/conn-orch-api-mapper": "^1.0.0",
24
+ "@onlineapps/conn-infra-mq": "^1.1.0",
25
+ "@onlineapps/conn-orch-registry": "^1.1.4",
26
26
  "@onlineapps/conn-orch-cookbook": "2.0.4",
27
- "@onlineapps/conn-orch-registry": "^1.1.4"
27
+ "@onlineapps/conn-orch-api-mapper": "^1.0.0"
28
28
  },
29
29
  "devDependencies": {
30
- "eslint": "^8.30.0",
31
30
  "jest": "^29.5.0",
31
+ "eslint": "^8.30.0",
32
32
  "jsdoc-to-markdown": "^8.0.0"
33
33
  },
34
34
  "jest": {
@@ -37,6 +37,14 @@ class WorkflowOrchestrator {
37
37
  this.logger = config.logger || console;
38
38
  this.defaultTimeout = config.defaultTimeout || 30000;
39
39
 
40
+ // Retry configuration for DLQ mechanism
41
+ this.retryConfig = {
42
+ maxAttempts: config.maxRetryAttempts || 3,
43
+ baseDelay: config.retryBaseDelay || 1000,
44
+ maxDelay: config.retryMaxDelay || 30000,
45
+ backoffMultiplier: config.retryBackoffMultiplier || 2
46
+ };
47
+
40
48
  // Create cookbook router - createRouter is an exported function, not a method
41
49
  const { createRouter } = this.cookbook;
42
50
  if (typeof createRouter === 'function') {
@@ -230,46 +238,131 @@ class WorkflowOrchestrator {
230
238
  };
231
239
 
232
240
  } catch (error) {
233
- // Publish to workflow.failed queue
241
+ // DLQ/Retry mechanism: retry with exponential backoff before sending to DLQ
242
+ const dlqHistory = context._dlq_history || [];
243
+ const stepAttempts = dlqHistory.filter(h => h.step_id === current_step && h.attempt).length;
244
+ const attemptNumber = stepAttempts + 1;
245
+
246
+ // Calculate step_index for failed step
247
+ let failedStepIndex = 0;
248
+ try {
249
+ const stepsArr = this._getStepsArray(cookbookDef);
250
+ const idx = stepsArr.findIndex(s => this._getStepId(s) === current_step);
251
+ if (idx >= 0) failedStepIndex = idx;
252
+ } catch (e) {
253
+ // Ignore - use default 0
254
+ }
255
+
256
+ // Add attempt to DLQ history
257
+ const attemptRecord = {
258
+ step_id: current_step,
259
+ step_index: failedStepIndex,
260
+ attempt: attemptNumber,
261
+ service: serviceName,
262
+ error: error.message,
263
+ at: new Date().toISOString()
264
+ };
265
+
266
+ const updatedDlqHistory = [...dlqHistory, attemptRecord];
267
+ const updatedContext = {
268
+ ...context,
269
+ _dlq_history: updatedDlqHistory
270
+ };
271
+
272
+ this.logger.warn(`[WorkflowOrchestrator] Step failed, attempt ${attemptNumber}/${this.retryConfig.maxAttempts}`, {
273
+ workflow_id,
274
+ current_step,
275
+ service: serviceName,
276
+ error: error.message,
277
+ attempt: attemptNumber
278
+ });
279
+
280
+ // Check if we should retry
281
+ if (attemptNumber < this.retryConfig.maxAttempts) {
282
+ // Calculate delay with exponential backoff
283
+ const delay = Math.min(
284
+ this.retryConfig.baseDelay * Math.pow(this.retryConfig.backoffMultiplier, attemptNumber - 1),
285
+ this.retryConfig.maxDelay
286
+ );
287
+
288
+ this.logger.info(`[WorkflowOrchestrator] Scheduling retry ${attemptNumber + 1} in ${delay}ms`, {
289
+ workflow_id,
290
+ current_step,
291
+ delay
292
+ });
293
+
294
+ // Wait for delay
295
+ await new Promise(resolve => setTimeout(resolve, delay));
296
+
297
+ // Republish to same service queue for retry
298
+ const serviceQueue = `${serviceName}.workflow`;
299
+ await this.mqClient.publish(serviceQueue, {
300
+ workflow_id,
301
+ cookbook: cookbookDef,
302
+ current_step,
303
+ context: updatedContext
304
+ });
305
+
306
+ this.logger.info(`[WorkflowOrchestrator] Message republished for retry`, {
307
+ workflow_id,
308
+ current_step,
309
+ queue: serviceQueue,
310
+ nextAttempt: attemptNumber + 1
311
+ });
312
+
313
+ // Don't throw - message will be retried
314
+ return { retrying: true, attempt: attemptNumber, nextAttempt: attemptNumber + 1 };
315
+ }
316
+
317
+ // Max retries exhausted - send to workflow.failed (DLQ entry)
318
+ const dlqEntryRecord = {
319
+ dlq_entered: true,
320
+ step_id: current_step,
321
+ step_index: failedStepIndex,
322
+ service: serviceName,
323
+ reason: 'max_retries_exceeded',
324
+ total_attempts: attemptNumber,
325
+ at: new Date().toISOString()
326
+ };
327
+
328
+ const finalDlqHistory = [...updatedDlqHistory, dlqEntryRecord];
329
+ const finalContext = {
330
+ ...context,
331
+ _dlq_history: finalDlqHistory
332
+ };
333
+
234
334
  try {
235
- console.log(`[WorkflowOrchestrator] [PUBLISH] Preparing to publish workflow.failed for ${workflow_id}`);
236
- this.logger.error(`[WorkflowOrchestrator] [PUBLISH] Workflow processing failed: ${error.message}`, {
335
+ console.log(`[WorkflowOrchestrator] [DLQ] Max retries exhausted, publishing to workflow.failed for ${workflow_id}`);
336
+ this.logger.error(`[WorkflowOrchestrator] [DLQ] Workflow entering DLQ after ${attemptNumber} attempts: ${error.message}`, {
237
337
  workflow_id,
238
338
  current_step,
339
+ attempts: attemptNumber,
239
340
  error: error.stack
240
341
  });
241
342
 
242
343
  await this.mqClient.publish('workflow.failed', {
243
344
  workflow_id,
244
345
  current_step,
346
+ step_id: current_step,
347
+ step_index: failedStepIndex,
348
+ service: serviceName,
349
+ cookbook: cookbookDef,
350
+ context: finalContext,
245
351
  error: error.message,
246
352
  errorStack: error.stack,
353
+ _dlq_history: finalDlqHistory,
247
354
  failed_at: new Date().toISOString()
248
355
  });
249
356
 
250
- console.log(`[WorkflowOrchestrator] [PUBLISH] ✓ Successfully published workflow.failed for ${workflow_id}`);
251
- this.logger.info(`[WorkflowOrchestrator] [PUBLISH] ✓ Published workflow.failed: ${workflow_id}`);
357
+ console.log(`[WorkflowOrchestrator] [DLQ] ✓ Successfully published workflow.failed for ${workflow_id}`);
358
+ this.logger.info(`[WorkflowOrchestrator] [DLQ] ✓ Published workflow.failed: ${workflow_id}`);
252
359
  } catch (publishError) {
253
- console.error(`[WorkflowOrchestrator] [PUBLISH] ✗ Failed to publish workflow.failed for ${workflow_id}:`, publishError.message);
254
- this.logger.error(`[WorkflowOrchestrator] [PUBLISH] ✗ Failed to publish workflow.failed`, {
360
+ console.error(`[WorkflowOrchestrator] [DLQ] ✗ Failed to publish workflow.failed for ${workflow_id}:`, publishError.message);
361
+ this.logger.error(`[WorkflowOrchestrator] [DLQ] ✗ Failed to publish workflow.failed`, {
255
362
  workflow_id,
256
363
  originalError: error.message,
257
364
  publishError: publishError.message
258
365
  });
259
- // Don't throw - original error is more important
260
- }
261
-
262
- // Handle error
263
- if (this.errorHandler) {
264
- await this.errorHandler.handleError(error, {
265
- workflow_id,
266
- current_step,
267
- service: serviceName,
268
- context
269
- });
270
- } else {
271
- // Fallback to router DLQ
272
- await this.router.routeToDLQ(cookbookDef, context, error, serviceName);
273
366
  }
274
367
 
275
368
  throw error;