@onlineapps/conn-orch-registry 2.0.2 → 3.0.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/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.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": [
@@ -48,10 +48,6 @@
48
48
  "@semantic-release/npm": "^11.0.3",
49
49
  "@semantic-release/release-notes-generator": "^12.1.0",
50
50
  "eslint": "^8.44.0",
51
- "eslint-config-airbnb-base": "^15.0.0",
52
- "eslint-config-prettier": "^10.1.5",
53
- "eslint-plugin-import": "^2.30.1",
54
- "eslint-plugin-prettier": "^5.4.0",
55
51
  "ioredis": "^5.8.0",
56
52
  "jest": "^29.6.1",
57
53
  "semantic-release": "^22.0.12"
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,63 @@ 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.** The owner decided that
719
+ * on 2026-09-02 (confirmation biz-discovery-redis-001). It is its own decision,
720
+ * not a reading of ADR 0005: that ADR
721
+ * (api/docs/biz/80-decisions/0005-no-http-in-biz-containers.md) bans an HTTP
722
+ * LISTENER in a biz container and is silent on outbound calls — this extends the
723
+ * same reasoning to the calls a biz container makes.
724
+ * Until then this method polled `GET /services`, which is what made every biz
725
+ * container an HTTP client of the registry ~142x a day.
726
+ *
727
+ * There is deliberately no HTTP fallback. A missing Redis client is a wiring
728
+ * error and says so (architecture-principles §3, §4) — falling back would
729
+ * restore the very traffic this change removes, and hide the misconfiguration.
730
+ *
731
+ * cookbook-router expects `{ status }`; the registry writes it lowercase
732
+ * ('active'/'inactive') into the summary, so no normalization happens here.
718
733
  *
719
734
  * @param {string} serviceName
720
- * @returns {Promise<{serviceName: string, status: 'active'|'inactive', version?: string, fingerprint?: string, bucket?: string, path?: string, updatedAt?: string} | null>}
735
+ * @returns {Promise<{serviceName: string, status: string, version?: string, lastHeartbeatAt?: string} | null>}
721
736
  */
722
737
  async getService(serviceName) {
723
738
  if (!serviceName || typeof serviceName !== 'string') {
724
739
  throw new Error('[RegistryClient] getService - serviceName is required and must be a string');
725
740
  }
726
741
 
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}`);
742
+ if (!this.redis || typeof this.redis.hGet !== 'function') {
743
+ throw new Error(
744
+ '[RegistryClient] getService - Missing Redis client for service discovery. '
745
+ + 'Expected/Fix: pass `redis` (a connected client exposing hGet) into the '
746
+ + 'ServiceRegistryClient constructor; @onlineapps/service-wrapper does this '
747
+ + 'from its own Redis configuration. Discovery reads the registry projection, '
748
+ + 'never HTTP (owner decision biz-discovery-redis-001, 2026-09-02).'
749
+ );
739
750
  }
740
751
 
741
- if (!response.ok) {
742
- throw new Error(`[RegistryClient] getService - Registry HTTP request failed (${url}). Status: ${response.status}`);
752
+ const key = `${this.registryKeyPrefix}services`;
753
+ const raw = await this.redis.hGet(key, serviceName);
754
+ if (!raw) {
755
+ return null;
743
756
  }
744
757
 
745
- const data = await response.json();
746
- const entry = data && data.services ? data.services[serviceName] : null;
747
- if (!entry) {
748
- return null;
758
+ let entry;
759
+ try {
760
+ entry = JSON.parse(raw);
761
+ } catch (err) {
762
+ throw new Error(
763
+ `[RegistryClient] getService - Registry projection entry for "${serviceName}" is not valid JSON `
764
+ + `(key ${key}). Problem: ${err.message}. Fix: the registry owns this value; `
765
+ + 'a corrupt entry is a registry-side defect, not something a caller may guess around.'
766
+ );
749
767
  }
750
768
 
751
- // Registry stores status as 'active'/'inactive' (lowercase).
752
769
  return {
753
770
  serviceName,
754
771
  status: entry.status,
755
772
  version: entry.version,
756
- lastHeartbeatAt: entry.lastHeartbeatAt,
757
- isAvailable: entry.isAvailable
773
+ lastHeartbeatAt: entry.lastHeartbeatAt
758
774
  };
759
775
  }
760
776