@onlineapps/conn-orch-registry 1.1.54 → 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.54",
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,12 +190,13 @@ 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
- // Emit API description request event
189
- this.emit('apiDescriptionRequest', payload);
193
+ // Handle revalidation request from registry (infra version change)
194
+ if (payload.type === 'revalidate') {
195
+ console.log(`[RegistryClient] ${this.serviceName}: Received revalidation request`, {
196
+ reason: payload.reason,
197
+ infraFingerprint: payload.infraFingerprint?.substring(0, 16)
198
+ });
199
+ this.emit('revalidate', payload);
190
200
  }
191
201
 
192
202
  // Handle registration response from registry
@@ -376,6 +386,7 @@ class ServiceRegistryClient extends EventEmitter {
376
386
  }
377
387
 
378
388
  const msgId = uuidv4();
389
+ // @see api/docs/standards/operations-registry-contract.md §2 MQ wire format
379
390
  const msg = {
380
391
  id: msgId,
381
392
  type: 'register',
@@ -383,6 +394,7 @@ class ServiceRegistryClient extends EventEmitter {
383
394
  version: this.version,
384
395
  specificationEndpoint: this.specificationEndpoint,
385
396
  endpoints: serviceInfo.endpoints || [],
397
+ operations: serviceInfo.operations || {},
386
398
  metadata: serviceInfo.metadata || {},
387
399
  health: serviceInfo.health || '/health',
388
400
  spec: serviceInfo.spec || null,
@@ -392,6 +404,12 @@ class ServiceRegistryClient extends EventEmitter {
392
404
  timestamp: new Date().toISOString()
393
405
  };
394
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
+
395
413
  // Include validation proof if loaded
396
414
  // Structure: { validationProof: "hash", validationData: { ... } }
397
415
  // See: @onlineapps/service-validator-core/README.md#validation-proof-structure
@@ -604,60 +622,6 @@ class ServiceRegistryClient extends EventEmitter {
604
622
  }
605
623
  }
606
624
 
607
- /**
608
- * Sends an API description message to the registry queue.
609
- * Emits 'apiDescriptionSent'.
610
- * @param {Object} apiDescription - JSON object describing the API
611
- * @returns {Promise<void>}
612
- */
613
- async sendApiDescription(apiDescription) {
614
- const msg = {
615
- id: uuidv4(),
616
- type: 'apiDescription',
617
- serviceName: this.serviceName,
618
- version: this.version,
619
- description: apiDescription,
620
- timestamp: new Date().toISOString()
621
- };
622
- // CRITICAL: Use queueConfig.js to get correct parameters (TTL, max-length, etc.)
623
- console.log(`[RegistryClient] [PUBLISH] Preparing to publish to ${this.registryQueue} (apiDescription)`);
624
- await this._ensureInfrastructureQueue(this.registryQueue);
625
- this.queueManager.channel.sendToQueue(
626
- this.registryQueue,
627
- Buffer.from(JSON.stringify(msg)),
628
- { persistent: true }
629
- );
630
- this.emit('apiDescriptionSent', msg);
631
- }
632
-
633
- /**
634
- * Alias for sendApiDescription - sends specification to registry
635
- * @param {Object} spec - API specification (optional, will fetch if not provided)
636
- * @returns {Promise<void>}
637
- */
638
- async sendSpecification(spec) {
639
- // If no spec provided, try to fetch it
640
- if (!spec) {
641
- try {
642
- const resolved = runtimeCfg.resolve({ specificationEndpoint: this.specificationEndpoint });
643
- if (!resolved.servicePort) {
644
- throw new Error('[RegistryClient] Missing required config - Expected PORT (servicePort) to auto-fetch specification, or pass spec explicitly');
645
- }
646
- const url = `${resolved.specProtocol}://${resolved.specHost}:${resolved.servicePort}${resolved.specificationEndpoint}`;
647
- const response = await fetch(url);
648
- if (response.ok) {
649
- spec = await response.json();
650
- }
651
- } catch (error) {
652
- console.error('Failed to fetch specification:', error);
653
- return;
654
- }
655
- }
656
-
657
- // Use existing method
658
- return this.sendApiDescription(spec);
659
- }
660
-
661
625
  /**
662
626
  * Subscribe to registry change events (opt-in)
663
627
  * Creates event consumer and starts listening to registry.changes exchange