@onlineapps/service-wrapper 2.0.20 → 2.0.22

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 (33) hide show
  1. package/API.md +0 -0
  2. package/README.md +0 -0
  3. package/docs/CONFIGURATION_GUIDE.md +0 -0
  4. package/docs/FINAL_ARCHITECTURE.md +0 -0
  5. package/docs/MIGRATION_FROM_OPENAPI.md +0 -0
  6. package/docs/STANDARDS_OVERVIEW.md +0 -0
  7. package/docs/archived-2025-09-29/API_STRUCTURE_STANDARD.md +0 -0
  8. package/docs/archived-2025-09-29/ARCHITECTURE_DECISION.md +0 -0
  9. package/docs/archived-2025-09-29/HANDLER_VS_HTTP_COMPARISON.md +0 -0
  10. package/docs/archived-2025-09-29/INSTALLATION_GUIDE.md +0 -0
  11. package/docs/archived-2025-09-29/OPERATIONS_SCHEMA.md +0 -0
  12. package/docs/archived-2025-09-29/PERFORMANCE.md +0 -0
  13. package/docs/archived-2025-09-29/PROCESS_FLOWS.md +0 -0
  14. package/docs/archived-2025-09-29/SERVICE_TESTING_STANDARD.md +0 -0
  15. package/docs/archived-2025-09-29/WRAPPER_ARCHITECTURE.md +0 -0
  16. package/examples/README.md +0 -0
  17. package/examples/cookbook-file-output.json +0 -0
  18. package/examples/cookbook-multi-step.json +0 -0
  19. package/examples/cookbook-single-operation.json +0 -0
  20. package/jest.config.js +0 -0
  21. package/jsdoc.json +0 -0
  22. package/package.json +1 -1
  23. package/src/ServiceWrapper.js +120 -25
  24. package/src/index.js +0 -0
  25. package/tests/component/ServiceWrapper.component.test.js +0 -407
  26. package/tests/component/connector-integration.test.js +0 -293
  27. package/tests/e2e/full-flow.test.js +0 -293
  28. package/tests/integration/orchestrator-integration.test.js +0 -170
  29. package/tests/mocks/connectors.js +0 -304
  30. package/tests/monitoring-integration.test.js +0 -150
  31. package/tests/run-tests.js +0 -135
  32. package/tests/setup.js +0 -31
  33. package/tests/unit/ServiceWrapper.test.js +0 -372
package/API.md CHANGED
File without changes
package/README.md CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/jest.config.js CHANGED
File without changes
package/jsdoc.json CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/service-wrapper",
3
- "version": "2.0.20",
3
+ "version": "2.0.22",
4
4
  "description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -37,6 +37,7 @@ class ServiceWrapper {
37
37
  * @param {Object} options.operations - Operations schema
38
38
  * @param {string} [options.serviceRoot] - Service root directory (for validation)
39
39
  * @param {Object} [options.validationProof=null] - Optional validation proof {hash, data}
40
+ * @param {Object} [options._validationOrchestrator=null] - Optional ValidationOrchestrator for testing
40
41
  */
41
42
  constructor(options = {}) {
42
43
  this._validateOptions(options);
@@ -48,6 +49,7 @@ class ServiceWrapper {
48
49
  this.operations = options.operations;
49
50
  this.serviceRoot = options.serviceRoot;
50
51
  this.validationProof = options.validationProof || null;
52
+ this._injectedValidationOrchestrator = options._validationOrchestrator || null;
51
53
 
52
54
  // Initialize connector placeholders
53
55
  this.mqClient = null;
@@ -149,19 +151,26 @@ class ServiceWrapper {
149
151
  await this._initializeMonitoring();
150
152
  }
151
153
 
152
- // 2. Run pre-validation (Tier 1)
154
+ // 2. Initialize MQ connection (needed for workflow.init listener)
155
+ if (this.config.wrapper?.mq?.enabled !== false) {
156
+ await this._initializeMQ();
157
+
158
+ // Start workflow.init listener immediately (independent of service registration)
159
+ // This allows the system to receive workflow messages even if service validation/registration fails
160
+ await this._startWorkflowInitListener();
161
+ }
162
+
163
+ // 3. Run pre-validation (Tier 1)
153
164
  // Validates service structure, config, operations, cookbook tests
154
165
  // Skips if valid proof exists in conn-runtime/validation-proof.json
166
+ // NOTE: Validation is required for service-specific workflow listener and registration
155
167
  if (this.serviceRoot && this.config.wrapper?.validation?.enabled !== false) {
156
168
  await this._ensureValidationProof();
157
169
  }
158
170
 
159
- // 3. Initialize MQ connection
160
- if (this.config.wrapper?.mq?.enabled !== false) {
161
- await this._initializeMQ();
162
- }
163
-
164
171
  // 4. Initialize service registry
172
+ // NOTE: Service-specific workflow listener ({serviceName}.workflow) will be started
173
+ // ONLY after successful validation and registration (see _initializeRegistry)
165
174
  if (this.config.wrapper?.registry?.enabled !== false) {
166
175
  await this._initializeRegistry();
167
176
  }
@@ -306,12 +315,13 @@ class ServiceWrapper {
306
315
  console.log(`✓ Certificate received: ${registrationResult.certificate.certificateId}`);
307
316
  }
308
317
 
309
- // CRITICAL: Only start workflow listeners after successful registration with certificate
318
+ // CRITICAL: Only start service-specific workflow listener after successful validation and registration with certificate
319
+ // workflow.init listener is already running (started during MQ initialization)
310
320
  if (registrationResult.success && registrationResult.certificate) {
311
- console.log('✓ Certificate validated, starting workflow listeners...');
312
- await this._startWorkflowListeners();
321
+ console.log('✓ Certificate validated, starting service-specific workflow listener...');
322
+ await this._startServiceWorkflowListener();
313
323
  } else {
314
- console.warn('⚠ Registration succeeded but no certificate received - workflow listeners NOT started');
324
+ console.warn('⚠ Registration succeeded but no certificate received - service-specific workflow listener NOT started');
315
325
  }
316
326
 
317
327
  } catch (error) {
@@ -401,12 +411,25 @@ class ServiceWrapper {
401
411
  const servicePort = this.config.service?.port || process.env.PORT || 3000;
402
412
  const serviceUrl = `http://localhost:${servicePort}`;
403
413
 
404
- // Create error handler
414
+ // Create error handler with monitoring instance
405
415
  const errorHandler = new ErrorHandlerConnector({
406
- maxRetries: this.config.wrapper?.errorHandling?.maxRetries || 3,
407
- retryDelay: this.config.wrapper?.errorHandling?.retryDelay || 1000,
408
- logger: this.logger || console
416
+ serviceName: this.config.service?.name || 'unnamed-service',
417
+ serviceVersion: this.config.service?.version || '0.0.0',
418
+ environment: process.env.NODE_ENV || 'development',
419
+ monitoring: this.monitoring, // Use monitoring instance from conn-base-monitoring
420
+ handling: {
421
+ maxRetries: this.config.wrapper?.errorHandling?.maxRetries || 3,
422
+ retryDelay: this.config.wrapper?.errorHandling?.retryDelay || 1000,
423
+ retryMultiplier: this.config.wrapper?.errorHandling?.retryMultiplier || 2,
424
+ circuitBreakerEnabled: this.config.wrapper?.errorHandling?.circuitBreakerEnabled !== false,
425
+ dlqEnabled: this.config.wrapper?.errorHandling?.dlqEnabled !== false,
426
+ mqClient: this.mqClient, // For DLQ routing
427
+ compensationEnabled: this.config.wrapper?.errorHandling?.compensationEnabled !== false
428
+ }
409
429
  });
430
+
431
+ // Store error handler for use in message processing
432
+ this.errorHandler = errorHandler;
410
433
 
411
434
  // Create API mapper for HTTP calls (never direct calls)
412
435
  const apiMapper = ApiMapperConnector.create({
@@ -432,25 +455,43 @@ class ServiceWrapper {
432
455
  }
433
456
 
434
457
  /**
435
- * Start workflow listeners - ONLY called after receiving certificate from Registry
436
- * CRITICAL: Unregistered services MUST NOT consume workflow messages
458
+ * Start workflow.init listener (runs independently of service registration)
459
+ * This allows the system to receive workflow messages even if service validation/registration fails
437
460
  * @private
438
461
  */
439
- async _startWorkflowListeners() {
440
- const serviceName = this.config.service?.name || 'unnamed-service';
462
+ async _startWorkflowInitListener() {
463
+ if (!this.mqClient) {
464
+ console.warn('Cannot start workflow.init listener: MQ client not initialized');
465
+ return;
466
+ }
441
467
 
442
468
  // Subscribe to workflow.init queue (for all services)
443
469
  await this.mqClient.consume('workflow.init', async (message) => {
444
470
  await this._processWorkflowMessage(message, 'workflow.init');
445
471
  });
446
472
 
447
- // Subscribe to service-specific queue
473
+ console.log('✓ workflow.init listener started (independent of service registration)');
474
+ }
475
+
476
+ /**
477
+ * Start service-specific workflow listener (runs only after successful validation and registration)
478
+ * @private
479
+ */
480
+ async _startServiceWorkflowListener() {
481
+ if (!this.mqClient) {
482
+ console.warn('Cannot start service workflow listener: MQ client not initialized');
483
+ return;
484
+ }
485
+
486
+ const serviceName = this.config.service?.name || 'unnamed-service';
448
487
  const serviceQueue = `${serviceName}.workflow`;
488
+
489
+ // Subscribe to service-specific queue
449
490
  await this.mqClient.consume(serviceQueue, async (message) => {
450
491
  await this._processWorkflowMessage(message, serviceQueue);
451
492
  });
452
493
 
453
- console.log(`Subscribed to queues: workflow.init, ${serviceQueue}`);
494
+ console.log(`✓ Service-specific workflow listener started: ${serviceQueue}`);
454
495
  }
455
496
 
456
497
  /**
@@ -517,17 +558,61 @@ class ServiceWrapper {
517
558
  };
518
559
 
519
560
  try {
561
+ // Detailed logging before publish
562
+ this.logger?.info(`[ServiceWrapper] Preparing to publish workflow.completed`, {
563
+ workflowId: message.workflow_id,
564
+ service: serviceName,
565
+ operation: workflowResponse.operation,
566
+ mqClientConnected: this.mqClient._connected,
567
+ hasTransport: !!this.mqClient._transport,
568
+ hasChannel: !!(this.mqClient._transport?._channel)
569
+ });
570
+
520
571
  await this.mqClient.publish('workflow.completed', workflowResponse);
521
- console.log(`✓ Workflow response sent to workflow.completed: ${message.workflow_id}`);
572
+
573
+ // Log success with details
574
+ this.logger?.info(`✓ Workflow response sent to workflow.completed: ${message.workflow_id}`, {
575
+ workflowId: message.workflow_id,
576
+ queue: 'workflow.completed',
577
+ responseSize: JSON.stringify(workflowResponse).length
578
+ });
522
579
  } catch (error) {
523
- console.error(`Failed to send workflow response:`, error);
580
+ // Use error handler to log and handle the error
581
+ this.logger?.error(`[ServiceWrapper] Failed to publish workflow.completed`, {
582
+ workflowId: message.workflow_id,
583
+ queue: 'workflow.completed',
584
+ error: error.message,
585
+ errorStack: error.stack,
586
+ mqClientConnected: this.mqClient?._connected,
587
+ hasTransport: !!this.mqClient?._transport,
588
+ hasChannel: !!(this.mqClient?._transport?._channel)
589
+ });
590
+
591
+ if (this.errorHandler) {
592
+ await this.errorHandler.logError({
593
+ moduleName: 'ServiceWrapper',
594
+ operation: 'sendWorkflowResponse',
595
+ error: error,
596
+ context: { workflowId: message.workflow_id, queue: 'workflow.completed' }
597
+ });
598
+ }
524
599
  }
525
600
  }
526
601
 
527
602
  return result;
528
603
 
529
604
  } catch (error) {
530
- console.error(`Error processing message from ${queueName}:`, error);
605
+ // Use error handler to log and handle the error
606
+ if (this.errorHandler) {
607
+ await this.errorHandler.logError({
608
+ moduleName: 'ServiceWrapper',
609
+ operation: 'processMessage',
610
+ error: error,
611
+ context: { queueName, workflowId: message?.workflow_id }
612
+ });
613
+ } else {
614
+ this.logger?.error(`Error processing message from ${queueName}:`, error);
615
+ }
531
616
  throw error;
532
617
  }
533
618
  }
@@ -684,14 +769,24 @@ class ServiceWrapper {
684
769
  throw new Error('Monitoring must be initialized before validation');
685
770
  }
686
771
 
687
- const { name: serviceName, version: serviceVersion } = this.config.service;
772
+ const { name: serviceName, version: serviceVersion, port: servicePort } = this.config.service;
773
+
774
+ // Validate service port is configured
775
+ if (!servicePort) {
776
+ throw new Error('Service port is required for validation (config.service.port)');
777
+ }
778
+
779
+ // Build service URL for validation (HTTP calls to running server)
780
+ const serviceUrl = `http://localhost:${servicePort}`;
688
781
 
689
782
  console.log('[ServiceWrapper] Checking validation proof...');
690
783
 
691
- const orchestrator = new ValidationOrchestrator({
784
+ // Use injected orchestrator (for testing) or create new one (production)
785
+ const orchestrator = this._injectedValidationOrchestrator || new ValidationOrchestrator({
692
786
  serviceRoot: this.serviceRoot,
693
787
  serviceName,
694
788
  serviceVersion,
789
+ serviceUrl,
695
790
  logger: this.logger
696
791
  });
697
792
 
package/src/index.js CHANGED
File without changes