@ebarahona/loopback-transport-core 1.0.0 → 1.2.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 +472 -35
- package/dist/client/client-proxy.d.ts +73 -42
- package/dist/client/client-proxy.js +71 -42
- package/dist/client/client-proxy.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +3 -15
- package/dist/client/index.js.map +1 -1
- package/dist/context/execution-context.d.ts +58 -11
- package/dist/context/execution-context.js +62 -17
- package/dist/context/execution-context.js.map +1 -1
- package/dist/context/index.d.ts +2 -1
- package/dist/context/index.js +3 -15
- package/dist/context/index.js.map +1 -1
- package/dist/decorators/constants.d.ts +20 -10
- package/dist/decorators/constants.js +6 -2
- package/dist/decorators/constants.js.map +1 -1
- package/dist/decorators/event-handler.decorator.d.ts +10 -7
- package/dist/decorators/event-handler.decorator.js +18 -13
- package/dist/decorators/event-handler.decorator.js.map +1 -1
- package/dist/decorators/index.d.ts +5 -4
- package/dist/decorators/index.js +11 -18
- package/dist/decorators/index.js.map +1 -1
- package/dist/decorators/message-handler.decorator.d.ts +8 -5
- package/dist/decorators/message-handler.decorator.js +16 -11
- package/dist/decorators/message-handler.decorator.js.map +1 -1
- package/dist/decorators/payload.decorator.d.ts +16 -7
- package/dist/decorators/payload.decorator.js +16 -8
- package/dist/decorators/payload.decorator.js.map +1 -1
- package/dist/discovery/discovery.service.d.ts +143 -0
- package/dist/discovery/discovery.service.js +165 -0
- package/dist/discovery/discovery.service.js.map +1 -0
- package/dist/discovery/event-handler-discoverer.d.ts +19 -0
- package/dist/discovery/event-handler-discoverer.js +50 -0
- package/dist/discovery/event-handler-discoverer.js.map +1 -0
- package/dist/discovery/handler-discoverer.d.ts +48 -0
- package/dist/discovery/handler-discoverer.js +3 -0
- package/dist/discovery/handler-discoverer.js.map +1 -0
- package/dist/discovery/handler-kind.d.ts +23 -0
- package/dist/discovery/handler-kind.js +14 -0
- package/dist/discovery/handler-kind.js.map +1 -0
- package/dist/discovery/index.d.ts +7 -0
- package/dist/discovery/index.js +13 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/message-handler-discoverer.d.ts +19 -0
- package/dist/discovery/message-handler-discoverer.js +50 -0
- package/dist/discovery/message-handler-discoverer.js.map +1 -0
- package/dist/discovery/registered-handler.d.ts +35 -0
- package/dist/discovery/registered-handler.js +3 -0
- package/dist/discovery/registered-handler.js.map +1 -0
- package/dist/helpers/errors.d.ts +61 -0
- package/dist/helpers/errors.js +80 -0
- package/dist/helpers/errors.js.map +1 -0
- package/dist/helpers/index.d.ts +2 -0
- package/dist/helpers/index.js +14 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/register-server.d.ts +44 -0
- package/dist/helpers/register-server.js +72 -0
- package/dist/helpers/register-server.js.map +1 -0
- package/dist/index.d.ts +15 -7
- package/dist/index.js +33 -9
- package/dist/index.js.map +1 -1
- package/dist/interfaces/index.d.ts +4 -4
- package/dist/interfaces/index.js +0 -18
- package/dist/interfaces/index.js.map +1 -1
- package/dist/interfaces/message-handler.interface.d.ts +12 -6
- package/dist/interfaces/packet.interface.d.ts +20 -2
- package/dist/interfaces/transport-client.interface.d.ts +25 -13
- package/dist/interfaces/transport-server.interface.d.ts +36 -10
- package/dist/keys.d.ts +143 -46
- package/dist/keys.js +142 -68
- package/dist/keys.js.map +1 -1
- package/dist/registry/handler-registry.d.ts +94 -24
- package/dist/registry/handler-registry.js +279 -89
- package/dist/registry/handler-registry.js.map +1 -1
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.js +3 -15
- package/dist/registry/index.js.map +1 -1
- package/dist/serializers/cloudevents-serializer.d.ts +107 -0
- package/dist/serializers/cloudevents-serializer.js +130 -0
- package/dist/serializers/cloudevents-serializer.js.map +1 -0
- package/dist/serializers/index.d.ts +4 -1
- package/dist/serializers/index.js +7 -15
- package/dist/serializers/index.js.map +1 -1
- package/dist/serializers/serializer.interface.d.ts +49 -16
- package/dist/serializers/serializer.interface.js +41 -15
- package/dist/serializers/serializer.interface.js.map +1 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +3 -15
- package/dist/server/index.js.map +1 -1
- package/dist/server/server-base.d.ts +125 -52
- package/dist/server/server-base.js +168 -57
- package/dist/server/server-base.js.map +1 -1
- package/dist/transport.component.d.ts +21 -8
- package/dist/transport.component.js +41 -18
- package/dist/transport.component.js.map +1 -1
- package/dist/transport.observer.d.ts +91 -0
- package/dist/transport.observer.js +297 -0
- package/dist/transport.observer.js.map +1 -0
- package/dist/utils/normalize-pattern.d.ts +11 -6
- package/dist/utils/normalize-pattern.js +18 -13
- package/dist/utils/normalize-pattern.js.map +1 -1
- package/package.json +58 -16
- package/dist/transport-booter.d.ts +0 -31
- package/dist/transport-booter.js +0 -117
- package/dist/transport-booter.js.map +0 -1
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
10
|
-
* was sent to the caller. Adapter should ack (the error was
|
|
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
|
-
*
|
|
26
|
+
* Each transport (Kafka, RabbitMQ, gRPC, MQTT, NATS) extends this class
|
|
27
|
+
* and implements `listen`/`close`/`unwrap`.
|
|
24
28
|
*
|
|
25
29
|
* Subclasses should:
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* - Call
|
|
30
|
-
* - Call setStatus('
|
|
31
|
-
* - Call
|
|
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
|
-
*
|
|
47
|
-
*
|
|
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
|
-
*
|
|
53
|
-
*
|
|
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()
|
|
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
|
|
66
|
-
* status emissions are possible and the server instance
|
|
67
|
-
* Existing subscribers receive the complete
|
|
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
|
-
*
|
|
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 (
|
|
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
|
-
*
|
|
80
|
-
*
|
|
138
|
+
* Mixing `@messageHandler` and `@eventHandler` on the same pattern
|
|
139
|
+
* throws.
|
|
81
140
|
*
|
|
82
|
-
*
|
|
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
|
-
*
|
|
85
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
124
|
-
* are terminated with a timeout 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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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 =
|
|
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
|
-
*
|
|
19
|
+
* Each transport (Kafka, RabbitMQ, gRPC, MQTT, NATS) extends this class
|
|
20
|
+
* and implements `listen`/`close`/`unwrap`.
|
|
16
21
|
*
|
|
17
22
|
* Subclasses should:
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* - Call
|
|
22
|
-
* - Call setStatus('
|
|
23
|
-
* - Call
|
|
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 =
|
|
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
|
-
*
|
|
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
|
|
42
|
-
* status emissions are possible and the server instance
|
|
43
|
-
* Existing subscribers receive the complete
|
|
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
|
-
*
|
|
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 (
|
|
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 (
|
|
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
|
|
149
|
+
* Mixing `@messageHandler` and `@eventHandler` on the same pattern
|
|
150
|
+
* throws.
|
|
62
151
|
*
|
|
63
|
-
* Handlers are never mutated. Each server owns its own handler
|
|
64
|
-
* so the same handler object can be safely shared across
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
136
|
-
* are terminated with a timeout 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
|
|
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(
|
|
292
|
+
return new Promise(resolve => {
|
|
182
293
|
let settled = false;
|
|
183
|
-
const settle = (
|
|
294
|
+
const settle = (settledResult) => {
|
|
184
295
|
if (settled)
|
|
185
296
|
return;
|
|
186
297
|
settled = true;
|
|
187
298
|
subscription?.unsubscribe();
|
|
188
|
-
resolve(
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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);
|