@ebarahona/loopback-transport-core 1.0.0 → 1.1.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.
Files changed (105) hide show
  1. package/README.md +442 -35
  2. package/dist/client/client-proxy.d.ts +73 -42
  3. package/dist/client/client-proxy.js +71 -42
  4. package/dist/client/client-proxy.js.map +1 -1
  5. package/dist/client/index.d.ts +1 -1
  6. package/dist/client/index.js +3 -15
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/context/execution-context.d.ts +58 -11
  9. package/dist/context/execution-context.js +62 -17
  10. package/dist/context/execution-context.js.map +1 -1
  11. package/dist/context/index.d.ts +2 -1
  12. package/dist/context/index.js +3 -15
  13. package/dist/context/index.js.map +1 -1
  14. package/dist/decorators/constants.d.ts +20 -10
  15. package/dist/decorators/constants.js +6 -2
  16. package/dist/decorators/constants.js.map +1 -1
  17. package/dist/decorators/event-handler.decorator.d.ts +10 -7
  18. package/dist/decorators/event-handler.decorator.js +18 -13
  19. package/dist/decorators/event-handler.decorator.js.map +1 -1
  20. package/dist/decorators/index.d.ts +5 -4
  21. package/dist/decorators/index.js +11 -18
  22. package/dist/decorators/index.js.map +1 -1
  23. package/dist/decorators/message-handler.decorator.d.ts +8 -5
  24. package/dist/decorators/message-handler.decorator.js +16 -11
  25. package/dist/decorators/message-handler.decorator.js.map +1 -1
  26. package/dist/decorators/payload.decorator.d.ts +16 -7
  27. package/dist/decorators/payload.decorator.js +16 -8
  28. package/dist/decorators/payload.decorator.js.map +1 -1
  29. package/dist/discovery/discovery.service.d.ts +143 -0
  30. package/dist/discovery/discovery.service.js +165 -0
  31. package/dist/discovery/discovery.service.js.map +1 -0
  32. package/dist/discovery/event-handler-discoverer.d.ts +19 -0
  33. package/dist/discovery/event-handler-discoverer.js +50 -0
  34. package/dist/discovery/event-handler-discoverer.js.map +1 -0
  35. package/dist/discovery/handler-discoverer.d.ts +48 -0
  36. package/dist/discovery/handler-discoverer.js +3 -0
  37. package/dist/discovery/handler-discoverer.js.map +1 -0
  38. package/dist/discovery/handler-kind.d.ts +23 -0
  39. package/dist/discovery/handler-kind.js +14 -0
  40. package/dist/discovery/handler-kind.js.map +1 -0
  41. package/dist/discovery/index.d.ts +7 -0
  42. package/dist/discovery/index.js +13 -0
  43. package/dist/discovery/index.js.map +1 -0
  44. package/dist/discovery/message-handler-discoverer.d.ts +19 -0
  45. package/dist/discovery/message-handler-discoverer.js +50 -0
  46. package/dist/discovery/message-handler-discoverer.js.map +1 -0
  47. package/dist/discovery/registered-handler.d.ts +35 -0
  48. package/dist/discovery/registered-handler.js +3 -0
  49. package/dist/discovery/registered-handler.js.map +1 -0
  50. package/dist/helpers/errors.d.ts +61 -0
  51. package/dist/helpers/errors.js +80 -0
  52. package/dist/helpers/errors.js.map +1 -0
  53. package/dist/helpers/index.d.ts +2 -0
  54. package/dist/helpers/index.js +14 -0
  55. package/dist/helpers/index.js.map +1 -0
  56. package/dist/helpers/register-server.d.ts +44 -0
  57. package/dist/helpers/register-server.js +72 -0
  58. package/dist/helpers/register-server.js.map +1 -0
  59. package/dist/index.d.ts +15 -7
  60. package/dist/index.js +33 -9
  61. package/dist/index.js.map +1 -1
  62. package/dist/interfaces/index.d.ts +4 -4
  63. package/dist/interfaces/index.js +0 -18
  64. package/dist/interfaces/index.js.map +1 -1
  65. package/dist/interfaces/message-handler.interface.d.ts +12 -6
  66. package/dist/interfaces/packet.interface.d.ts +20 -2
  67. package/dist/interfaces/transport-client.interface.d.ts +25 -13
  68. package/dist/interfaces/transport-server.interface.d.ts +36 -10
  69. package/dist/keys.d.ts +132 -46
  70. package/dist/keys.js +131 -68
  71. package/dist/keys.js.map +1 -1
  72. package/dist/registry/handler-registry.d.ts +94 -24
  73. package/dist/registry/handler-registry.js +195 -89
  74. package/dist/registry/handler-registry.js.map +1 -1
  75. package/dist/registry/index.d.ts +1 -1
  76. package/dist/registry/index.js +3 -15
  77. package/dist/registry/index.js.map +1 -1
  78. package/dist/serializers/cloudevents-serializer.d.ts +107 -0
  79. package/dist/serializers/cloudevents-serializer.js +130 -0
  80. package/dist/serializers/cloudevents-serializer.js.map +1 -0
  81. package/dist/serializers/index.d.ts +4 -1
  82. package/dist/serializers/index.js +7 -15
  83. package/dist/serializers/index.js.map +1 -1
  84. package/dist/serializers/serializer.interface.d.ts +49 -16
  85. package/dist/serializers/serializer.interface.js +41 -15
  86. package/dist/serializers/serializer.interface.js.map +1 -1
  87. package/dist/server/index.d.ts +2 -1
  88. package/dist/server/index.js +3 -15
  89. package/dist/server/index.js.map +1 -1
  90. package/dist/server/server-base.d.ts +125 -52
  91. package/dist/server/server-base.js +168 -57
  92. package/dist/server/server-base.js.map +1 -1
  93. package/dist/transport.component.d.ts +21 -8
  94. package/dist/transport.component.js +40 -18
  95. package/dist/transport.component.js.map +1 -1
  96. package/dist/transport.observer.d.ts +87 -0
  97. package/dist/transport.observer.js +296 -0
  98. package/dist/transport.observer.js.map +1 -0
  99. package/dist/utils/normalize-pattern.d.ts +11 -6
  100. package/dist/utils/normalize-pattern.js +18 -13
  101. package/dist/utils/normalize-pattern.js.map +1 -1
  102. package/package.json +58 -16
  103. package/dist/transport-booter.d.ts +0 -31
  104. package/dist/transport-booter.js +0 -117
  105. package/dist/transport-booter.js.map +0 -1
@@ -1,15 +1,19 @@
1
- import { Observable } from 'rxjs';
2
- import { TransportServer, TransportStatus, MessageHandler, WritePacket, IncomingRequest, IncomingEvent } from '../interfaces';
3
- import { Serializer, Deserializer } from '../serializers';
1
+ import type { Application } from '@loopback/core';
2
+ import type { Observable } from 'rxjs';
3
+ import type { IncomingEvent, IncomingRequest, MessageHandler, TransportServer, TransportStatus, WritePacket } from '../interfaces';
4
+ import { type Deserializer, type Serializer } from '../serializers';
4
5
  /**
5
6
  * Result of handling a message. Separates "what was sent to the caller"
6
- * from "should the broker ack or nack this message."
7
+ * from "should the broker ack or nack this message".
7
8
  *
8
9
  * - `success`: Handler completed. Response was sent. Adapter should ack.
9
- * - `handler-error`: Handler threw or Observable errored. Error response
10
- * was sent to the caller. Adapter should ack (the error was handled).
10
+ * - `handler-error`: Handler threw or Observable errored. Error
11
+ * response was sent to the caller. Adapter should ack (the error was
12
+ * handled).
11
13
  * - `infrastructure-error`: No handler found, serialization failure, or
12
14
  * other framework-level failure. Adapter should nack/dead-letter.
15
+ *
16
+ * @public
13
17
  */
14
18
  export interface HandlerResult {
15
19
  readonly outcome: 'success' | 'handler-error' | 'infrastructure-error';
@@ -19,16 +23,19 @@ export interface HandlerResult {
19
23
  * Abstract base class for transport servers.
20
24
  *
21
25
  * Manages the handler registry, message dispatching, and serialization.
22
- * Each transport (Kafka, RabbitMQ, gRPC, MQTT, NATS) extends this
23
- * class and implements listen/close/unwrap.
26
+ * Each transport (Kafka, RabbitMQ, gRPC, MQTT, NATS) extends this class
27
+ * and implements `listen`/`close`/`unwrap`.
24
28
  *
25
29
  * Subclasses should:
26
- * - Call deserializer.deserialize() on raw broker messages before
27
- * passing them to handleMessage()/handleEvent()
28
- * - Call serializer.serialize() on outbound responses
29
- * - Call setStatus('connected') in listen() when ready
30
- * - Call setStatus('disconnected') in close() when stopped
31
- * - Call dispose() only when the server will never be restarted
30
+ *
31
+ * - Call `deserializer.deserialize()` on raw broker messages before
32
+ * passing them to `handleMessage()` / `handleEvent()`.
33
+ * - Call `serializer.serialize()` on outbound responses.
34
+ * - Call `setStatus('connected')` in `listen()` when ready.
35
+ * - Call `setStatus('disconnected')` in `close()` when stopped.
36
+ * - Call `dispose()` only when the server will never be restarted.
37
+ *
38
+ * @public
32
39
  */
33
40
  export declare abstract class ServerBase implements TransportServer {
34
41
  protected readonly messageHandlers: Map<string, MessageHandler<unknown, unknown, unknown>[]>;
@@ -43,108 +50,174 @@ export declare abstract class ServerBase implements TransportServer {
43
50
  handlerTimeoutMs?: number;
44
51
  });
45
52
  /**
46
- * Start listening for messages from the broker.
47
- * Subclasses should call setStatus('connected') when ready.
53
+ * Resolve the serializer/deserializer pair for this server from the
54
+ * app container, honoring transport-scoped \> generic \> subclass-default
55
+ * precedence.
56
+ *
57
+ * Called by the transport lifecycle observer between
58
+ * `bindToServers` and `listen()`. Plugins contribute tag-based
59
+ * serializers via {@link SERIALIZER_TAG} / {@link DESERIALIZER_TAG};
60
+ * subclass defaults set via the `protected serializer` /
61
+ * `protected deserializer` fields are still honored when no tagged
62
+ * binding matches.
63
+ *
64
+ * Resolution precedence (most specific wins):
65
+ *
66
+ * 1. Transport-scoped binding (tagged with `SERIALIZER_TAG` /
67
+ * `DESERIALIZER_TAG` AND `TRANSPORT_NAME_TAG` equal to this
68
+ * server's transport name).
69
+ * 2. Generic binding (tagged with `SERIALIZER_TAG` /
70
+ * `DESERIALIZER_TAG` but no `TRANSPORT_NAME_TAG`).
71
+ * 3. Subclass default — the existing `protected serializer` /
72
+ * `protected deserializer` field value.
73
+ *
74
+ * @public
75
+ * @param app - The LoopBack application container to resolve
76
+ * serializer bindings from.
77
+ */
78
+ resolveSerializer(app: Application): Promise<void>;
79
+ /**
80
+ * Find the transport name tag for this server instance by scanning
81
+ * `TRANSPORT_SERVER_TAG` bindings and matching the resolved instance
82
+ * against `this`. Returns `undefined` if the server is not bound (in
83
+ * tests, or before the booter wires servers up), in which case
84
+ * `resolveSerializer` falls back to generic-only matching.
85
+ */
86
+ private findTransportName;
87
+ /**
88
+ * Resolve a tagged binding, preferring transport-scoped over generic.
89
+ * Returns `undefined` if no tagged binding exists.
90
+ */
91
+ private lookupTaggedBinding;
92
+ /**
93
+ * Start listening for messages from the broker. Subclasses should
94
+ * call `setStatus('connected')` when ready.
95
+ *
96
+ * @public
48
97
  */
49
98
  abstract listen(): Promise<void>;
50
99
  /**
51
- * Stop listening and close connections.
52
- * Subclasses should call setStatus('disconnected') when stopped.
53
- * Call dispose() only when the server will never be restarted.
100
+ * Stop listening and close connections. Subclasses should call
101
+ * `setStatus('disconnected')` when stopped. Call `dispose()` only
102
+ * when the server will never be restarted.
103
+ *
104
+ * @public
54
105
  */
55
106
  abstract close(): Promise<void>;
56
107
  /**
57
108
  * Access the underlying native server/consumer.
109
+ *
110
+ * @public
111
+ * @typeParam T - Caller-asserted native server shape.
58
112
  */
59
113
  abstract unwrap<T>(): T;
60
114
  /**
61
- * Emit a status change. Called by transport adapters in listen() and close().
115
+ * Emit a status change. Called by transport adapters in `listen()`
116
+ * and `close()`.
62
117
  */
63
118
  protected setStatus(status: TransportStatus): void;
64
119
  /**
65
- * Permanently complete the status stream. After this call, no further
66
- * status emissions are possible and the server instance cannot restart.
67
- * Existing subscribers receive the complete notification.
120
+ * Permanently complete the status stream. After this call, no
121
+ * further status emissions are possible and the server instance
122
+ * cannot restart. Existing subscribers receive the complete
123
+ * notification.
68
124
  *
69
- * Call only when the server is being permanently disposed.
70
- * Normal close() should call setStatus('disconnected'), NOT dispose().
125
+ * Call only when the server is being permanently disposed. Normal
126
+ * `close()` should call `setStatus('disconnected')`, NOT `dispose()`.
71
127
  */
72
128
  protected dispose(): void;
73
129
  /**
74
130
  * Register a handler for a message pattern.
75
131
  *
76
- * For request/response handlers (@messageHandler): duplicate patterns
77
- * throw an error. Only one handler per pattern is allowed.
132
+ * For request/response handlers (`@messageHandler`): duplicate
133
+ * patterns throw an error. Only one handler per pattern is allowed.
134
+ *
135
+ * For event handlers (`@eventHandler`): multiple handlers on the
136
+ * same pattern are stored in an array and all execute.
78
137
  *
79
- * For event handlers (@eventHandler): multiple handlers on the same
80
- * pattern are stored in an array and all execute.
138
+ * Mixing `@messageHandler` and `@eventHandler` on the same pattern
139
+ * throws.
81
140
  *
82
- * Mixing @messageHandler and @eventHandler on the same pattern throws.
141
+ * Handlers are never mutated. Each server owns its own handler
142
+ * array, so the same handler object can be safely shared across
143
+ * servers.
83
144
  *
84
- * Handlers are never mutated. Each server owns its own handler array,
85
- * so the same handler object can be safely shared across servers.
145
+ * @public
146
+ * @param pattern - The message pattern (already normalized by the registry).
147
+ * @param handler - The handler to register.
148
+ * @throws TransportConfigError When a request handler is duplicated
149
+ * or a request/event mix is attempted on the same pattern.
86
150
  */
87
151
  addHandler(pattern: string, handler: MessageHandler): void;
88
152
  /**
89
153
  * Get all registered handlers. Returns a defensive copy.
154
+ *
155
+ * @public
90
156
  */
91
157
  getHandlers(): ReadonlyMap<string, readonly MessageHandler[]>;
92
158
  /**
93
- * Remove all registered handlers.
94
- * Called before rebinding on restart to prevent duplicate registration.
159
+ * Remove all registered handlers. Called before rebinding on
160
+ * restart to prevent duplicate registration.
161
+ *
162
+ * @public
95
163
  */
96
164
  clearHandlers(): void;
97
165
  /**
98
166
  * Get handlers by pattern. Normalizes the pattern before lookup.
99
167
  * Returns a defensive copy for external consumers.
168
+ *
169
+ * @public
100
170
  */
101
171
  getHandlersByPattern(pattern: string | Record<string, unknown>): readonly MessageHandler[] | undefined;
102
172
  /**
103
- * Internal handler lookup. Returns the live array for dispatch performance.
173
+ * Internal handler lookup. Returns the live array for dispatch
174
+ * performance.
104
175
  */
105
176
  private lookupHandlers;
106
177
  /**
107
178
  * Handle a request/response message.
108
179
  *
109
- * Invokes the handler and calls respond() with each result value.
180
+ * Invokes the handler and calls `respond()` with each result value.
110
181
  * Observable subscriptions are tracked and cleaned up on completion,
111
182
  * error, or timeout.
112
183
  *
113
- * Returns a HandlerResult that separates response semantics from
114
- * broker settlement semantics:
184
+ * Returns a {@link HandlerResult} that separates response semantics
185
+ * from broker settlement semantics:
115
186
  *
116
187
  * - `success`: Handler completed normally. Ack the message.
117
188
  * - `handler-error`: Handler threw or Observable errored. The error
118
- * response was already sent to the caller. Ack the message --
119
- * the error was handled as an application-level response.
189
+ * response was already sent to the caller. Ack the message — the
190
+ * error was handled as an application-level response.
120
191
  * - `infrastructure-error`: No handler found or framework failure.
121
192
  * Nack/dead-letter the message.
122
193
  *
123
- * Observable handlers that do not complete within handlerTimeoutMs
124
- * are terminated with a timeout error (handler-error).
194
+ * Observable handlers that do not complete within
195
+ * `handlerTimeoutMs` are terminated with a timeout error
196
+ * (`handler-error`).
125
197
  *
126
- * If respond() throws (adapter publication failure), the error is
127
- * caught and returned as infrastructure-error.
198
+ * If `respond()` throws (adapter publication failure), the error is
199
+ * caught and returned as `infrastructure-error`.
128
200
  */
129
201
  protected handleMessage(request: IncomingRequest, respond: (packet: WritePacket) => void, context?: unknown): Promise<HandlerResult>;
130
202
  /**
131
- * Core handler execution. Separated from handleMessage so that
132
- * respond() failures bubble up and are caught by the outer wrapper.
203
+ * Core handler execution. Separated from `handleMessage` so that
204
+ * `respond()` failures bubble up and are caught by the outer
205
+ * wrapper.
133
206
  */
134
207
  private executeHandler;
135
208
  /**
136
209
  * Handle a fire-and-forget event.
137
210
  *
138
- * Invokes all chained handlers for the pattern.
139
- * Errors are logged but not propagated (fire-and-forget semantics).
211
+ * Invokes all chained handlers for the pattern. Errors are logged
212
+ * but not propagated (fire-and-forget semantics).
140
213
  *
141
- * Observable event handlers that do not complete within handlerTimeoutMs
142
- * are terminated and logged.
214
+ * Observable event handlers that do not complete within
215
+ * `handlerTimeoutMs` are terminated and logged.
143
216
  */
144
217
  protected handleEvent(event: IncomingEvent, context?: unknown): Promise<void>;
145
218
  /**
146
- * Normalize a pattern to a consistent string key.
147
- * Delegates to the shared normalizePattern utility.
219
+ * Normalize a pattern to a consistent string key. Delegates to the
220
+ * shared `normalizePattern` utility.
148
221
  */
149
222
  protected normalizePattern(pattern: string | Record<string, unknown>): string;
150
223
  /**
@@ -1,49 +1,137 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.ServerBase = void 0;
4
7
  const rxjs_1 = require("rxjs");
5
- const debug_1 = require("debug");
8
+ const debug_1 = __importDefault(require("debug"));
9
+ const errors_1 = require("../helpers/errors");
10
+ const keys_1 = require("../keys");
6
11
  const serializers_1 = require("../serializers");
7
12
  const utils_1 = require("../utils");
8
13
  const debug = (0, debug_1.default)('loopback:transport:server');
9
- const DEFAULT_HANDLER_TIMEOUT_MS = 30000;
14
+ const DEFAULT_HANDLER_TIMEOUT_MS = 30_000;
10
15
  /**
11
16
  * Abstract base class for transport servers.
12
17
  *
13
18
  * Manages the handler registry, message dispatching, and serialization.
14
- * Each transport (Kafka, RabbitMQ, gRPC, MQTT, NATS) extends this
15
- * class and implements listen/close/unwrap.
19
+ * Each transport (Kafka, RabbitMQ, gRPC, MQTT, NATS) extends this class
20
+ * and implements `listen`/`close`/`unwrap`.
16
21
  *
17
22
  * Subclasses should:
18
- * - Call deserializer.deserialize() on raw broker messages before
19
- * passing them to handleMessage()/handleEvent()
20
- * - Call serializer.serialize() on outbound responses
21
- * - Call setStatus('connected') in listen() when ready
22
- * - Call setStatus('disconnected') in close() when stopped
23
- * - Call dispose() only when the server will never be restarted
23
+ *
24
+ * - Call `deserializer.deserialize()` on raw broker messages before
25
+ * passing them to `handleMessage()` / `handleEvent()`.
26
+ * - Call `serializer.serialize()` on outbound responses.
27
+ * - Call `setStatus('connected')` in `listen()` when ready.
28
+ * - Call `setStatus('disconnected')` in `close()` when stopped.
29
+ * - Call `dispose()` only when the server will never be restarted.
30
+ *
31
+ * @public
24
32
  */
25
33
  class ServerBase {
34
+ messageHandlers = new Map();
35
+ statusSubject = new rxjs_1.ReplaySubject(1);
36
+ serializer;
37
+ deserializer;
38
+ handlerTimeoutMs;
39
+ status$ = this.statusSubject.asObservable();
26
40
  constructor(options) {
27
- this.messageHandlers = new Map();
28
- this.statusSubject = new rxjs_1.ReplaySubject(1);
29
- this.status$ = this.statusSubject.asObservable();
30
41
  this.serializer = options?.serializer ?? new serializers_1.JsonSerializer();
31
42
  this.deserializer = options?.deserializer ?? new serializers_1.JsonDeserializer();
32
- this.handlerTimeoutMs = options?.handlerTimeoutMs ?? DEFAULT_HANDLER_TIMEOUT_MS;
43
+ this.handlerTimeoutMs =
44
+ options?.handlerTimeoutMs ?? DEFAULT_HANDLER_TIMEOUT_MS;
45
+ }
46
+ /**
47
+ * Resolve the serializer/deserializer pair for this server from the
48
+ * app container, honoring transport-scoped \> generic \> subclass-default
49
+ * precedence.
50
+ *
51
+ * Called by the transport lifecycle observer between
52
+ * `bindToServers` and `listen()`. Plugins contribute tag-based
53
+ * serializers via {@link SERIALIZER_TAG} / {@link DESERIALIZER_TAG};
54
+ * subclass defaults set via the `protected serializer` /
55
+ * `protected deserializer` fields are still honored when no tagged
56
+ * binding matches.
57
+ *
58
+ * Resolution precedence (most specific wins):
59
+ *
60
+ * 1. Transport-scoped binding (tagged with `SERIALIZER_TAG` /
61
+ * `DESERIALIZER_TAG` AND `TRANSPORT_NAME_TAG` equal to this
62
+ * server's transport name).
63
+ * 2. Generic binding (tagged with `SERIALIZER_TAG` /
64
+ * `DESERIALIZER_TAG` but no `TRANSPORT_NAME_TAG`).
65
+ * 3. Subclass default — the existing `protected serializer` /
66
+ * `protected deserializer` field value.
67
+ *
68
+ * @public
69
+ * @param app - The LoopBack application container to resolve
70
+ * serializer bindings from.
71
+ */
72
+ async resolveSerializer(app) {
73
+ const transportName = await this.findTransportName(app);
74
+ const serializer = await this.lookupTaggedBinding(app, keys_1.SERIALIZER_TAG, transportName);
75
+ if (serializer !== undefined) {
76
+ this.serializer = serializer;
77
+ }
78
+ const deserializer = await this.lookupTaggedBinding(app, keys_1.DESERIALIZER_TAG, transportName);
79
+ if (deserializer !== undefined) {
80
+ this.deserializer = deserializer;
81
+ }
82
+ }
83
+ /**
84
+ * Find the transport name tag for this server instance by scanning
85
+ * `TRANSPORT_SERVER_TAG` bindings and matching the resolved instance
86
+ * against `this`. Returns `undefined` if the server is not bound (in
87
+ * tests, or before the booter wires servers up), in which case
88
+ * `resolveSerializer` falls back to generic-only matching.
89
+ */
90
+ async findTransportName(app) {
91
+ for (const binding of app.findByTag(keys_1.TRANSPORT_SERVER_TAG)) {
92
+ const instance = await app.get(binding.key);
93
+ if (instance === this) {
94
+ const tag = binding.tagMap?.[keys_1.TRANSPORT_NAME_TAG];
95
+ return typeof tag === 'string' && tag.length > 0 ? tag : undefined;
96
+ }
97
+ }
98
+ return undefined;
33
99
  }
34
100
  /**
35
- * Emit a status change. Called by transport adapters in listen() and close().
101
+ * Resolve a tagged binding, preferring transport-scoped over generic.
102
+ * Returns `undefined` if no tagged binding exists.
103
+ */
104
+ async lookupTaggedBinding(app, tag, transportName) {
105
+ const candidates = app.findByTag(tag);
106
+ if (transportName !== undefined) {
107
+ for (const binding of candidates) {
108
+ if (binding.tagMap?.[keys_1.TRANSPORT_NAME_TAG] === transportName) {
109
+ return app.get(binding.key);
110
+ }
111
+ }
112
+ }
113
+ for (const binding of candidates) {
114
+ if (binding.tagMap?.[keys_1.TRANSPORT_NAME_TAG] === undefined) {
115
+ return app.get(binding.key);
116
+ }
117
+ }
118
+ return undefined;
119
+ }
120
+ /**
121
+ * Emit a status change. Called by transport adapters in `listen()`
122
+ * and `close()`.
36
123
  */
37
124
  setStatus(status) {
38
125
  this.statusSubject.next(status);
39
126
  }
40
127
  /**
41
- * Permanently complete the status stream. After this call, no further
42
- * status emissions are possible and the server instance cannot restart.
43
- * Existing subscribers receive the complete notification.
128
+ * Permanently complete the status stream. After this call, no
129
+ * further status emissions are possible and the server instance
130
+ * cannot restart. Existing subscribers receive the complete
131
+ * notification.
44
132
  *
45
- * Call only when the server is being permanently disposed.
46
- * Normal close() should call setStatus('disconnected'), NOT dispose().
133
+ * Call only when the server is being permanently disposed. Normal
134
+ * `close()` should call `setStatus('disconnected')`, NOT `dispose()`.
47
135
  */
48
136
  dispose() {
49
137
  this.statusSubject.next('disconnected');
@@ -52,33 +140,42 @@ class ServerBase {
52
140
  /**
53
141
  * Register a handler for a message pattern.
54
142
  *
55
- * For request/response handlers (@messageHandler): duplicate patterns
56
- * throw an error. Only one handler per pattern is allowed.
143
+ * For request/response handlers (`@messageHandler`): duplicate
144
+ * patterns throw an error. Only one handler per pattern is allowed.
57
145
  *
58
- * For event handlers (@eventHandler): multiple handlers on the same
59
- * pattern are stored in an array and all execute.
146
+ * For event handlers (`@eventHandler`): multiple handlers on the
147
+ * same pattern are stored in an array and all execute.
60
148
  *
61
- * Mixing @messageHandler and @eventHandler on the same pattern throws.
149
+ * Mixing `@messageHandler` and `@eventHandler` on the same pattern
150
+ * throws.
62
151
  *
63
- * Handlers are never mutated. Each server owns its own handler array,
64
- * so the same handler object can be safely shared across servers.
152
+ * Handlers are never mutated. Each server owns its own handler
153
+ * array, so the same handler object can be safely shared across
154
+ * servers.
155
+ *
156
+ * @public
157
+ * @param pattern - The message pattern (already normalized by the registry).
158
+ * @param handler - The handler to register.
159
+ * @throws TransportConfigError When a request handler is duplicated
160
+ * or a request/event mix is attempted on the same pattern.
65
161
  */
66
162
  addHandler(pattern, handler) {
67
163
  const normalized = this.normalizePattern(pattern);
68
164
  const existing = this.messageHandlers.get(normalized);
69
165
  if (existing) {
70
- // Reject mixed handler types on the same pattern
71
- if (existing[0].isEventHandler !== handler.isEventHandler) {
72
- throw new Error(`Cannot mix @messageHandler and @eventHandler for pattern: ${normalized}. ` +
166
+ const first = existing[0];
167
+ // Reject mixed handler types on the same pattern.
168
+ if (first && first.isEventHandler !== handler.isEventHandler) {
169
+ throw new errors_1.TransportConfigError(`Cannot mix @messageHandler and @eventHandler for pattern: ${normalized}. ` +
73
170
  'A pattern must be exclusively request/response or event, not both.');
74
171
  }
75
- // Reject duplicate request/response handlers
172
+ // Reject duplicate request/response handlers.
76
173
  if (!handler.isEventHandler) {
77
- throw new Error(`Handler already registered for pattern: ${normalized}. ` +
174
+ throw new errors_1.TransportConfigError(`Handler already registered for pattern: ${normalized}. ` +
78
175
  'Only one @messageHandler per pattern is allowed. ' +
79
176
  'Use @eventHandler for multiple handlers on the same pattern.');
80
177
  }
81
- // Append event handler to the array
178
+ // Append event handler to the array.
82
179
  existing.push(handler);
83
180
  return;
84
181
  }
@@ -86,6 +183,8 @@ class ServerBase {
86
183
  }
87
184
  /**
88
185
  * Get all registered handlers. Returns a defensive copy.
186
+ *
187
+ * @public
89
188
  */
90
189
  getHandlers() {
91
190
  const copy = new Map();
@@ -95,8 +194,10 @@ class ServerBase {
95
194
  return copy;
96
195
  }
97
196
  /**
98
- * Remove all registered handlers.
99
- * Called before rebinding on restart to prevent duplicate registration.
197
+ * Remove all registered handlers. Called before rebinding on
198
+ * restart to prevent duplicate registration.
199
+ *
200
+ * @public
100
201
  */
101
202
  clearHandlers() {
102
203
  this.messageHandlers.clear();
@@ -104,13 +205,16 @@ class ServerBase {
104
205
  /**
105
206
  * Get handlers by pattern. Normalizes the pattern before lookup.
106
207
  * Returns a defensive copy for external consumers.
208
+ *
209
+ * @public
107
210
  */
108
211
  getHandlersByPattern(pattern) {
109
212
  const handlers = this.lookupHandlers(pattern);
110
213
  return handlers ? [...handlers] : undefined;
111
214
  }
112
215
  /**
113
- * Internal handler lookup. Returns the live array for dispatch performance.
216
+ * Internal handler lookup. Returns the live array for dispatch
217
+ * performance.
114
218
  */
115
219
  lookupHandlers(pattern) {
116
220
  return this.messageHandlers.get(this.normalizePattern(pattern));
@@ -118,28 +222,29 @@ class ServerBase {
118
222
  /**
119
223
  * Handle a request/response message.
120
224
  *
121
- * Invokes the handler and calls respond() with each result value.
225
+ * Invokes the handler and calls `respond()` with each result value.
122
226
  * Observable subscriptions are tracked and cleaned up on completion,
123
227
  * error, or timeout.
124
228
  *
125
- * Returns a HandlerResult that separates response semantics from
126
- * broker settlement semantics:
229
+ * Returns a {@link HandlerResult} that separates response semantics
230
+ * from broker settlement semantics:
127
231
  *
128
232
  * - `success`: Handler completed normally. Ack the message.
129
233
  * - `handler-error`: Handler threw or Observable errored. The error
130
- * response was already sent to the caller. Ack the message --
131
- * the error was handled as an application-level response.
234
+ * response was already sent to the caller. Ack the message — the
235
+ * error was handled as an application-level response.
132
236
  * - `infrastructure-error`: No handler found or framework failure.
133
237
  * Nack/dead-letter the message.
134
238
  *
135
- * Observable handlers that do not complete within handlerTimeoutMs
136
- * are terminated with a timeout error (handler-error).
239
+ * Observable handlers that do not complete within
240
+ * `handlerTimeoutMs` are terminated with a timeout error
241
+ * (`handler-error`).
137
242
  *
138
- * If respond() throws (adapter publication failure), the error is
139
- * caught and returned as infrastructure-error.
243
+ * If `respond()` throws (adapter publication failure), the error is
244
+ * caught and returned as `infrastructure-error`.
140
245
  */
141
246
  async handleMessage(request, respond, context) {
142
- // Wrap respond to catch adapter publication failures
247
+ // Wrap respond to catch adapter publication failures.
143
248
  const safeRespond = (packet) => {
144
249
  try {
145
250
  respond(packet);
@@ -157,8 +262,9 @@ class ServerBase {
157
262
  }
158
263
  }
159
264
  /**
160
- * Core handler execution. Separated from handleMessage so that
161
- * respond() failures bubble up and are caught by the outer wrapper.
265
+ * Core handler execution. Separated from `handleMessage` so that
266
+ * `respond()` failures bubble up and are caught by the outer
267
+ * wrapper.
162
268
  */
163
269
  async executeHandler(request, respond, context) {
164
270
  const handlers = this.lookupHandlers(request.pattern);
@@ -168,6 +274,11 @@ class ServerBase {
168
274
  return { outcome: 'infrastructure-error', error: new Error(err) };
169
275
  }
170
276
  const handler = handlers[0];
277
+ if (!handler) {
278
+ const err = `No handler for pattern: ${request.pattern}`;
279
+ respond({ err: this.serializeError(err), isDisposed: true });
280
+ return { outcome: 'infrastructure-error', error: new Error(err) };
281
+ }
171
282
  let result;
172
283
  try {
173
284
  result = await handler(request.data, context);
@@ -178,14 +289,14 @@ class ServerBase {
178
289
  }
179
290
  if ((0, rxjs_1.isObservable)(result)) {
180
291
  const obs = result.pipe((0, rxjs_1.timeout)(this.handlerTimeoutMs));
181
- return new Promise((resolve) => {
292
+ return new Promise(resolve => {
182
293
  let settled = false;
183
- const settle = (result) => {
294
+ const settle = (settledResult) => {
184
295
  if (settled)
185
296
  return;
186
297
  settled = true;
187
298
  subscription?.unsubscribe();
188
- resolve(result);
299
+ resolve(settledResult);
189
300
  };
190
301
  let subscription;
191
302
  subscription = obs.subscribe({
@@ -235,11 +346,11 @@ class ServerBase {
235
346
  /**
236
347
  * Handle a fire-and-forget event.
237
348
  *
238
- * Invokes all chained handlers for the pattern.
239
- * Errors are logged but not propagated (fire-and-forget semantics).
349
+ * Invokes all chained handlers for the pattern. Errors are logged
350
+ * but not propagated (fire-and-forget semantics).
240
351
  *
241
- * Observable event handlers that do not complete within handlerTimeoutMs
242
- * are terminated and logged.
352
+ * Observable event handlers that do not complete within
353
+ * `handlerTimeoutMs` are terminated and logged.
243
354
  */
244
355
  async handleEvent(event, context) {
245
356
  const handlers = this.lookupHandlers(event.pattern);
@@ -264,8 +375,8 @@ class ServerBase {
264
375
  }
265
376
  }
266
377
  /**
267
- * Normalize a pattern to a consistent string key.
268
- * Delegates to the shared normalizePattern utility.
378
+ * Normalize a pattern to a consistent string key. Delegates to the
379
+ * shared `normalizePattern` utility.
269
380
  */
270
381
  normalizePattern(pattern) {
271
382
  return (0, utils_1.normalizePattern)(pattern);