@onlineapps/conn-orch-registry 3.0.1 → 4.0.0

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
@@ -1,9 +1,28 @@
1
+ > Status: current
2
+ > Owns: a service's side of the services registry — heartbeats, API-description submission, and the registry events it consumes
3
+
4
+ <!-- BEGIN GENERATED: library-uniform — regenerate: npx oa-sync-template readme-uniform --all -->
5
+ Uniform: [library/orchestration](../conn-orch-validator/manifests/library.manifest.json)
6
+
7
+ Duty sections that apply:
8
+
9
+ - `all`: L-MAIN, L-ENGINES, L-TESTS, L-TEST-SCRIPT, L-PACK-TESTS, L-PINS, L-NO-FILE-RANGE, L-CHANGELOG, L-README, L-README-REGION, L-CONSUMER
10
+ <!-- END GENERATED: library-uniform -->
11
+
1
12
  # @onlineapps/conn-orch-registry
2
13
 
3
- [![Build Status](https://img.shields.io/github/actions/workflow/status/onlineapps/conn-orch-registry/nodejs.yml?branch=main)](https://github.com/onlineapps/conn-orch-registry/actions)
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
14
  [![npm version](https://img.shields.io/npm/v/@onlineapps/conn-orch-registry)](https://www.npmjs.com/package/@onlineapps/conn-orch-registry)
6
15
 
16
+ <!--
17
+ The GitHub Actions and Codecov badges that used to sit here pointed at
18
+ github.com/onlineapps/conn-orch-registry, which does not exist — this package lives
19
+ in gitlab.com/onlineapps/oadrive/infra-mono under shared/connector/conn-orch-registry.
20
+ A build badge for a pipeline nobody runs is a green light with nothing behind it,
21
+ so it was removed rather than repointed — same treatment as conn-infra-mq (a4808857).
22
+ The npm badge stays: the package IS published under that name.
23
+ -->
24
+
25
+
7
26
  > A lightweight client for microservice registration and heartbeat over RabbitMQ.
8
27
  > Sends the full service specification (including `operations`) in the `register`
9
28
  > message — see [operations-registry-contract.md §2](../../../docs/biz/30-operations/registration-wire.md).
@@ -16,6 +35,13 @@
16
35
  * Event-driven API using `EventEmitter`
17
36
  * Fully configurable via environment variables or constructor options
18
37
 
38
+ > **This client registers; it does not read.** Reading another service's spec goes
39
+ > through `createRegistryReader` from `@onlineapps/service-common`, which reads the
40
+ > Redis projection — see
41
+ > [registration-wire.md §7 Consumer contract](../../../docs/biz/30-operations/registration-wire.md).
42
+ > The `registry.changes` events this client can subscribe to are an invalidation
43
+ > hint, not a read path (§5).
44
+
19
45
  ## 📦 Installation
20
46
 
21
47
  ```bash
@@ -34,7 +60,10 @@ const { ServiceRegistryClient, EVENTS } = require('@onlineapps/conn-orch-registr
34
60
  const client = new ServiceRegistryClient({
35
61
  amqpUrl: process.env.AMQP_URL,
36
62
  serviceName: 'invoicing',
37
- version: '1.0.0'
63
+ version: '1.0.0',
64
+ specificationEndpoint: '/api/v1/specification', // required — see § Configuration
65
+ heartbeatInterval: 30000, // required — see § Configuration
66
+ logger // required — see § Logger contract below
38
67
  });
39
68
 
40
69
  await client.init();
@@ -60,14 +89,94 @@ process.on('SIGINT', async () => {
60
89
 
61
90
  Configuration can be provided via environment variables or constructor options:
62
91
 
63
- | Variable | Description | Default |
64
- | -------------------- | --------------------------------------------- | -------------------- |
65
- | `AMQP_URL` | RabbitMQ connection string (required) | — |
66
- | `SERVICE_NAME` | Logical name of your service (required) | — |
67
- | `SERVICE_VERSION` | Service version in SemVer format (required) | — |
68
- | `HEARTBEAT_INTERVAL` | Interval in ms between heartbeats | `10000` |
69
- | `API_QUEUE` | Queue name for heartbeat/API messages | `api_services_queuer` |
70
- | `REGISTRY_QUEUE` | Queue name for registry requests/descriptions | `registry.register` |
92
+ | Option | Description | Default |
93
+ | ----------------------- | --------------------------------------------- | -------------------- |
94
+ | `amqpUrl` | RabbitMQ connection string (required) | — |
95
+ | `serviceName` | Logical name of your service (required) | — |
96
+ | `version` | Service version in SemVer format (required) | — |
97
+ | `specificationEndpoint` | Path the `register`/`heartbeat` messages carry (**required**) | — |
98
+ | `heartbeatInterval` | Milliseconds between heartbeats (**required**) | — |
99
+ | `apiQueue` | Queue name for heartbeat/API messages | `api_services_queuer` |
100
+ | `registryQueue` | Queue name for registry requests | `registry.register` |
101
+
102
+ **`specificationEndpoint` and `heartbeatInterval` carry no default** (d.278c). Both are
103
+ decisions somebody else owns, and a library default is a second decision that fires
104
+ whenever the caller forgets the first (architecture-principles §3):
105
+
106
+ * the specification path is REQUIRED on the wire
107
+ ([registration-wire.md](/api/docs/biz/30-operations/registration-wire.md) §2.1) and the
108
+ registry stores what it is sent, so a substituted path lands in `registry:services` for
109
+ a service that never declared it;
110
+ * the heartbeat cadence has ONE owner — `wrapper.registry.heartbeatInterval`, fed from
111
+ `BIZ_HEARTBEAT_INTERVAL_MS` (owner confirmation
112
+ [`biz-health-freshness.md`](/api/docs/governance/confirmations/biz-health-freshness.md)
113
+ 001 point 3) — and the freshness window the monitoring side computes is a multiple of it.
114
+
115
+ Both are refused at construction, by value, naming the fix.
116
+
117
+ `amqpUrl` carries the broker credential, so no error message and no log line of this
118
+ package renders it as given: the URL passes through `redactUrl` from
119
+ `@onlineapps/mq-client-core`, which drops the userinfo (`user:password@`) and keeps
120
+ scheme, host, port and vhost. The connection itself uses the URL you passed — the
121
+ redaction applies only to what gets written out. (The helper was a local copy until
122
+ d.446b, from the days when that package masked the password alone; one concern, one
123
+ rail.)
124
+
125
+ ## 📬 Delivery of registry messages
126
+
127
+ Both consumers in this package — the service's own `<service>.registry` response queue
128
+ and the `<service>.registry.events` fanout copy — run on the delivery contract of
129
+ [`@onlineapps/mq-client-core`](https://www.npmjs.com/package/@onlineapps/mq-client-core)
130
+ (§ `consume()` — the delivery contract), registered with `requeueOnError: false`.
131
+
132
+ | The handler | What happens to the message |
133
+ | --- | --- |
134
+ | returns | acked, once |
135
+ | throws | rejected into `<service>.dlq` over `dlx`, on the first failure, with a `message_dlq` event |
136
+
137
+ There is no hand-written `ack`/`nack` here any more and no raw amqplib channel leaves
138
+ `QueueManager` (d.198b-3). The queue declarations — durability, TTL, cap and the
139
+ dead-letter route — come from `queueConfig` in that package; this one names its queues
140
+ and declares nothing of its own.
141
+
142
+ ## 🪵 Logger contract
143
+
144
+ Every constructor in this package — `ServiceRegistryClient`, `RegistryEventConsumer`
145
+ and `QueueManager` — **requires** a `logger` and validates it on the spot. A logger is
146
+ an object with all four of `info`, `warn`, `error` and `debug` as functions. Anything
147
+ else fails at construction, naming the method that is missing:
148
+
149
+ ```
150
+ [RegistryClient] logger is incomplete - Expected: info, warn, error, debug as functions;
151
+ missing: debug. Fix: pass a logger implementing all four.
152
+ ```
153
+
154
+ There is no fallback to `console` (architecture-principles §3), and no delayed check:
155
+ a service that boots with half a logger learns it at construction, not on the first
156
+ registration failure (§4).
157
+
158
+ What the package writes through it:
159
+
160
+ | Level | Events |
161
+ | --- | --- |
162
+ | `info` | client initialized · validation proof present · registration message sent · registration resolved (with certificate id) · deregistration request published · revalidation requested · registry subscription, snapshot, status change, spec published, service removed |
163
+ | `warn` | no validation proof (registry falls back to Tier 2) · registration response with no matching pending request · unknown registry event type · index-only mode |
164
+ | `debug` | inbound registry message · registry verdict payload · heartbeat sent · queue asserted · spec download |
165
+ | `error` | resolve threw · heartbeat failed · index persist/load failure |
166
+
167
+ `ServiceRegistryClient` passes its logger down to the `QueueManager` and
168
+ `RegistryEventConsumer` it constructs, so one service gets one sink.
169
+
170
+ Errors that are **thrown** are not also logged: the thrown message carries the context
171
+ and the caller decides. A delivery this package cannot handle is reported the same way
172
+ — the handler throws and the core dead-letters the message, logging that move once.
173
+
174
+ Owner confirmation:
175
+ [`docs/governance/confirmations/connector-logger-contract.md`](/api/docs/governance/confirmations/connector-logger-contract.md)
176
+ 001 (all four methods, validated in the constructor) and 002 (a logger a component
177
+ demands is a logger that component writes to). Standard:
178
+ [`docs/standards/ERROR_HANDLING.md`](/api/docs/standards/ERROR_HANDLING.md)
179
+ § Logger Configuration.
71
180
 
72
181
  ## 📨 Message Formats
73
182
 
@@ -94,10 +203,17 @@ Configuration can be provided via environment variables or constructor options:
94
203
  signature: "cryptographic-signature",
95
204
  validUntil: "ISO-8601"
96
205
  },
206
+ errorCode: null,
97
207
  cached: false
98
208
  }
99
209
  ```
100
210
 
211
+ `errorCode` is the registry's machine reason for a refusal, and `register()` hands it to
212
+ the caller unchanged as `result.errorCode` — branch on it, never on `message`, whose
213
+ wording is not a contract. It is `null` on a confirmed registration, and `undefined` for
214
+ the two failure replies that carry no code at all. The codes, who emits each and what
215
+ fixes it: `api/docs/biz/30-operations/registration-wire.md` §8.
216
+
101
217
  ### Heartbeat
102
218
  ```javascript
103
219
  {
@@ -110,13 +226,21 @@ Configuration can be provided via environment variables or constructor options:
110
226
 
111
227
  ## 🛠️ API Reference
112
228
 
113
- See [docs/api.md](https://github.com/onlineapps/conn-orch-registry/blob/main/docs/api.md) for full details on classes, methods, and events.
229
+ See [API.md](API.md) for full details on classes, methods, and events. It is generated
230
+ from the JSDoc in `src/` by `npm run docs` — do not edit it by hand.
231
+
232
+ <!--
233
+ The three links that used to stand here pointed at github.com/onlineapps — at
234
+ docs/api.md and docs/architecture.md in a repo that does not exist, and neither
235
+ file has ever existed in this package. Replaced with the files that do exist,
236
+ by relative path, which resolves both on GitLab and on npmjs.com.
237
+ -->
114
238
 
115
239
  ## 📖 Documentation
116
240
 
117
- * Architecture overview: [docs/architecture.md](https://github.com/onlineapps/conn-orch-registry/blob/main/docs/architecture.md)
118
- * API reference: [docs/api.md](https://github.com/onlineapps/conn-orch-registry/blob/main/docs/api.md)
119
- * Examples: [examples/basicUsage.js](https://github.com/onlineapps/agent-registry-client/blob/main/examples/basicUsage.js)
241
+ * API reference (generated): [API.md](API.md)
242
+ * Usage guide: [docs/REGISTRY_CLIENT_GUIDE.md](docs/REGISTRY_CLIENT_GUIDE.md)
243
+ * Examples: [examples/basicUsage.js](examples/basicUsage.js), [examples/event-consumer-example.js](examples/event-consumer-example.js)
120
244
 
121
245
  ## 🔗 Related Documentation
122
246
 
@@ -139,13 +263,19 @@ npm test
139
263
  * **Formatting**: Prettier
140
264
  * **Testing**: Jest
141
265
 
142
- ## 🤝 Contributing
143
-
144
- Please read [CONTRIBUTING.md](https://github.com/onlineapps/agent-registry-client/blob/main/CONTRIBUTING.md) for details on submitting issues and pull requests.
266
+ <!--
267
+ A "Contributing" section used to stand here, linking to CONTRIBUTING.md on
268
+ github.com/onlineapps/agent-registry-client — a repo that does not exist and never
269
+ held this package; there is no CONTRIBUTING.md in this package either, and the
270
+ fork/PR flow is not how this monorepo is developed. Removed rather than repointed:
271
+ the workspace rules that actually govern changes here load automatically from
272
+ .claude/rules/. The now-orphaned "CONTRIBUTING.md" entry in package.json `files`
273
+ went with it, in the same commit.
274
+ -->
145
275
 
146
276
  ## 📜 License
147
277
 
148
- This project is licensed under the MIT License. See [LICENSE](https://github.com/onlineapps/agent-registry-client/blob/main/LICENSE) for details.
278
+ This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
149
279
 
150
280
  ## 📚 Documentation
151
281
 
@@ -38,7 +38,7 @@ Microservice Registry Backend
38
38
  ### 1. Basic Setup
39
39
 
40
40
  ```javascript
41
- const { ServiceRegistryClient } = require('@onlineapps/connector-registry-client');
41
+ const { ServiceRegistryClient } = require('@onlineapps/conn-orch-registry');
42
42
 
43
43
  const registryClient = new ServiceRegistryClient({
44
44
  amqpUrl: 'amqp://localhost:5672',
@@ -113,8 +113,8 @@ async function shutdown() {
113
113
  | `amqpUrl` | string | required | AMQP connection URL |
114
114
  | `serviceName` | string | required | Unique service identifier |
115
115
  | `version` | string | required | Service version (semver) |
116
- | `specificationEndpoint` | string | '/api/v1/specification' | Endpoint for API spec |
117
- | `heartbeatInterval` | number | 10000 | Heartbeat interval in ms |
116
+ | `specificationEndpoint` | string | required | Path the register/heartbeat messages carry; no default (d.278c) |
117
+ | `heartbeatInterval` | number | required | Milliseconds between heartbeats; no default — the cadence has one owner (d.278c) |
118
118
  | `apiQueue` | string | 'api_services_queuer' | Queue for API traffic |
119
119
  | `registryQueue` | string | 'registry.register' | Registry queue name |
120
120
 
@@ -216,13 +216,13 @@ Verify:
216
216
 
217
217
  ### Unit Tests
218
218
  ```bash
219
- npm test:unit
219
+ npm run test:unit
220
220
  ```
221
221
 
222
222
  ### Integration Tests
223
223
  ```bash
224
224
  # Requires RabbitMQ and MinIO running
225
- npm test:integration
225
+ npm run test:integration
226
226
  ```
227
227
 
228
228
  ### Full Test Suite
@@ -20,14 +20,25 @@ function requireEnv(name, description) {
20
20
  return String(value).trim();
21
21
  }
22
22
 
23
- const { ServiceRegistryClient, EVENTS } = require('@onlineapps/connector-registry-client');
23
+ // Requiring the package by its own name does not resolve from inside the package
24
+ // (no self-reference `exports` field), so the example loads the sources directly —
25
+ // same as examples/event-consumer-example.js. Consumers require
26
+ // '@onlineapps/conn-orch-registry'; see README § Quick Start.
27
+ const { ServiceRegistryClient, EVENTS } = require('../src/index');
24
28
 
29
+ // The logger is a hard requirement of every constructor in this package
30
+ // (README § Logger contract). An example script has no service logger, so it passes
31
+ // `console` explicitly — it implements info/warn/error/debug, and choosing it here is
32
+ // the caller's decision, not a fallback inside the library.
25
33
  const registryClient = new ServiceRegistryClient({
34
+ logger: console,
26
35
  amqpUrl: requireEnv('AMQP_URL', 'RabbitMQ URL (IPv4-only recommended: amqp://user:pass@127.0.0.1:PORT)'),
27
36
  serviceName: requireEnv('SERVICE_NAME', 'Service logical name'),
28
37
  version: requireEnv('SERVICE_VERSION', 'Service version (SemVer)'),
29
- specificationEndpoint: process.env.SPECIFICATION_ENDPOINT,
30
- heartbeatInterval: process.env.HEARTBEAT_INTERVAL ? parseInt(process.env.HEARTBEAT_INTERVAL, 10) : undefined,
38
+ // Both REQUIRED since d.278c — the client substitutes neither, so the example
39
+ // demands them the same way it demands the URL and the service name.
40
+ specificationEndpoint: requireEnv('SPECIFICATION_ENDPOINT', 'Path the register message carries (e.g. /api/v1/specification)'),
41
+ heartbeatInterval: parseInt(requireEnv('HEARTBEAT_INTERVAL', 'Milliseconds between heartbeats'), 10),
31
42
  apiQueue: process.env.API_QUEUE,
32
43
  registryQueue: process.env.REGISTRY_QUEUE
33
44
  });
@@ -1,7 +1,11 @@
1
1
  /**
2
2
  * Example: Using ServiceRegistryClient with event consumption
3
3
  *
4
- * Demonstrates opt-in subscription to registry change events
4
+ * Demonstrates opt-in subscription to registry change events. The events are an
5
+ * invalidation hint ("something changed"), not a read path: to read another
6
+ * service's spec, use createRegistryReader from @onlineapps/service-common.
7
+ *
8
+ * @see api/docs/biz/30-operations/registration-wire.md §5, §7
5
9
  */
6
10
 
7
11
  const { ServiceRegistryClient } = require('../src/index');
@@ -15,7 +19,12 @@ async function main() {
15
19
  });
16
20
 
17
21
  // Initialize client with storage configuration
22
+ // The logger is a hard requirement of every constructor in this package
23
+ // (README § Logger contract). An example script has no service logger, so it passes
24
+ // `console` explicitly — it implements info/warn/error/debug, and choosing it here is
25
+ // the caller's decision, not a fallback inside the library.
18
26
  const client = new ServiceRegistryClient({
27
+ logger: console,
19
28
  amqpUrl: process.env.AMQP_URL || 'amqp://localhost',
20
29
  serviceName: 'example-service',
21
30
  version: '1.0.0',
@@ -71,30 +80,8 @@ async function main() {
71
80
 
72
81
  client.on('snapshotReceived', ({ count }) => {
73
82
  console.log(`Received snapshot with ${count} services`);
74
-
75
- // Show active services
76
- const activeServices = client.getActiveServices();
77
- console.log('Active services:', activeServices);
78
83
  });
79
84
 
80
- // Example: Check if another service is active
81
- setTimeout(() => {
82
- if (client.isServiceActive('invoicing')) {
83
- console.log('Invoicing service is active');
84
-
85
- // Load its spec
86
- client.getServiceSpec('invoicing')
87
- .then(spec => {
88
- console.log('Invoicing API:', spec.info?.title);
89
- })
90
- .catch(err => {
91
- console.error('Failed to load invoicing spec:', err.message);
92
- });
93
- } else {
94
- console.log('Invoicing service is not active');
95
- }
96
- }, 5000);
97
-
98
85
  // Graceful shutdown
99
86
  process.on('SIGINT', async () => {
100
87
  console.log('\nShutting down...');
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-orch-registry",
3
- "version": "3.0.1",
3
+ "version": "4.0.0",
4
4
  "license": "MIT",
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.",
5
+ "description": "conn-orch-registry 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": [
7
7
  "microservice",
8
8
  "connector",
@@ -18,41 +18,35 @@
18
18
  "docs/",
19
19
  "examples/",
20
20
  "README.md",
21
- "LICENSE",
22
- "CONTRIBUTING.md"
21
+ "LICENSE"
23
22
  ],
23
+ "oa": {
24
+ "category": "orchestration"
25
+ },
24
26
  "main": "src/index.js",
25
27
  "scripts": {
26
- "test": "jest --coverage",
27
- "test:unit": "jest --testPathPattern=unit --coverage",
28
- "test:component": "jest --testPathPattern=component --coverage",
28
+ "test": "npm run test:unit && npm run test:integration",
29
+ "test:unit": "jest tests/unit --coverage",
29
30
  "test:integration": "jest --config=jest.integration.config.js",
30
31
  "test:watch": "jest --watch",
31
32
  "test:coverage": "jest --coverage --coverageReporters=text-lcov html",
32
- "semantic-release": "semantic-release",
33
- "lint": "eslint src tests examples --ext .js --cache --cache-location .eslintcache",
34
- "docs": "jsdoc2md --files src/**/*.js > API.md"
33
+ "docs": "jsdoc2md --files 'src/**/*.js' > API.md.tmp && mv API.md.tmp API.md || (rm -f API.md.tmp; exit 1)"
35
34
  },
36
35
  "publishConfig": {
37
36
  "access": "public"
38
37
  },
39
38
  "dependencies": {
40
- "@onlineapps/mq-client-core": "2.0.1",
41
- "amqplib": "^0.10.9",
39
+ "@onlineapps/logger-contract": "1.1.0",
40
+ "@onlineapps/mq-client-core": "3.0.0",
42
41
  "uuid": "^9.0.1"
43
42
  },
44
43
  "devDependencies": {
45
- "@semantic-release/changelog": "^6.0.3",
46
- "@semantic-release/commit-analyzer": "^11.1.0",
47
- "@semantic-release/git": "^10.0.1",
48
- "@semantic-release/npm": "^11.0.3",
49
- "@semantic-release/release-notes-generator": "^12.1.0",
50
- "eslint": "^8.44.0",
44
+ "amqplib": "0.10.9",
51
45
  "ioredis": "^5.8.0",
52
46
  "jest": "^29.6.1",
53
- "semantic-release": "^22.0.12"
47
+ "jsdoc-to-markdown": "^8.0.0"
54
48
  },
55
49
  "engines": {
56
- "node": ">=18"
50
+ "node": ">=24.0.0 <25"
57
51
  }
58
52
  }
package/src/events.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * events.js
3
3
  *
4
- * List of events used in the connector-registry-client module.
4
+ * List of events used in the conn-orch-registry module.
5
5
  * Acts as a central place for constants to avoid typos
6
6
  * when emitting and listening for events.
7
7
  *
@@ -9,7 +9,7 @@
9
9
  * the full service specification travels as part of the `register` message.
10
10
  *
11
11
  * @see api/docs/biz/30-operations/registration-wire.md §2 MQ wire format
12
- * @module @onlineapps/connector-registry-client/src/events
12
+ * @module @onlineapps/conn-orch-registry/src/events
13
13
  */
14
14
 
15
15
  /**
package/src/index.js CHANGED
@@ -3,7 +3,6 @@
3
3
  * @description Service registry connector for dynamic service discovery, health monitoring,
4
4
  * and OpenAPI specification management in OA Drive microservices architecture.
5
5
  *
6
- * @see {@link https://github.com/onlineapps/oa-drive/tree/main/shared/connector/conn-orch-registry|GitHub Repository}
7
6
  * @author OA Drive Team
8
7
  * @license MIT
9
8
  * @since 1.0.0