@onlineapps/conn-orch-registry 1.2.2 → 2.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 +5 -5
- package/docs/REGISTRY_CLIENT_GUIDE.md +2 -2
- package/examples/basicUsage.js +2 -2
- package/examples/event-consumer-example.js +1 -1
- package/package.json +5 -7
- package/src/defaults.js +15 -0
- package/src/events.js +1 -1
- package/src/registryClient.js +103 -51
- package/src/registryEventConsumer.js +33 -16
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
> A lightweight client for microservice registration and heartbeat over RabbitMQ.
|
|
8
8
|
> Sends the full service specification (including `operations`) in the `register`
|
|
9
|
-
> message — see [operations-registry-contract.md §2](../../../docs/
|
|
9
|
+
> message — see [operations-registry-contract.md §2](../../../docs/biz/30-operations/registration-wire.md).
|
|
10
10
|
|
|
11
11
|
## 🚀 Features
|
|
12
12
|
|
|
@@ -120,8 +120,8 @@ See [docs/api.md](https://github.com/onlineapps/conn-orch-registry/blob/main/doc
|
|
|
120
120
|
|
|
121
121
|
## 🔗 Related Documentation
|
|
122
122
|
|
|
123
|
-
- **[
|
|
124
|
-
- [Registry
|
|
123
|
+
- **[Two-Tier Validation Architecture](/docs/architecture/validator.md)** - Full validation architecture and flow
|
|
124
|
+
- [Registry Architecture](/docs/architecture/registry.md) - High-level registry system architecture
|
|
125
125
|
- [api_services_registry](/api/api_services_registry/README.md) - Registry service implementation
|
|
126
126
|
|
|
127
127
|
## ✅ Testing
|
|
@@ -149,6 +149,6 @@ This project is licensed under the MIT License. See [LICENSE](https://github.com
|
|
|
149
149
|
|
|
150
150
|
## 📚 Documentation
|
|
151
151
|
|
|
152
|
-
- [Complete Connectors Documentation](
|
|
153
|
-
- [Testing
|
|
152
|
+
- [Complete Connectors Documentation](/docs/architecture/connector.md)
|
|
153
|
+
- [Testing Standard](/docs/standards/TESTING.md)
|
|
154
154
|
|
|
@@ -31,7 +31,7 @@ Microservice Registry Backend
|
|
|
31
31
|
> The legacy `apiDescriptionRequest` / `sendApiDescription` round-trip has
|
|
32
32
|
> been removed — the full service specification (including the `operations`
|
|
33
33
|
> map) travels as part of the `register` message. See
|
|
34
|
-
> [operations-registry-contract.md §2](../../../../docs/
|
|
34
|
+
> [operations-registry-contract.md §2](../../../../docs/biz/30-operations/registration-wire.md).
|
|
35
35
|
|
|
36
36
|
## Usage
|
|
37
37
|
|
|
@@ -129,7 +129,7 @@ against the Operations Registry contract and either confirms or rejects.
|
|
|
129
129
|
|
|
130
130
|
**Parameters:**
|
|
131
131
|
- `serviceInfo.endpoints` - Array of API endpoints
|
|
132
|
-
- `serviceInfo.operations` - Operations map (see [operations-registry-contract.md §3](../../../../docs/
|
|
132
|
+
- `serviceInfo.operations` - Operations map (see [operations-registry-contract.md §3](../../../../docs/biz/30-operations/registration-wire.md))
|
|
133
133
|
- `serviceInfo.metadata` - Service metadata object
|
|
134
134
|
- `serviceInfo.health` - Health check endpoint
|
|
135
135
|
|
package/examples/basicUsage.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 5. Start heartbeat loop
|
|
10
10
|
* 6. Graceful shutdown on process signals
|
|
11
11
|
*
|
|
12
|
-
* See: api/docs/
|
|
12
|
+
* See: api/docs/biz/30-operations/registration-wire.md §2 MQ wire format
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
function requireEnv(name, description) {
|
|
@@ -81,7 +81,7 @@ registryClient.on(EVENTS.ERROR, (err) => {
|
|
|
81
81
|
* In a real service this is typically loaded from config/service/operations.json
|
|
82
82
|
* plus the endpoint manifest.
|
|
83
83
|
*
|
|
84
|
-
* @see api/docs/
|
|
84
|
+
* @see api/docs/biz/30-operations/registration-wire.md §3 Operation schema
|
|
85
85
|
*/
|
|
86
86
|
async function loadServiceSpec() {
|
|
87
87
|
return {
|
|
@@ -34,7 +34,7 @@ async function main() {
|
|
|
34
34
|
|
|
35
35
|
// Register ourselves with the registry. The full spec (endpoints +
|
|
36
36
|
// operations) is published as part of the register message — see
|
|
37
|
-
// api/docs/
|
|
37
|
+
// api/docs/biz/30-operations/registration-wire.md §2.
|
|
38
38
|
await client.register({
|
|
39
39
|
endpoints: [{ path: '/hello', method: 'GET' }],
|
|
40
40
|
operations: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/conn-orch-registry",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
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": [
|
|
@@ -27,22 +27,19 @@
|
|
|
27
27
|
"test:unit": "jest --testPathPattern=unit --coverage",
|
|
28
28
|
"test:component": "jest --testPathPattern=component --coverage",
|
|
29
29
|
"test:integration": "jest --config=jest.integration.config.js",
|
|
30
|
-
"test:integration:required": "REQUIRE_SERVICES=true jest --config=jest.integration.config.js",
|
|
31
30
|
"test:watch": "jest --watch",
|
|
32
31
|
"test:coverage": "jest --coverage --coverageReporters=text-lcov html",
|
|
33
32
|
"semantic-release": "semantic-release",
|
|
34
|
-
"lint": "eslint src
|
|
33
|
+
"lint": "eslint src tests examples --ext .js --cache --cache-location .eslintcache",
|
|
35
34
|
"docs": "jsdoc2md --files src/**/*.js > API.md"
|
|
36
35
|
},
|
|
37
36
|
"publishConfig": {
|
|
38
37
|
"access": "public"
|
|
39
38
|
},
|
|
40
39
|
"dependencies": {
|
|
41
|
-
"@onlineapps/
|
|
42
|
-
"@onlineapps/
|
|
43
|
-
"@onlineapps/runtime-config": "1.0.2",
|
|
40
|
+
"@onlineapps/mq-client-core": "2.0.0",
|
|
41
|
+
"@onlineapps/runtime-config": "1.0.3",
|
|
44
42
|
"amqplib": "^0.10.9",
|
|
45
|
-
"ioredis": "^5.8.0",
|
|
46
43
|
"uuid": "^9.0.1"
|
|
47
44
|
},
|
|
48
45
|
"devDependencies": {
|
|
@@ -56,6 +53,7 @@
|
|
|
56
53
|
"eslint-config-prettier": "^10.1.5",
|
|
57
54
|
"eslint-plugin-import": "^2.30.1",
|
|
58
55
|
"eslint-plugin-prettier": "^5.4.0",
|
|
56
|
+
"ioredis": "^5.8.0",
|
|
59
57
|
"jest": "^29.6.1",
|
|
60
58
|
"semantic-release": "^22.0.12"
|
|
61
59
|
},
|
package/src/defaults.js
CHANGED
|
@@ -13,6 +13,21 @@ module.exports = {
|
|
|
13
13
|
|
|
14
14
|
specProtocol: 'http',
|
|
15
15
|
specHost: '127.0.0.1',
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* How long register() waits for the registry's `registerResponse` /
|
|
19
|
+
* `register.confirmed` message before it fails.
|
|
20
|
+
*
|
|
21
|
+
* Owned here (module default), injectable per client via the
|
|
22
|
+
* `registrationTimeoutMs` constructor option and per call via
|
|
23
|
+
* `serviceInfo.timeout`. Never read from the environment inside the client.
|
|
24
|
+
*
|
|
25
|
+
* 30 s matches the registry's own validation timeout
|
|
26
|
+
* (`api/infra/api_services_registry/src/listeners/registry.listener.js` —
|
|
27
|
+
* `}, 30000); // 30 seconds timeout for Registry`), so a service gives the
|
|
28
|
+
* registry the full window it needs before declaring the round-trip dead.
|
|
29
|
+
*/
|
|
30
|
+
registrationTimeoutMs: 30000,
|
|
16
31
|
};
|
|
17
32
|
|
|
18
33
|
|
package/src/events.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Legacy `apiDescriptionRequest` / `apiDescriptionSent` events were removed —
|
|
9
9
|
* the full service specification travels as part of the `register` message.
|
|
10
10
|
*
|
|
11
|
-
* @see api/docs/
|
|
11
|
+
* @see api/docs/biz/30-operations/registration-wire.md §2 MQ wire format
|
|
12
12
|
* @module @onlineapps/connector-registry-client/src/events
|
|
13
13
|
*/
|
|
14
14
|
|
package/src/registryClient.js
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
* - 'registerRejected'
|
|
20
20
|
* - 'error'
|
|
21
21
|
*
|
|
22
|
-
* @see api/docs/
|
|
23
|
-
* @see api/docs/
|
|
22
|
+
* @see api/docs/biz/30-operations/registration-wire.md §2 MQ wire format
|
|
23
|
+
* @see api/docs/biz/30-operations/schema-v3.md
|
|
24
24
|
*
|
|
25
25
|
* @module @onlineapps/connector-registry-client/src/registryClient
|
|
26
26
|
*/
|
|
@@ -30,7 +30,7 @@ const QueueManager = require('./queueManager');
|
|
|
30
30
|
const RegistryEventConsumer = require('./registryEventConsumer');
|
|
31
31
|
const { v4: uuidv4 } = require('uuid');
|
|
32
32
|
const queueConfig = require('@onlineapps/mq-client-core/src/config/queueConfig');
|
|
33
|
-
const
|
|
33
|
+
const DEFAULTS = require('./defaults');
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class ServiceRegistryClient extends EventEmitter {
|
|
@@ -44,14 +44,25 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
44
44
|
* @param {string} [opts.apiQueue='api_services_queuer'] - Queue name for heartbeat and API traffic
|
|
45
45
|
* @param {string} [opts.registryQueue='registry.register'] - Queue name for registry messages
|
|
46
46
|
* @param {Object} [opts.validationProof=null] - Validation proof object {hash, data} (injected from outside)
|
|
47
|
+
* @param {number} [opts.registrationTimeoutMs] - How long register() waits for the registry response
|
|
48
|
+
* before rejecting. Default comes from ./defaults.js (30000 ms); never read from the environment here.
|
|
47
49
|
*/
|
|
48
50
|
constructor({ amqpUrl, serviceName, version, specificationEndpoint = '/api/v1/specification',
|
|
49
51
|
heartbeatInterval = 10000, apiQueue = 'api_services_queuer', registryQueue = 'registry.register',
|
|
50
|
-
registryUrl = null, redis = null, storageConfig = {}, validationProof = null
|
|
52
|
+
registryUrl = null, redis = null, storageConfig = {}, validationProof = null,
|
|
53
|
+
registrationTimeoutMs = DEFAULTS.registrationTimeoutMs }) {
|
|
51
54
|
super();
|
|
52
55
|
if (!amqpUrl || !serviceName || !version) {
|
|
53
56
|
throw new Error('amqpUrl, serviceName, and version are required');
|
|
54
57
|
}
|
|
58
|
+
if (typeof registrationTimeoutMs !== 'number' || !Number.isFinite(registrationTimeoutMs) || registrationTimeoutMs <= 0) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`[RegistryClient] ${serviceName}: Invalid registrationTimeoutMs - expected a positive number of milliseconds, `
|
|
61
|
+
+ `got ${JSON.stringify(registrationTimeoutMs)}. Fix: pass a positive registrationTimeoutMs `
|
|
62
|
+
+ `(module default ${DEFAULTS.registrationTimeoutMs}).`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
this.registrationTimeoutMs = registrationTimeoutMs;
|
|
55
66
|
this.serviceName = serviceName;
|
|
56
67
|
this.version = version;
|
|
57
68
|
// Spec endpoint is still configurable, but defaults are module-owned and can be overridden via runtime config.
|
|
@@ -385,8 +396,21 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
385
396
|
throw new Error('endpoints must be an array');
|
|
386
397
|
}
|
|
387
398
|
|
|
399
|
+
// FAIL-FAST: a client that was never initialized, or that has been closed, is not
|
|
400
|
+
// usable. It used to silently call queueManager.init() here, which re-opened a
|
|
401
|
+
// channel WITHOUT restarting the response consumer started by init() - so the
|
|
402
|
+
// registry answered into a queue nobody was reading and register() waited out the
|
|
403
|
+
// full timeout. No fallbacks (architecture-principles §3), fail fast (§4).
|
|
404
|
+
if (!this.queueManager.channel) {
|
|
405
|
+
throw new Error(
|
|
406
|
+
`[RegistryClient] ${this.serviceName}: Cannot register - the MQ channel is not open `
|
|
407
|
+
+ '(client not initialized or already closed). Expected: init() completed before register(). '
|
|
408
|
+
+ 'Fix: call init() on a fresh ServiceRegistryClient; a closed client is not reusable.'
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
388
412
|
const msgId = uuidv4();
|
|
389
|
-
// @see api/docs/
|
|
413
|
+
// @see api/docs/biz/30-operations/registration-wire.md §2 MQ wire format
|
|
390
414
|
const msg = {
|
|
391
415
|
id: msgId,
|
|
392
416
|
type: 'register',
|
|
@@ -422,8 +446,9 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
422
446
|
console.log(`[RegistryClient] ${this.serviceName}: ⚠️ NO validation proof available - Registry will perform Tier 2 validation`);
|
|
423
447
|
}
|
|
424
448
|
|
|
425
|
-
// Create promise to wait for registration response
|
|
426
|
-
|
|
449
|
+
// Create promise to wait for registration response.
|
|
450
|
+
// Timeout is injected (constructor) or overridden per call - never read from env here.
|
|
451
|
+
const timeout = serviceInfo.timeout || this.registrationTimeoutMs;
|
|
427
452
|
const responsePromise = new Promise((resolve, reject) => {
|
|
428
453
|
// Store resolver for this registration request
|
|
429
454
|
this.pendingRegistrations = this.pendingRegistrations || new Map();
|
|
@@ -434,7 +459,12 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
434
459
|
if (this.pendingRegistrations.has(msgId)) {
|
|
435
460
|
this.pendingRegistrations.delete(msgId);
|
|
436
461
|
this.pendingTimeouts.delete(msgId);
|
|
437
|
-
reject(new Error(
|
|
462
|
+
reject(new Error(
|
|
463
|
+
`[RegistryClient] ${this.serviceName}: Registration timed out - no response from the registry `
|
|
464
|
+
+ `within ${timeout}ms. Expected: api_services_registry consumes '${this.registryQueue}' and answers `
|
|
465
|
+
+ `on '${this.serviceRegistryQueue}'. Fix: start api_services_registry (docker compose up -d `
|
|
466
|
+
+ 'api_services_registry) or raise registrationTimeoutMs.'
|
|
467
|
+
));
|
|
438
468
|
}
|
|
439
469
|
}, timeout);
|
|
440
470
|
|
|
@@ -442,10 +472,10 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
442
472
|
this.pendingTimeouts.set(msgId, timeoutId);
|
|
443
473
|
});
|
|
444
474
|
|
|
445
|
-
//
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
475
|
+
// The publish path below can throw before anyone awaits responsePromise. This no-op
|
|
476
|
+
// handler marks the promise as handled so an abort never surfaces as an unhandled
|
|
477
|
+
// rejection; the real result is still awaited at the end of this method.
|
|
478
|
+
responsePromise.catch(() => {});
|
|
449
479
|
|
|
450
480
|
// Send registration message to registry
|
|
451
481
|
// CRITICAL: Use queueConfig.js to get correct parameters (TTL, max-length, etc.)
|
|
@@ -457,20 +487,21 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
457
487
|
} catch (queueError) {
|
|
458
488
|
const errorMsg = `[RegistryClient] ${this.serviceName}: Cannot register - infrastructure queue verification failed: ${queueError.message}`;
|
|
459
489
|
console.error(errorMsg);
|
|
460
|
-
throw new Error(errorMsg);
|
|
490
|
+
throw this._abortPendingRegistration(msgId, new Error(errorMsg));
|
|
461
491
|
}
|
|
462
492
|
|
|
463
493
|
console.log(`[RegistryClient] ${this.serviceName}: Sending registration message to queue: ${this.registryQueue}`);
|
|
464
494
|
console.log(`[RegistryClient] ${this.serviceName}: Message type: ${msg.type}, serviceName: ${msg.serviceName}, version: ${msg.version}`);
|
|
465
|
-
|
|
466
|
-
// Send message synchronously (sendToQueue is synchronous, returns boolean)
|
|
467
|
-
//
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
495
|
+
|
|
496
|
+
// Send message synchronously (sendToQueue is synchronous, returns boolean).
|
|
497
|
+
// The channel existed when register() started; it can still have been closed while
|
|
498
|
+
// the infrastructure-queue check awaited above.
|
|
499
|
+
if (!this.queueManager.channel || this.queueManager.channel.closed) {
|
|
500
|
+
throw this._abortPendingRegistration(msgId, new Error(
|
|
501
|
+
`[RegistryClient] ${this.serviceName}: Cannot publish - the MQ channel closed before the registration `
|
|
502
|
+
+ 'message was sent. Expected: an open channel for the whole register() call. '
|
|
503
|
+
+ 'Fix: re-create the client (init() + register()) once the MQ connection is back.'
|
|
504
|
+
));
|
|
474
505
|
}
|
|
475
506
|
|
|
476
507
|
try {
|
|
@@ -486,31 +517,49 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
486
517
|
} catch (sendError) {
|
|
487
518
|
const errorMsg = `[RegistryClient] ${this.serviceName}: Failed to send registration message: ${sendError.message}`;
|
|
488
519
|
console.error(errorMsg);
|
|
489
|
-
throw new Error(errorMsg);
|
|
520
|
+
throw this._abortPendingRegistration(msgId, new Error(errorMsg));
|
|
490
521
|
}
|
|
491
522
|
|
|
492
523
|
this.emit('registerSent', msg);
|
|
493
524
|
|
|
494
|
-
// Wait for registration response from registry
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
525
|
+
// Wait for registration response from registry.
|
|
526
|
+
// A timeout or an abort REJECTS - it is not downgraded to `{ success: false }`,
|
|
527
|
+
// which is what a legitimate registry rejection looks like (e.g. missing
|
|
528
|
+
// validation proof). The caller must be able to tell "the registry said no"
|
|
529
|
+
// from "the registry never answered" (architecture-principles §3, §4).
|
|
530
|
+
console.log(`[RegistryClient] ${this.serviceName}: [AWAIT] Waiting for registration response promise...`);
|
|
531
|
+
const response = await responsePromise;
|
|
532
|
+
console.log(`[RegistryClient] ${this.serviceName}: [AWAIT] ✓ Promise resolved, response received:`, {
|
|
533
|
+
hasResponse: !!response,
|
|
534
|
+
responseType: typeof response,
|
|
535
|
+
responseKeys: response ? Object.keys(response) : [],
|
|
536
|
+
hasSuccess: !!response?.success,
|
|
537
|
+
hasCertificate: !!response?.certificate
|
|
538
|
+
});
|
|
539
|
+
return response;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Cancels one in-flight registration: clears its timeout, drops it from the pending
|
|
544
|
+
* maps and rejects its promise with `error`.
|
|
545
|
+
* @param {string} msgId - Registration request id
|
|
546
|
+
* @param {Error} error - Error the pending promise is rejected with
|
|
547
|
+
* @returns {Error} the same error, so callers can `throw this._abortPendingRegistration(...)`
|
|
548
|
+
* @private
|
|
549
|
+
*/
|
|
550
|
+
_abortPendingRegistration(msgId, error) {
|
|
551
|
+
const pending = this.pendingRegistrations && this.pendingRegistrations.get(msgId);
|
|
552
|
+
if (this.pendingTimeouts && this.pendingTimeouts.has(msgId)) {
|
|
553
|
+
clearTimeout(this.pendingTimeouts.get(msgId));
|
|
554
|
+
this.pendingTimeouts.delete(msgId);
|
|
555
|
+
}
|
|
556
|
+
if (this.pendingRegistrations) {
|
|
557
|
+
this.pendingRegistrations.delete(msgId);
|
|
513
558
|
}
|
|
559
|
+
if (pending) {
|
|
560
|
+
pending.reject(error);
|
|
561
|
+
}
|
|
562
|
+
return error;
|
|
514
563
|
}
|
|
515
564
|
|
|
516
565
|
/**
|
|
@@ -737,17 +786,20 @@ class ServiceRegistryClient extends EventEmitter {
|
|
|
737
786
|
* @returns {Promise<void>}
|
|
738
787
|
*/
|
|
739
788
|
async close() {
|
|
740
|
-
//
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
789
|
+
// Abort every in-flight registration. Clearing the maps without rejecting (the
|
|
790
|
+
// behaviour until 2026-08-27) cleared the timeout too, so the register() promise
|
|
791
|
+
// never settled and its caller waited forever.
|
|
792
|
+
if (this.pendingRegistrations) {
|
|
793
|
+
for (const msgId of Array.from(this.pendingRegistrations.keys())) {
|
|
794
|
+
this._abortPendingRegistration(msgId, new Error(
|
|
795
|
+
`[RegistryClient] ${this.serviceName}: Registration aborted - the client was closed while waiting `
|
|
796
|
+
+ 'for the registry response. Expected: register() settles before close(). '
|
|
797
|
+
+ 'Fix: await register() before close(), or init() and register() again on a new client.'
|
|
798
|
+
));
|
|
744
799
|
}
|
|
745
|
-
this.pendingTimeouts.clear();
|
|
746
800
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
if (this.pendingRegistrations) {
|
|
750
|
-
this.pendingRegistrations.clear();
|
|
801
|
+
if (this.pendingTimeouts) {
|
|
802
|
+
this.pendingTimeouts.clear();
|
|
751
803
|
}
|
|
752
804
|
|
|
753
805
|
this.stopHeartbeat();
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
|
|
11
11
|
const EventEmitter = require('events');
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
// The storage connector arrives by INJECTION. There used to be an implicit
|
|
14
|
+
// default here — `require('@onlineapps/connector-storage')` inside a try/catch
|
|
15
|
+
// that turned the miss into `null`. Three defects in five lines: the package
|
|
16
|
+
// does not exist and nothing declared it (§20), the catch is a silent fallback
|
|
17
|
+
// (architecture-principles.md §3), and the consequence surfaced layers away
|
|
18
|
+
// from the cause, as "Storage connector not available for downloading specs" at
|
|
19
|
+
// download time. The constructor now fail-fasts at the moment the caller asks
|
|
20
|
+
// for storage without supplying one.
|
|
21
21
|
|
|
22
22
|
class RegistryEventConsumer extends EventEmitter {
|
|
23
23
|
/**
|
|
@@ -37,12 +37,12 @@ class RegistryEventConsumer extends EventEmitter {
|
|
|
37
37
|
this.serviceName = serviceName;
|
|
38
38
|
this.redis = redis;
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
this.StorageConnectorClass = StorageConnector || DefaultStorageConnector;
|
|
40
|
+
this.StorageConnectorClass = StorageConnector;
|
|
42
41
|
|
|
43
|
-
//
|
|
44
|
-
// Service discovery (active/inactive status) does NOT require
|
|
45
|
-
//
|
|
42
|
+
// Storage is initialised for MinIO access ONLY when config is explicitly
|
|
43
|
+
// provided. Service discovery (active/inactive status) does NOT require
|
|
44
|
+
// storage, so without a complete storageConfig this runs in "index-only"
|
|
45
|
+
// mode and spec downloading is off.
|
|
46
46
|
const hasStorageConfig =
|
|
47
47
|
storageConfig &&
|
|
48
48
|
typeof storageConfig === 'object' &&
|
|
@@ -51,7 +51,17 @@ class RegistryEventConsumer extends EventEmitter {
|
|
|
51
51
|
storageConfig.accessKey &&
|
|
52
52
|
storageConfig.secretKey;
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
if (hasStorageConfig && !this.StorageConnectorClass) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'[RegistryEventConsumer] storageConfig was given without a StorageConnector - '
|
|
57
|
+
+ 'Expected: the connector class injected as options.StorageConnector, because this package '
|
|
58
|
+
+ 'declares no storage implementation of its own. '
|
|
59
|
+
+ 'Fix: pass StorageConnector (e.g. require("@onlineapps/conn-base-storage")), or drop '
|
|
60
|
+
+ 'storageConfig to run in index-only mode.'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.storage = hasStorageConfig
|
|
55
65
|
? new this.StorageConnectorClass({
|
|
56
66
|
...storageConfig,
|
|
57
67
|
cacheEnabled,
|
|
@@ -317,9 +327,16 @@ class RegistryEventConsumer extends EventEmitter {
|
|
|
317
327
|
return this.specCache.get(fingerprint);
|
|
318
328
|
}
|
|
319
329
|
|
|
320
|
-
//
|
|
330
|
+
// Index-only mode is a legitimate construction, so this is not a bug — but
|
|
331
|
+
// the caller asked for something this consumer was never built to do, and
|
|
332
|
+
// has to learn which of the two to change (architecture-principles.md §5).
|
|
321
333
|
if (!this.storage) {
|
|
322
|
-
throw new Error(
|
|
334
|
+
throw new Error(
|
|
335
|
+
'[RegistryEventConsumer] Spec download requested in index-only mode - '
|
|
336
|
+
+ 'Expected: a storage connector, which this consumer was constructed without. '
|
|
337
|
+
+ 'Fix: construct it with storageConfig and StorageConnector, or read the service '
|
|
338
|
+
+ 'index only (isServiceActive, getActiveServices, getServiceInfo).'
|
|
339
|
+
);
|
|
323
340
|
}
|
|
324
341
|
|
|
325
342
|
// Download from MinIO with verification
|