@onlineapps/conn-orch-registry 1.1.55 → 1.2.1

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/README.md CHANGED
@@ -4,13 +4,15 @@
4
4
  [![Coverage Status](https://codecov.io/gh/onlineapps/conn-orch-registry/branch/main/graph/badge.svg)](https://codecov.io/gh/onlineapps/conn-orch-registry)
5
5
  [![npm version](https://img.shields.io/npm/v/@onlineapps/conn-orch-registry)](https://www.npmjs.com/package/@onlineapps/conn-orch-registry)
6
6
 
7
- > A lightweight client for microservice registration, heartbeat, and API description exchange via RabbitMQ.
7
+ > A lightweight client for microservice registration and heartbeat over RabbitMQ.
8
+ > Sends the full service specification (including `operations`) in the `register`
9
+ > message — see [operations-registry-contract.md §2](../../../docs/standards/operations-registry-contract.md).
8
10
 
9
11
  ## 🚀 Features
10
12
 
11
13
  * Automatic queue management (`workflow`, `<serviceName>.registry`, `api_services_queuer`, `registry.register`)
12
14
  * Periodic heartbeat messages with metadata
13
- * API description request/response flow
15
+ * Full specification (endpoints + operations) published inside `register`
14
16
  * Event-driven API using `EventEmitter`
15
17
  * Fully configurable via environment variables or constructor options
16
18
 
@@ -35,14 +37,16 @@ const client = new ServiceRegistryClient({
35
37
  version: '1.0.0'
36
38
  });
37
39
 
38
- // Listen for API description requests
39
- client.on(EVENTS.API_DESCRIPTION_REQUEST, async () => {
40
- const apiSpec = await loadOpenApiSpec();
41
- await client.sendApiDescription(apiSpec);
40
+ await client.init();
41
+
42
+ // Publish full service spec in the register message (single round-trip).
43
+ await client.register({
44
+ endpoints: [/* ... */],
45
+ operations: await loadOperationsJson(), // see operations-registry-contract.md §3
46
+ metadata: { /* ... */ },
47
+ health: '/health'
42
48
  });
43
49
 
44
- // Initialize and start heartbeats
45
- await client.init();
46
50
  client.startHeartbeat();
47
51
 
48
52
  // Graceful shutdown
@@ -17,20 +17,22 @@ Microservice Registry Backend
17
17
  | (setup connection) | |
18
18
  | | |
19
19
  |-- register() ----------->| |
20
- | (send service info) | |
21
- | |-- validate() --------->|
20
+ | (full spec: endpoints | |
21
+ | + operations + meta) |-- validate() --------->|
22
22
  | | (check if tested) |
23
23
  | |<-- validation result --|
24
24
  | | |
25
- |<-- registration result --| |
25
+ |<-- register.confirmed ---| |
26
26
  | | |
27
27
  |-- startHeartbeat() ----->| |
28
28
  | (if validated OK) | |
29
- | | |
30
- |-- sendApiDescription() ->| |
31
- | (on request) | |
32
29
  ```
33
30
 
31
+ > The legacy `apiDescriptionRequest` / `sendApiDescription` round-trip has
32
+ > been removed — the full service specification (including the `operations`
33
+ > map) travels as part of the `register` message. See
34
+ > [operations-registry-contract.md §2](../../../../docs/standards/operations-registry-contract.md).
35
+
34
36
  ## Usage
35
37
 
36
38
  ### 1. Basic Setup
@@ -83,17 +85,7 @@ async function initializeService() {
83
85
  }
84
86
  ```
85
87
 
86
- ### 3. Handling API Description Requests
87
-
88
- ```javascript
89
- // Listen for requests to send API specification
90
- registryClient.on('apiDescriptionRequest', async (request) => {
91
- const apiSpec = await loadApiSpecification();
92
- await registryClient.sendApiDescription(apiSpec);
93
- });
94
- ```
95
-
96
- ### 4. Event Subscription (Optional)
88
+ ### 3. Event Subscription (Optional)
97
89
 
98
90
  For services that need to be notified about registry changes:
99
91
 
@@ -132,13 +124,14 @@ async function shutdown() {
132
124
  Initializes the connection to RabbitMQ and sets up queues.
133
125
 
134
126
  #### `async register(serviceInfo)`
135
- Registers the service with the registry. The registry will validate that the service is properly tested and approved before activation.
127
+ Registers the service with the registry. The registry validates the service
128
+ against the Operations Registry contract and either confirms or rejects.
136
129
 
137
130
  **Parameters:**
138
131
  - `serviceInfo.endpoints` - Array of API endpoints
132
+ - `serviceInfo.operations` - Operations map (see [operations-registry-contract.md §3](../../../../docs/standards/operations-registry-contract.md))
139
133
  - `serviceInfo.metadata` - Service metadata object
140
134
  - `serviceInfo.health` - Health check endpoint
141
- - `serviceInfo.spec` - OpenAPI specification (optional)
142
135
 
143
136
  **Returns:** Registration result object with `success` status
144
137
 
@@ -148,9 +141,6 @@ Starts sending periodic heartbeat messages. Should only be called after successf
148
141
  #### `stopHeartbeat()`
149
142
  Stops the heartbeat timer.
150
143
 
151
- #### `async sendApiDescription(description)`
152
- Sends the API specification to the registry when requested.
153
-
154
144
  #### `async subscribeToChanges()`
155
145
  Subscribes to registry change events (optional).
156
146
 
@@ -162,9 +152,10 @@ Closes all connections and cleans up resources.
162
152
  | Event | Payload | Description |
163
153
  |-------|---------|-------------|
164
154
  | `registerSent` | Registration message | Emitted when registration is sent |
155
+ | `registerConfirmed` | Confirmation payload | Registry accepted the registration |
156
+ | `registerRejected` | Rejection payload | Registry rejected the registration |
165
157
  | `heartbeatSent` | Heartbeat message | Emitted on each heartbeat |
166
- | `apiDescriptionRequest` | Request details | Registry requests API spec |
167
- | `apiDescriptionSent` | Description message | API spec was sent |
158
+ | `revalidate` | Request payload | Registry asks service to revalidate |
168
159
  | `error` | Error object | Error occurred |
169
160
 
170
161
  ## Registration Validation
@@ -4,10 +4,12 @@
4
4
  * Example demonstrating full lifecycle of ServiceRegistryClient:
5
5
  * 1. Load environment variables
6
6
  * 2. Instantiate client
7
- * 3. Initialize (setup queues and start listening for requests)
8
- * 4. Register event listeners (apiDescriptionRequest, heartbeatSent, apiDescriptionSent, error)
7
+ * 3. Initialize (setup queues and start listening for responses)
8
+ * 4. Register with the full service specification (endpoints + operations)
9
9
  * 5. Start heartbeat loop
10
10
  * 6. Graceful shutdown on process signals
11
+ *
12
+ * See: api/docs/standards/operations-registry-contract.md §2 MQ wire format
11
13
  */
12
14
 
13
15
  function requireEnv(name, description) {
@@ -20,7 +22,6 @@ function requireEnv(name, description) {
20
22
 
21
23
  const { ServiceRegistryClient, EVENTS } = require('@onlineapps/connector-registry-client');
22
24
 
23
- // Step 1: Create a new ServiceRegistryClient instance
24
25
  const registryClient = new ServiceRegistryClient({
25
26
  amqpUrl: requireEnv('AMQP_URL', 'RabbitMQ URL (IPv4-only recommended: amqp://user:pass@127.0.0.1:PORT)'),
26
27
  serviceName: requireEnv('SERVICE_NAME', 'Service logical name'),
@@ -31,20 +32,20 @@ const registryClient = new ServiceRegistryClient({
31
32
  registryQueue: process.env.REGISTRY_QUEUE
32
33
  });
33
34
 
34
- // Step 2: Register event listeners
35
35
  registryClient.on(EVENTS.HEARTBEAT_SENT, (msg) => {
36
36
  console.log(`[Heartbeat] Sent at ${msg.timestamp} for ${msg.serviceName}@${msg.version}`);
37
37
  });
38
38
 
39
- registryClient.on(EVENTS.API_DESCRIPTION_REQUEST, async (payload) => {
40
- console.log(`[API Description Request] for ${payload.serviceName}@${payload.version}`);
41
- // Load local API description (from file or in-memory)
42
- const apiDesc = await loadLocalApiDescription();
43
- await registryClient.sendApiDescription(apiDesc);
39
+ registryClient.on(EVENTS.REGISTER_CONFIRMED, (msg) => {
40
+ console.log(`[Register] Confirmed for ${msg.serviceName || registryClient.serviceName}`);
41
+ });
42
+
43
+ registryClient.on(EVENTS.REGISTER_REJECTED, (msg) => {
44
+ console.error('[Register] Rejected', msg);
44
45
  });
45
46
 
46
- registryClient.on(EVENTS.API_DESCRIPTION_SENT, (msg) => {
47
- console.log(`[API Description Sent] at ${msg.timestamp} for ${msg.serviceName}@${msg.version}`);
47
+ registryClient.on(EVENTS.REVALIDATE, (payload) => {
48
+ console.log('[Revalidate] Registry asked us to revalidate', payload.reason);
48
49
  });
49
50
 
50
51
  registryClient.on(EVENTS.ERROR, (err) => {
@@ -53,15 +54,15 @@ registryClient.on(EVENTS.ERROR, (err) => {
53
54
 
54
55
  (async () => {
55
56
  try {
56
- // Step 3: Initialize client (setup queues and listeners)
57
57
  await registryClient.init();
58
58
  console.log('RegistryClient initialized: queues are ready.');
59
59
 
60
- // Step 4: Start heartbeat loop
60
+ await registryClient.register(await loadServiceSpec());
61
+ console.log('Register message published.');
62
+
61
63
  registryClient.startHeartbeat();
62
64
  console.log('Heartbeat loop started.');
63
65
 
64
- // Step 5: Graceful shutdown on SIGINT/SIGTERM
65
66
  const shutdown = async () => {
66
67
  console.log('Shutting down RegistryClient...');
67
68
  await registryClient.close();
@@ -76,15 +77,41 @@ registryClient.on(EVENTS.ERROR, (err) => {
76
77
  })();
77
78
 
78
79
  /**
79
- * Helper: Load local API description
80
- * In a real application, this could read from a JSON file,
81
- * introspect OpenAPI spec, or build dynamically.
80
+ * Helper: assemble the service specification published in the register message.
81
+ * In a real service this is typically loaded from config/service/operations.json
82
+ * plus the endpoint manifest.
83
+ *
84
+ * @see api/docs/standards/operations-registry-contract.md §3 Operation schema
82
85
  */
83
- async function loadLocalApiDescription() {
86
+ async function loadServiceSpec() {
84
87
  return {
85
88
  endpoints: [
86
- { path: '/invoices', method: 'POST', description: 'Create a new invoice' },
87
- { path: '/invoices/:id', method: 'GET', description: 'Retrieve invoice by ID' }
88
- ]
89
+ { path: '/invoices', method: 'POST' },
90
+ { path: '/invoices/:id', method: 'GET' }
91
+ ],
92
+ operations: {
93
+ 'create-invoice': {
94
+ description: 'Create a new invoice',
95
+ endpoint: '/invoices',
96
+ method: 'POST',
97
+ mutates: true,
98
+ resource_type: 'invoice',
99
+ minRole: 'EDITOR',
100
+ input: {},
101
+ output: {}
102
+ },
103
+ 'get-invoice': {
104
+ description: 'Retrieve invoice by ID',
105
+ endpoint: '/invoices/:id',
106
+ method: 'GET',
107
+ mutates: false,
108
+ resource_type: null,
109
+ minRole: 'VIEWER',
110
+ input: {},
111
+ output: {}
112
+ }
113
+ },
114
+ metadata: {},
115
+ health: '/health'
89
116
  };
90
117
  }
@@ -32,25 +32,25 @@ async function main() {
32
32
  // Initialize connection
33
33
  await client.init();
34
34
 
35
- // Register ourselves with registry
36
- await client.sendApiDescription({
37
- openapi: '3.0.0',
38
- info: {
39
- title: 'Example Service',
40
- version: '1.0.0'
41
- },
42
- paths: {
43
- '/hello': {
44
- get: {
45
- summary: 'Say hello',
46
- responses: {
47
- '200': {
48
- description: 'Success'
49
- }
50
- }
51
- }
35
+ // Register ourselves with the registry. The full spec (endpoints +
36
+ // operations) is published as part of the register message — see
37
+ // api/docs/standards/operations-registry-contract.md §2.
38
+ await client.register({
39
+ endpoints: [{ path: '/hello', method: 'GET' }],
40
+ operations: {
41
+ 'say-hello': {
42
+ description: 'Say hello',
43
+ endpoint: '/hello',
44
+ method: 'GET',
45
+ mutates: false,
46
+ resource_type: null,
47
+ minRole: 'VIEWER',
48
+ input: {},
49
+ output: {}
52
50
  }
53
- }
51
+ },
52
+ metadata: {},
53
+ health: '/health'
54
54
  });
55
55
 
56
56
  // Start heartbeat
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-orch-registry",
3
- "version": "1.1.55",
3
+ "version": "1.2.1",
4
4
  "license": "MIT",
5
5
  "description": "Connector-registry-client provides the core communication mechanism for microservices in this environment. It enables them to interact with a services_registry to receive and fulfill tasks by submitting heartbeats or their API descriptions.",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@onlineapps/conn-base-storage": "1.0.9",
42
- "@onlineapps/mq-client-core": "1.0.82",
42
+ "@onlineapps/mq-client-core": "1.0.83",
43
43
  "@onlineapps/runtime-config": "1.0.2",
44
44
  "amqplib": "^0.10.9",
45
45
  "axios": "^1.12.2",
package/src/events.js CHANGED
@@ -5,6 +5,10 @@
5
5
  * Acts as a central place for constants to avoid typos
6
6
  * when emitting and listening for events.
7
7
  *
8
+ * Legacy `apiDescriptionRequest` / `apiDescriptionSent` events were removed —
9
+ * the full service specification travels as part of the `register` message.
10
+ *
11
+ * @see api/docs/standards/operations-registry-contract.md §2 MQ wire format
8
12
  * @module @onlineapps/connector-registry-client/src/events
9
13
  */
10
14
 
@@ -14,15 +18,18 @@
14
18
  const EVENTS = {
15
19
  /** Emitted after a successful heartbeat is sent. Payload: { id, type, serviceName, version, timestamp } */
16
20
  HEARTBEAT_SENT: 'heartbeatSent',
17
-
18
- /** Emitted when the registryOffice requests the API description. Payload: { id, type, serviceName, version, timestamp } */
19
- API_DESCRIPTION_REQUEST: 'apiDescriptionRequest',
20
-
21
- /** Emitted after sending the API description. Payload: { id, type, serviceName, version, description, timestamp } */
22
- API_DESCRIPTION_SENT: 'apiDescriptionSent',
23
-
21
+
22
+ /** Emitted when the registry confirms a successful registration. Payload: { requestId, certificate, ... } */
23
+ REGISTER_CONFIRMED: 'registerConfirmed',
24
+
25
+ /** Emitted when the registry rejects a registration. Payload: { requestId, reason, errors, ... } */
26
+ REGISTER_REJECTED: 'registerRejected',
27
+
28
+ /** Emitted when the registry asks the service to revalidate (e.g. infra fingerprint change). */
29
+ REVALIDATE: 'revalidate',
30
+
24
31
  /** Emitted on internal errors. Payload: Error instance or error description. */
25
32
  ERROR: 'error'
26
33
  };
27
-
34
+
28
35
  module.exports = EVENTS;
@@ -1,17 +1,27 @@
1
1
  /**
2
2
  * registryClient.js
3
3
  *
4
- * ServiceRegistryClient for communication between a microservice (via Agent) and the central registry.
5
- * Sends heartbeat messages, listens for API description requests, and sends API descriptions.
6
- * Uses QueueManager to manage AMQP queues.
7
- * Emits events through EventEmitter.
4
+ * ServiceRegistryClient for communication between a microservice (via Agent)
5
+ * and the central registry. Sends the full service specification in the
6
+ * `register` message (including the `operations` map) and periodic heartbeat
7
+ * messages. Receives registration responses (register.confirmed /
8
+ * register.rejected) and revalidation requests from the registry.
9
+ *
10
+ * Legacy `apiDescription` / `apiDescriptionRequest` round-trips have been
11
+ * removed — the full spec is part of the `register` message itself.
12
+ *
13
+ * Uses QueueManager to manage AMQP queues. Emits events through EventEmitter.
8
14
  *
9
15
  * Events (see src/events.js):
10
16
  * - 'heartbeatSent'
11
- * - 'apiDescriptionRequest'
12
- * - 'apiDescriptionSent'
17
+ * - 'revalidate'
18
+ * - 'registerConfirmed'
19
+ * - 'registerRejected'
13
20
  * - 'error'
14
21
  *
22
+ * @see api/docs/standards/operations-registry-contract.md §2 MQ wire format
23
+ * @see api/docs/standards/OPERATIONS.md
24
+ *
15
25
  * @module @onlineapps/connector-registry-client/src/registryClient
16
26
  */
17
27
 
@@ -147,8 +157,7 @@ class ServiceRegistryClient extends EventEmitter {
147
157
 
148
158
  /**
149
159
  * Internal handler for incoming messages from the registry queue.
150
- * Emits 'apiDescriptionRequest' when appropriate.
151
- * Handles registration responses.
160
+ * Handles registration responses and revalidation requests.
152
161
  * @param {Object} msg - AMQP message
153
162
  * @private
154
163
  */
@@ -181,13 +190,6 @@ class ServiceRegistryClient extends EventEmitter {
181
190
  return;
182
191
  }
183
192
 
184
- // Handle API description request from registry
185
- if (payload.type === 'apiDescriptionRequest' &&
186
- payload.serviceName === this.serviceName &&
187
- payload.version === this.version) {
188
- this.emit('apiDescriptionRequest', payload);
189
- }
190
-
191
193
  // Handle revalidation request from registry (infra version change)
192
194
  if (payload.type === 'revalidate') {
193
195
  console.log(`[RegistryClient] ${this.serviceName}: Received revalidation request`, {
@@ -384,6 +386,7 @@ class ServiceRegistryClient extends EventEmitter {
384
386
  }
385
387
 
386
388
  const msgId = uuidv4();
389
+ // @see api/docs/standards/operations-registry-contract.md §2 MQ wire format
387
390
  const msg = {
388
391
  id: msgId,
389
392
  type: 'register',
@@ -391,6 +394,7 @@ class ServiceRegistryClient extends EventEmitter {
391
394
  version: this.version,
392
395
  specificationEndpoint: this.specificationEndpoint,
393
396
  endpoints: serviceInfo.endpoints || [],
397
+ operations: serviceInfo.operations || {},
394
398
  metadata: serviceInfo.metadata || {},
395
399
  health: serviceInfo.health || '/health',
396
400
  spec: serviceInfo.spec || null,
@@ -400,6 +404,12 @@ class ServiceRegistryClient extends EventEmitter {
400
404
  timestamp: new Date().toISOString()
401
405
  };
402
406
 
407
+ // Propagate workspaceScoped flag to the registry so consumers can discover
408
+ // which services are workspace-scoped. See biz-service-onboarding.md §4.
409
+ if (typeof serviceInfo.workspaceScoped === 'boolean') {
410
+ msg.workspaceScoped = serviceInfo.workspaceScoped;
411
+ }
412
+
403
413
  // Include validation proof if loaded
404
414
  // Structure: { validationProof: "hash", validationData: { ... } }
405
415
  // See: @onlineapps/service-validator-core/README.md#validation-proof-structure
@@ -612,60 +622,6 @@ class ServiceRegistryClient extends EventEmitter {
612
622
  }
613
623
  }
614
624
 
615
- /**
616
- * Sends an API description message to the registry queue.
617
- * Emits 'apiDescriptionSent'.
618
- * @param {Object} apiDescription - JSON object describing the API
619
- * @returns {Promise<void>}
620
- */
621
- async sendApiDescription(apiDescription) {
622
- const msg = {
623
- id: uuidv4(),
624
- type: 'apiDescription',
625
- serviceName: this.serviceName,
626
- version: this.version,
627
- description: apiDescription,
628
- timestamp: new Date().toISOString()
629
- };
630
- // CRITICAL: Use queueConfig.js to get correct parameters (TTL, max-length, etc.)
631
- console.log(`[RegistryClient] [PUBLISH] Preparing to publish to ${this.registryQueue} (apiDescription)`);
632
- await this._ensureInfrastructureQueue(this.registryQueue);
633
- this.queueManager.channel.sendToQueue(
634
- this.registryQueue,
635
- Buffer.from(JSON.stringify(msg)),
636
- { persistent: true }
637
- );
638
- this.emit('apiDescriptionSent', msg);
639
- }
640
-
641
- /**
642
- * Alias for sendApiDescription - sends specification to registry
643
- * @param {Object} spec - API specification (optional, will fetch if not provided)
644
- * @returns {Promise<void>}
645
- */
646
- async sendSpecification(spec) {
647
- // If no spec provided, try to fetch it
648
- if (!spec) {
649
- try {
650
- const resolved = runtimeCfg.resolve({ specificationEndpoint: this.specificationEndpoint });
651
- if (!resolved.servicePort) {
652
- throw new Error('[RegistryClient] Missing required config - Expected PORT (servicePort) to auto-fetch specification, or pass spec explicitly');
653
- }
654
- const url = `${resolved.specProtocol}://${resolved.specHost}:${resolved.servicePort}${resolved.specificationEndpoint}`;
655
- const response = await fetch(url);
656
- if (response.ok) {
657
- spec = await response.json();
658
- }
659
- } catch (error) {
660
- console.error('Failed to fetch specification:', error);
661
- return;
662
- }
663
- }
664
-
665
- // Use existing method
666
- return this.sendApiDescription(spec);
667
- }
668
-
669
625
  /**
670
626
  * Subscribe to registry change events (opt-in)
671
627
  * Creates event consumer and starts listening to registry.changes exchange