@onlineapps/conn-orch-registry 2.0.2 → 3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-orch-registry",
3
- "version": "2.0.2",
3
+ "version": "3.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": [
package/src/defaults.js CHANGED
@@ -9,6 +9,10 @@
9
9
  */
10
10
 
11
11
  module.exports = {
12
+ // Key prefix the registry builds its Redis keys with (its own
13
+ // REDIS_REGISTRY_KEY_PREFIX). Module-owned default, overridable per instance —
14
+ // never a literal inside a method (architecture-principles §2).
15
+ registryKeyPrefix: 'registry:',
12
16
  // `specificationEndpoint`, `specProtocol` and `specHost` lived here until
13
17
  // 2026-08-29. Their only reader was src/config.js, whose only readers were its
14
18
  // own tests — a declaration nothing runtime read, which forced every
@@ -50,6 +50,7 @@ class ServiceRegistryClient extends EventEmitter {
50
50
  constructor({ amqpUrl, serviceName, version, specificationEndpoint = '/api/v1/specification',
51
51
  heartbeatInterval = 10000, apiQueue = 'api_services_queuer', registryQueue = 'registry.register',
52
52
  registryUrl = null, redis = null, storageConfig = {}, validationProof = null,
53
+ registryKeyPrefix = DEFAULTS.registryKeyPrefix,
53
54
  registrationTimeoutMs = DEFAULTS.registrationTimeoutMs }) {
54
55
  super();
55
56
  if (!amqpUrl || !serviceName || !version) {
@@ -77,6 +78,7 @@ class ServiceRegistryClient extends EventEmitter {
77
78
  // Event consumer (optional, activated via subscribeToChanges)
78
79
  this.eventConsumer = null;
79
80
  this.redis = redis;
81
+ this.registryKeyPrefix = registryKeyPrefix;
80
82
  this.storageConfig = storageConfig;
81
83
 
82
84
  // Validation proof (injected via constructor - DEPENDENCY INJECTION)
@@ -712,49 +714,61 @@ class ServiceRegistryClient extends EventEmitter {
712
714
  /**
713
715
  * Get basic service state for service discovery.
714
716
  *
715
- * cookbook-router expects `registryClient.getService(serviceName)` to exist and
716
- * return an object with `status`. RegistryEventConsumer stores status as
717
- * 'ACTIVE'/'INACTIVE' (uppercase), so we normalize to lowercase.
717
+ * Reads the projection the registry already maintains — the `<prefix>services`
718
+ * hash, one JSON summary per service name. **Not HTTP.** A biz container runs no
719
+ * HTTP listener and makes no HTTP call to the registry either: ADR 0005
720
+ * (api/docs/biz/80-decisions/0005-no-http-in-biz-containers.md), applied to
721
+ * discovery by the owner on 2026-09-02 (confirmation biz-discovery-redis-001).
722
+ * Until then this method polled `GET /services`, which is what made every biz
723
+ * container an HTTP client of the registry ~142x a day.
724
+ *
725
+ * There is deliberately no HTTP fallback. A missing Redis client is a wiring
726
+ * error and says so (architecture-principles §3, §4) — falling back would
727
+ * restore the very traffic this change removes, and hide the misconfiguration.
728
+ *
729
+ * cookbook-router expects `{ status }`; the registry writes it lowercase
730
+ * ('active'/'inactive') into the summary, so no normalization happens here.
718
731
  *
719
732
  * @param {string} serviceName
720
- * @returns {Promise<{serviceName: string, status: 'active'|'inactive', version?: string, fingerprint?: string, bucket?: string, path?: string, updatedAt?: string} | null>}
733
+ * @returns {Promise<{serviceName: string, status: string, version?: string, lastHeartbeatAt?: string} | null>}
721
734
  */
722
735
  async getService(serviceName) {
723
736
  if (!serviceName || typeof serviceName !== 'string') {
724
737
  throw new Error('[RegistryClient] getService - serviceName is required and must be a string');
725
738
  }
726
739
 
727
- if (!this.registryUrl || typeof this.registryUrl !== 'string') {
728
- throw new Error('[RegistryClient] getService - Missing required config: registryUrl. Fix: pass wrapper.registry.url into ServiceRegistryClient.');
729
- }
730
-
731
- // Primary source of truth for service availability is the central registry HTTP API.
732
- // This avoids relying on optional event subscriptions and ensures deterministic routing.
733
- const url = `${this.registryUrl.replace(/\/$/, '')}/services`;
734
- let response;
735
- try {
736
- response = await fetch(url, { method: 'GET' });
737
- } catch (err) {
738
- throw new Error(`[RegistryClient] getService - Registry HTTP request failed (${url}). Problem: ${err.message}`);
740
+ if (!this.redis || typeof this.redis.hGet !== 'function') {
741
+ throw new Error(
742
+ '[RegistryClient] getService - Missing Redis client for service discovery. '
743
+ + 'Expected/Fix: pass `redis` (a connected client exposing hGet) into the '
744
+ + 'ServiceRegistryClient constructor; @onlineapps/service-wrapper does this '
745
+ + 'from its own Redis configuration. Discovery reads the registry projection, '
746
+ + 'never HTTP (ADR 0005).'
747
+ );
739
748
  }
740
749
 
741
- if (!response.ok) {
742
- throw new Error(`[RegistryClient] getService - Registry HTTP request failed (${url}). Status: ${response.status}`);
750
+ const key = `${this.registryKeyPrefix}services`;
751
+ const raw = await this.redis.hGet(key, serviceName);
752
+ if (!raw) {
753
+ return null;
743
754
  }
744
755
 
745
- const data = await response.json();
746
- const entry = data && data.services ? data.services[serviceName] : null;
747
- if (!entry) {
748
- return null;
756
+ let entry;
757
+ try {
758
+ entry = JSON.parse(raw);
759
+ } catch (err) {
760
+ throw new Error(
761
+ `[RegistryClient] getService - Registry projection entry for "${serviceName}" is not valid JSON `
762
+ + `(key ${key}). Problem: ${err.message}. Fix: the registry owns this value; `
763
+ + 'a corrupt entry is a registry-side defect, not something a caller may guess around.'
764
+ );
749
765
  }
750
766
 
751
- // Registry stores status as 'active'/'inactive' (lowercase).
752
767
  return {
753
768
  serviceName,
754
769
  status: entry.status,
755
770
  version: entry.version,
756
- lastHeartbeatAt: entry.lastHeartbeatAt,
757
- isAvailable: entry.isAvailable
771
+ lastHeartbeatAt: entry.lastHeartbeatAt
758
772
  };
759
773
  }
760
774