@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,48 +1,119 @@
|
|
|
1
|
-
import { Application } from '@loopback/core';
|
|
2
|
-
import {
|
|
1
|
+
import { Application, type Constructor } from '@loopback/core';
|
|
2
|
+
import type { HandlerKind } from '../discovery';
|
|
3
|
+
import type { HandlerOptions } from '../decorators';
|
|
4
|
+
import type { MessageHandler } from '../interfaces';
|
|
3
5
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
+
* Internal registry entry tracking both the runtime handler function
|
|
7
|
+
* and the metadata required to construct a {@link RegisteredHandler}
|
|
8
|
+
* view for cross-cutting wrappers and the {@link DiscoveryService}.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export interface RegistryEntry {
|
|
13
|
+
/** Original (unnormalized) pattern as supplied by the discoverer. */
|
|
14
|
+
readonly pattern: string | Record<string, unknown>;
|
|
15
|
+
/** Transport server tag (`'*'` for wildcard). */
|
|
16
|
+
readonly transport: string;
|
|
17
|
+
/** Handler kind. */
|
|
18
|
+
readonly kind: HandlerKind;
|
|
19
|
+
/** Controller class that declared this handler. */
|
|
20
|
+
readonly controllerClass: Constructor<unknown>;
|
|
21
|
+
/** Method name on the controller prototype. */
|
|
22
|
+
readonly methodName: string;
|
|
23
|
+
/** Id of the discoverer that produced this entry. */
|
|
24
|
+
readonly discovererId: string;
|
|
25
|
+
/** Discoverer-specific handler options. */
|
|
26
|
+
readonly options?: HandlerOptions;
|
|
27
|
+
/**
|
|
28
|
+
* Live handler function. Mutable so the lifecycle observer can swap
|
|
29
|
+
* in the wrapped version between discovery and server-binding.
|
|
30
|
+
*/
|
|
31
|
+
handler: MessageHandler;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Discovers transport handlers from controllers via every bound
|
|
35
|
+
* {@link HandlerDiscoverer} and registers them with the appropriate
|
|
36
|
+
* transport server.
|
|
37
|
+
*
|
|
38
|
+
* The two built-in vocabularies (`@messageHandler` and `@eventHandler`)
|
|
39
|
+
* are implemented as default discoverers bound by `TransportComponent`.
|
|
40
|
+
* Plugins (gRPC, cron, WebSocket, etc.) contribute additional decorator
|
|
41
|
+
* vocabularies by binding their own `HandlerDiscoverer` implementations
|
|
42
|
+
* under `TransportBindings.tags.HANDLER_DISCOVERER`.
|
|
6
43
|
*
|
|
7
44
|
* This class does NOT run in the constructor. It is invoked by
|
|
8
|
-
* TransportBooter after all controllers are registered.
|
|
45
|
+
* `TransportBooter` after all controllers are registered.
|
|
9
46
|
*
|
|
10
47
|
* Handler invocations use per-message child contexts for concurrency
|
|
11
48
|
* safety. The application context is never mutated per-message.
|
|
12
49
|
*
|
|
13
|
-
* Context lifetime extends until the handler result (including
|
|
14
|
-
* streams) fully completes, errors, or times out.
|
|
50
|
+
* Context lifetime extends until the handler result (including
|
|
51
|
+
* Observable streams) fully completes, errors, or times out.
|
|
52
|
+
*
|
|
53
|
+
* @public
|
|
15
54
|
*/
|
|
16
55
|
export declare class HandlerRegistry {
|
|
17
56
|
/**
|
|
18
57
|
* Registry keyed by `transport::pattern` to support the same pattern
|
|
19
|
-
* on different transports without collision.
|
|
58
|
+
* on different transports without collision. Each value is the chain
|
|
59
|
+
* of entries for that pattern (multiple allowed for event fan-out).
|
|
20
60
|
*/
|
|
21
|
-
private readonly
|
|
61
|
+
private readonly entries;
|
|
22
62
|
private discovered;
|
|
23
63
|
/**
|
|
24
64
|
* Scan all controllers in the application for transport decorators
|
|
25
65
|
* and register the handlers.
|
|
26
66
|
*
|
|
67
|
+
* Discovers handlers by consulting every `HandlerDiscoverer` bound
|
|
68
|
+
* under `TransportBindings.tags.HANDLER_DISCOVERER`, including the
|
|
69
|
+
* built-in `MessageHandlerDiscoverer` and `EventHandlerDiscoverer`
|
|
70
|
+
* registered by `TransportComponent`, plus any plugin-contributed
|
|
71
|
+
* discoverers.
|
|
72
|
+
*
|
|
27
73
|
* Idempotent: clears previously discovered handlers before scanning.
|
|
74
|
+
*
|
|
75
|
+
* @public
|
|
76
|
+
* @param app - The LoopBack application instance.
|
|
77
|
+
* @returns Resolves once every controller has been scanned.
|
|
78
|
+
* @throws TransportConfigError When the same pattern is targeted by
|
|
79
|
+
* both `@messageHandler` and `@eventHandler`, or by two
|
|
80
|
+
* `@messageHandler` declarations.
|
|
28
81
|
*/
|
|
29
82
|
discoverHandlers(app: Application): Promise<void>;
|
|
30
83
|
/**
|
|
31
84
|
* Bind discovered handlers to the registered transport servers.
|
|
32
|
-
* Throws if discovery has not been run.
|
|
33
85
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
86
|
+
* Precedence rules:
|
|
87
|
+
*
|
|
88
|
+
* - Request handlers: transport-specific overrides wildcard (only one
|
|
89
|
+
* per pattern survives).
|
|
90
|
+
* - Event handlers: both specific and wildcard run (fan-out).
|
|
91
|
+
*
|
|
92
|
+
* @public
|
|
93
|
+
* @param app - The LoopBack application instance.
|
|
94
|
+
* @returns Resolves once every handler has been bound.
|
|
95
|
+
* @throws TransportConfigError When `discoverHandlers()` has not been
|
|
96
|
+
* called first, or when a transport server binding is missing the
|
|
97
|
+
* `TRANSPORT_NAME_TAG` tag.
|
|
36
98
|
*/
|
|
37
99
|
bindToServers(app: Application): Promise<void>;
|
|
38
100
|
/**
|
|
39
|
-
* Get all discovered handlers.
|
|
101
|
+
* Get all discovered handlers. The returned map is a defensive copy.
|
|
102
|
+
*
|
|
103
|
+
* @public
|
|
104
|
+
* @returns A read-only snapshot of every registered pattern and its
|
|
105
|
+
* handler chain.
|
|
40
106
|
*/
|
|
41
107
|
getHandlers(): ReadonlyMap<string, readonly MessageHandler[]>;
|
|
42
108
|
/**
|
|
43
|
-
* Whether
|
|
109
|
+
* Whether {@link discoverHandlers} has been run.
|
|
110
|
+
*
|
|
111
|
+
* @public
|
|
44
112
|
*/
|
|
45
113
|
isDiscovered(): boolean;
|
|
114
|
+
/** @internal Package-scoped accessor used by TransportObserver to build the DiscoveryService snapshot. */
|
|
115
|
+
_getEntries(): IterableIterator<RegistryEntry>;
|
|
116
|
+
private iterateEntries;
|
|
46
117
|
/**
|
|
47
118
|
* Build the registry key from transport and raw pattern.
|
|
48
119
|
* Normalizes the pattern (sorts object keys) before keying.
|
|
@@ -53,21 +124,20 @@ export declare class HandlerRegistry {
|
|
|
53
124
|
* Extract the pattern portion from a registry key.
|
|
54
125
|
*/
|
|
55
126
|
private extractPattern;
|
|
56
|
-
private scanMessageHandlers;
|
|
57
|
-
private scanEventHandlers;
|
|
58
127
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
128
|
+
* Convert a `DiscoveredHandler` into a runtime `MessageHandler`,
|
|
129
|
+
* applying validation rules (no mixed kinds per pattern, no duplicate
|
|
130
|
+
* request handlers) before inserting into the registry.
|
|
61
131
|
*/
|
|
62
|
-
private
|
|
132
|
+
private registerDiscovered;
|
|
63
133
|
/**
|
|
64
|
-
* Create a handler function that invokes the controller method
|
|
65
|
-
* LoopBack's native invocation pipeline.
|
|
134
|
+
* Create a handler function that invokes the controller method
|
|
135
|
+
* through LoopBack's native invocation pipeline.
|
|
66
136
|
*
|
|
67
137
|
* Each invocation creates a uniquely named child Context from the
|
|
68
|
-
* application context. Per-message bindings (payload, transport
|
|
69
|
-
* are scoped to the child context and cannot collide with
|
|
70
|
-
* messages.
|
|
138
|
+
* application context. Per-message bindings (payload, transport
|
|
139
|
+
* context) are scoped to the child context and cannot collide with
|
|
140
|
+
* concurrent messages.
|
|
71
141
|
*
|
|
72
142
|
* The child context lifetime extends until the handler result fully
|
|
73
143
|
* resolves. For Observable results, the context stays open until the
|
|
@@ -5,13 +5,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
6
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
7
|
};
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
8
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
12
|
exports.HandlerRegistry = void 0;
|
|
10
13
|
const core_1 = require("@loopback/core");
|
|
11
14
|
const crypto_1 = require("crypto");
|
|
12
|
-
const debug_1 = require("debug");
|
|
15
|
+
const debug_1 = __importDefault(require("debug"));
|
|
13
16
|
const rxjs_1 = require("rxjs");
|
|
14
|
-
const
|
|
17
|
+
const discovery_1 = require("../discovery");
|
|
18
|
+
const errors_1 = require("../helpers/errors");
|
|
15
19
|
const keys_1 = require("../keys");
|
|
16
20
|
const utils_1 = require("../utils");
|
|
17
21
|
const debug = (0, debug_1.default)('loopback:transport:registry');
|
|
@@ -21,77 +25,220 @@ const debug = (0, debug_1.default)('loopback:transport:registry');
|
|
|
21
25
|
*/
|
|
22
26
|
const KEY_SEPARATOR = '::';
|
|
23
27
|
/**
|
|
24
|
-
* Discovers
|
|
25
|
-
*
|
|
28
|
+
* Discovers transport handlers from controllers via every bound
|
|
29
|
+
* {@link HandlerDiscoverer} and registers them with the appropriate
|
|
30
|
+
* transport server.
|
|
31
|
+
*
|
|
32
|
+
* The two built-in vocabularies (`@messageHandler` and `@eventHandler`)
|
|
33
|
+
* are implemented as default discoverers bound by `TransportComponent`.
|
|
34
|
+
* Plugins (gRPC, cron, WebSocket, etc.) contribute additional decorator
|
|
35
|
+
* vocabularies by binding their own `HandlerDiscoverer` implementations
|
|
36
|
+
* under `TransportBindings.tags.HANDLER_DISCOVERER`.
|
|
26
37
|
*
|
|
27
38
|
* This class does NOT run in the constructor. It is invoked by
|
|
28
|
-
* TransportBooter after all controllers are registered.
|
|
39
|
+
* `TransportBooter` after all controllers are registered.
|
|
29
40
|
*
|
|
30
41
|
* Handler invocations use per-message child contexts for concurrency
|
|
31
42
|
* safety. The application context is never mutated per-message.
|
|
32
43
|
*
|
|
33
|
-
* Context lifetime extends until the handler result (including
|
|
34
|
-
* streams) fully completes, errors, or times out.
|
|
44
|
+
* Context lifetime extends until the handler result (including
|
|
45
|
+
* Observable streams) fully completes, errors, or times out.
|
|
46
|
+
*
|
|
47
|
+
* @public
|
|
35
48
|
*/
|
|
36
49
|
let HandlerRegistry = class HandlerRegistry {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
50
|
+
/**
|
|
51
|
+
* Registry keyed by `transport::pattern` to support the same pattern
|
|
52
|
+
* on different transports without collision. Each value is the chain
|
|
53
|
+
* of entries for that pattern (multiple allowed for event fan-out).
|
|
54
|
+
*/
|
|
55
|
+
entries = new Map();
|
|
56
|
+
discovered = false;
|
|
45
57
|
/**
|
|
46
58
|
* Scan all controllers in the application for transport decorators
|
|
47
59
|
* and register the handlers.
|
|
48
60
|
*
|
|
61
|
+
* Discovers handlers by consulting every `HandlerDiscoverer` bound
|
|
62
|
+
* under `TransportBindings.tags.HANDLER_DISCOVERER`, including the
|
|
63
|
+
* built-in `MessageHandlerDiscoverer` and `EventHandlerDiscoverer`
|
|
64
|
+
* registered by `TransportComponent`, plus any plugin-contributed
|
|
65
|
+
* discoverers.
|
|
66
|
+
*
|
|
49
67
|
* Idempotent: clears previously discovered handlers before scanning.
|
|
68
|
+
*
|
|
69
|
+
* @public
|
|
70
|
+
* @param app - The LoopBack application instance.
|
|
71
|
+
* @returns Resolves once every controller has been scanned.
|
|
72
|
+
* @throws TransportConfigError When the same pattern is targeted by
|
|
73
|
+
* both `@messageHandler` and `@eventHandler`, or by two
|
|
74
|
+
* `@messageHandler` declarations.
|
|
50
75
|
*/
|
|
51
76
|
async discoverHandlers(app) {
|
|
52
|
-
this.
|
|
77
|
+
this.entries.clear();
|
|
53
78
|
this.discovered = false;
|
|
79
|
+
const discovererBindings = app.findByTag(keys_1.HANDLER_DISCOVERER_TAG);
|
|
80
|
+
const discoverers = [];
|
|
81
|
+
for (const binding of discovererBindings) {
|
|
82
|
+
discoverers.push(await app.get(binding.key));
|
|
83
|
+
}
|
|
54
84
|
const controllerBindings = app.findByTag('controller');
|
|
55
|
-
debug('scanning %d controllers
|
|
85
|
+
debug('scanning %d controllers with %d discoverers', controllerBindings.length, discoverers.length);
|
|
86
|
+
const perDiscovererCounts = new Map();
|
|
56
87
|
for (const binding of controllerBindings) {
|
|
57
88
|
const controllerClass = binding.valueConstructor;
|
|
58
89
|
if (!controllerClass)
|
|
59
90
|
continue;
|
|
60
91
|
const bindingKey = binding.key;
|
|
61
|
-
|
|
62
|
-
|
|
92
|
+
for (const discoverer of discoverers) {
|
|
93
|
+
const found = await discoverer.discover(controllerClass);
|
|
94
|
+
if (found.length === 0)
|
|
95
|
+
continue;
|
|
96
|
+
perDiscovererCounts.set(discoverer.id, (perDiscovererCounts.get(discoverer.id) ?? 0) + found.length);
|
|
97
|
+
for (const item of found) {
|
|
98
|
+
this.registerDiscovered(item, controllerClass, bindingKey, app, discoverer.id);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
63
101
|
}
|
|
64
102
|
this.discovered = true;
|
|
65
|
-
debug
|
|
103
|
+
if (debug.enabled) {
|
|
104
|
+
for (const [id, count] of perDiscovererCounts) {
|
|
105
|
+
debug('discoverer [%s] contributed %d handlers', id, count);
|
|
106
|
+
}
|
|
107
|
+
debug('discovered %d transport handlers total', this.entries.size);
|
|
108
|
+
}
|
|
66
109
|
}
|
|
67
110
|
/**
|
|
68
111
|
* Bind discovered handlers to the registered transport servers.
|
|
69
|
-
* Throws if discovery has not been run.
|
|
70
112
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
113
|
+
* Precedence rules:
|
|
114
|
+
*
|
|
115
|
+
* - Request handlers: transport-specific overrides wildcard (only one
|
|
116
|
+
* per pattern survives).
|
|
117
|
+
* - Event handlers: both specific and wildcard run (fan-out).
|
|
118
|
+
*
|
|
119
|
+
* @public
|
|
120
|
+
* @param app - The LoopBack application instance.
|
|
121
|
+
* @returns Resolves once every handler has been bound.
|
|
122
|
+
* @throws TransportConfigError When `discoverHandlers()` has not been
|
|
123
|
+
* called first, or when a transport server binding is missing the
|
|
124
|
+
* `TRANSPORT_NAME_TAG` tag.
|
|
73
125
|
*/
|
|
74
126
|
async bindToServers(app) {
|
|
75
127
|
if (!this.discovered) {
|
|
76
|
-
throw new
|
|
128
|
+
throw new errors_1.TransportConfigError('Cannot bind handlers to servers: discoverHandlers() has not been called.');
|
|
77
129
|
}
|
|
78
130
|
const serverBindings = app.findByTag(keys_1.TRANSPORT_SERVER_TAG);
|
|
131
|
+
// Boot-time validation: surface misconfigurations loudly before any
|
|
132
|
+
// handler is bound to a server. Four guards run here:
|
|
133
|
+
// 1. Handlers referencing unknown transports.
|
|
134
|
+
// 2. Handlers exist but no transport servers registered.
|
|
135
|
+
// 3. Duplicate NAME tags across TransportServer bindings.
|
|
136
|
+
// 4. TransportServer binding without a NAME tag.
|
|
137
|
+
let strictBinding = true;
|
|
138
|
+
if (app.isBound(keys_1.TransportBindings.STRICT_BINDING)) {
|
|
139
|
+
strictBinding = await app.get(keys_1.TransportBindings.STRICT_BINDING);
|
|
140
|
+
}
|
|
141
|
+
// Build the set of known transport names and detect duplicate NAME
|
|
142
|
+
// tags (Guard 3) and missing NAME tags (Guard 4) in a single pass.
|
|
143
|
+
const knownTransports = new Set();
|
|
144
|
+
const nameToBindingKeys = new Map();
|
|
79
145
|
for (const serverBinding of serverBindings) {
|
|
146
|
+
const tag = serverBinding.tagMap?.[keys_1.TRANSPORT_NAME_TAG];
|
|
147
|
+
if (typeof tag !== 'string' || tag.length === 0) {
|
|
148
|
+
debug('%s is registered as a TransportServer but has no NAME tag; the server is skipped during binding and will not receive handlers (the lifecycle observer also skips it during startup)', String(serverBinding.key));
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
knownTransports.add(tag);
|
|
152
|
+
const list = nameToBindingKeys.get(tag) ?? [];
|
|
153
|
+
list.push(String(serverBinding.key));
|
|
154
|
+
nameToBindingKeys.set(tag, list);
|
|
155
|
+
}
|
|
156
|
+
// Guard 3: duplicate NAME tags. Always throws regardless of strict mode.
|
|
157
|
+
for (const [name, keys] of nameToBindingKeys) {
|
|
158
|
+
if (keys.length > 1) {
|
|
159
|
+
throw new errors_1.TransportConfigError(`duplicate transport name "${name}": multiple TransportServer ` +
|
|
160
|
+
`bindings tagged with the same NAME (${keys.join(', ')}). ` +
|
|
161
|
+
'Each transport must have a unique name.');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Guard 2: no transport servers but handlers exist. Always warns.
|
|
165
|
+
if (serverBindings.length === 0 && this.entries.size > 0) {
|
|
166
|
+
let handlerCount = 0;
|
|
167
|
+
for (const chain of this.entries.values())
|
|
168
|
+
handlerCount += chain.length;
|
|
169
|
+
debug('no transport servers registered but %d handlers are bound; handlers will not receive events', handlerCount);
|
|
170
|
+
}
|
|
171
|
+
// Guard 1: handlers referencing unknown transports.
|
|
172
|
+
// Group orphaned entries by transport name for the error/log message.
|
|
173
|
+
const orphansByTransport = new Map();
|
|
174
|
+
for (const chain of this.entries.values()) {
|
|
175
|
+
for (const entry of chain) {
|
|
176
|
+
if (entry.transport === undefined || entry.transport === '*')
|
|
177
|
+
continue;
|
|
178
|
+
if (knownTransports.has(entry.transport))
|
|
179
|
+
continue;
|
|
180
|
+
const list = orphansByTransport.get(entry.transport) ?? [];
|
|
181
|
+
list.push({
|
|
182
|
+
controllerName: entry.controllerClass.name,
|
|
183
|
+
methodName: entry.methodName,
|
|
184
|
+
pattern: (0, utils_1.normalizePattern)(entry.pattern),
|
|
185
|
+
discovererId: entry.discovererId,
|
|
186
|
+
});
|
|
187
|
+
orphansByTransport.set(entry.transport, list);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (orphansByTransport.size > 0) {
|
|
191
|
+
const knownList = knownTransports.size === 0
|
|
192
|
+
? '(none)'
|
|
193
|
+
: Array.from(knownTransports).join(', ');
|
|
194
|
+
const summaryParts = [];
|
|
195
|
+
for (const [name, list] of orphansByTransport) {
|
|
196
|
+
summaryParts.push(`${name} (${list.length} handlers)`);
|
|
197
|
+
}
|
|
198
|
+
const detailLines = [];
|
|
199
|
+
for (const [, list] of orphansByTransport) {
|
|
200
|
+
for (const o of list) {
|
|
201
|
+
detailLines.push(` - ${o.controllerName}.${o.methodName} on pattern "${o.pattern}" (discoverer "${o.discovererId}")`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const message = `handlers reference unknown transport(s): ${summaryParts.join(', ')}.\n` +
|
|
205
|
+
` Known transports: ${knownList}\n` +
|
|
206
|
+
` Orphaned handlers:\n${detailLines.join('\n')}`;
|
|
207
|
+
if (strictBinding) {
|
|
208
|
+
throw new errors_1.TransportConfigError(message);
|
|
209
|
+
}
|
|
210
|
+
for (const [name, list] of orphansByTransport) {
|
|
211
|
+
const perTransportDetails = list
|
|
212
|
+
.map(o => ` - ${o.controllerName}.${o.methodName} on pattern "${o.pattern}" (discoverer "${o.discovererId}")`)
|
|
213
|
+
.join('\n');
|
|
214
|
+
debug('handlers reference unknown transport "%s" (%d handlers).\n Known transports: %s\n Orphaned handlers:\n%s', name, list.length, knownList, perTransportDetails);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
for (const serverBinding of serverBindings) {
|
|
218
|
+
const tag = serverBinding.tagMap?.[keys_1.TRANSPORT_NAME_TAG];
|
|
219
|
+
if (typeof tag !== 'string' || tag.length === 0) {
|
|
220
|
+
// Guard 4 already emitted a debug warning above; only wildcard
|
|
221
|
+
// handlers can ever reach this server, so skip the routing loop.
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const transportName = tag;
|
|
80
225
|
const server = await app.get(serverBinding.key);
|
|
81
|
-
const transportName = serverBinding.tagMap?.[keys_1.TRANSPORT_NAME_TAG];
|
|
82
226
|
// Collect handlers for this server.
|
|
83
227
|
// Precedence rules differ by handler type:
|
|
84
228
|
// - Request handlers: specific overrides wildcard (only one per pattern)
|
|
85
229
|
// - Event handlers: both specific and wildcard run (fan-out)
|
|
86
230
|
const boundRequestPatterns = new Set();
|
|
87
231
|
// First pass: transport-specific handlers
|
|
88
|
-
for (const [registryKey,
|
|
89
|
-
|
|
232
|
+
for (const [registryKey, entryChain] of this.entries) {
|
|
233
|
+
const first = entryChain[0];
|
|
234
|
+
if (!first)
|
|
235
|
+
continue;
|
|
236
|
+
if (first.transport !== '*' && first.transport === transportName) {
|
|
90
237
|
const pattern = this.extractPattern(registryKey);
|
|
91
|
-
for (const
|
|
92
|
-
server.addHandler(pattern, handler);
|
|
238
|
+
for (const entry of entryChain) {
|
|
239
|
+
server.addHandler(pattern, entry.handler);
|
|
93
240
|
}
|
|
94
|
-
if (
|
|
241
|
+
if (first.kind !== discovery_1.HANDLER_KIND_EVENT) {
|
|
95
242
|
boundRequestPatterns.add(pattern);
|
|
96
243
|
}
|
|
97
244
|
debug('bound handler [%s] to transport [%s] (specific)', pattern, transportName);
|
|
@@ -100,35 +247,56 @@ let HandlerRegistry = class HandlerRegistry {
|
|
|
100
247
|
// Second pass: wildcard handlers
|
|
101
248
|
// - Request handlers: skip if transport-specific already bound
|
|
102
249
|
// - Event handlers: always bind (fan-out semantics)
|
|
103
|
-
for (const [registryKey,
|
|
104
|
-
|
|
250
|
+
for (const [registryKey, entryChain] of this.entries) {
|
|
251
|
+
const first = entryChain[0];
|
|
252
|
+
if (!first)
|
|
253
|
+
continue;
|
|
254
|
+
if (first.transport === '*') {
|
|
105
255
|
const pattern = this.extractPattern(registryKey);
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
256
|
+
if (first.kind === discovery_1.HANDLER_KIND_EVENT ||
|
|
257
|
+
!boundRequestPatterns.has(pattern)) {
|
|
258
|
+
for (const entry of entryChain) {
|
|
259
|
+
server.addHandler(pattern, entry.handler);
|
|
109
260
|
}
|
|
110
|
-
debug('bound handler [%s] to transport [%s] (wildcard)', pattern, transportName
|
|
261
|
+
debug('bound handler [%s] to transport [%s] (wildcard)', pattern, transportName);
|
|
111
262
|
}
|
|
112
263
|
}
|
|
113
264
|
}
|
|
114
265
|
}
|
|
115
266
|
}
|
|
116
267
|
/**
|
|
117
|
-
* Get all discovered handlers.
|
|
268
|
+
* Get all discovered handlers. The returned map is a defensive copy.
|
|
269
|
+
*
|
|
270
|
+
* @public
|
|
271
|
+
* @returns A read-only snapshot of every registered pattern and its
|
|
272
|
+
* handler chain.
|
|
118
273
|
*/
|
|
119
274
|
getHandlers() {
|
|
120
275
|
const copy = new Map();
|
|
121
|
-
for (const [key,
|
|
122
|
-
copy.set(key,
|
|
276
|
+
for (const [key, entries] of this.entries) {
|
|
277
|
+
copy.set(key, entries.map(e => e.handler));
|
|
123
278
|
}
|
|
124
279
|
return copy;
|
|
125
280
|
}
|
|
126
281
|
/**
|
|
127
|
-
* Whether
|
|
282
|
+
* Whether {@link discoverHandlers} has been run.
|
|
283
|
+
*
|
|
284
|
+
* @public
|
|
128
285
|
*/
|
|
129
286
|
isDiscovered() {
|
|
130
287
|
return this.discovered;
|
|
131
288
|
}
|
|
289
|
+
/** @internal Package-scoped accessor used by TransportObserver to build the DiscoveryService snapshot. */
|
|
290
|
+
_getEntries() {
|
|
291
|
+
return this.iterateEntries();
|
|
292
|
+
}
|
|
293
|
+
*iterateEntries() {
|
|
294
|
+
for (const chain of this.entries.values()) {
|
|
295
|
+
for (const entry of chain) {
|
|
296
|
+
yield entry;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
132
300
|
/**
|
|
133
301
|
* Build the registry key from transport and raw pattern.
|
|
134
302
|
* Normalizes the pattern (sorts object keys) before keying.
|
|
@@ -146,56 +314,77 @@ let HandlerRegistry = class HandlerRegistry {
|
|
|
146
314
|
? key.slice(separatorIndex + KEY_SEPARATOR.length)
|
|
147
315
|
: key;
|
|
148
316
|
}
|
|
149
|
-
scanMessageHandlers(controllerClass, controllerBindingKey, app) {
|
|
150
|
-
this.scanHandlers(controllerClass, controllerBindingKey, app, decorators_1.MESSAGE_HANDLER_METADATA.key, false);
|
|
151
|
-
}
|
|
152
|
-
scanEventHandlers(controllerClass, controllerBindingKey, app) {
|
|
153
|
-
this.scanHandlers(controllerClass, controllerBindingKey, app, decorators_1.EVENT_HANDLER_METADATA.key, true);
|
|
154
|
-
}
|
|
155
317
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
318
|
+
* Convert a `DiscoveredHandler` into a runtime `MessageHandler`,
|
|
319
|
+
* applying validation rules (no mixed kinds per pattern, no duplicate
|
|
320
|
+
* request handlers) before inserting into the registry.
|
|
158
321
|
*/
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
handler
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
322
|
+
registerDiscovered(discovered, controllerClass, controllerBindingKey, app, discovererId) {
|
|
323
|
+
const isEvent = discovered.kind === discovery_1.HANDLER_KIND_EVENT;
|
|
324
|
+
const rawTransport = discovered.transport;
|
|
325
|
+
const transportForKey = rawTransport === '*' ? undefined : rawTransport;
|
|
326
|
+
const key = this.buildKey(transportForKey, discovered.pattern);
|
|
327
|
+
const keyOrPattern = (0, utils_1.normalizePattern)(discovered.pattern);
|
|
328
|
+
const existing = this.entries.get(key);
|
|
329
|
+
const firstExisting = existing?.[0];
|
|
330
|
+
// Reject mixed handler types on the same pattern.
|
|
331
|
+
if (firstExisting && firstExisting.kind !== discovered.kind) {
|
|
332
|
+
throw new errors_1.TransportConfigError(`Cannot mix handler kinds for pattern "${keyOrPattern}" on transport "${rawTransport}": ` +
|
|
333
|
+
`existing kind "${firstExisting.kind}" from ` +
|
|
334
|
+
`${firstExisting.controllerClass.name}.${firstExisting.methodName} ` +
|
|
335
|
+
`(discoverer "${firstExisting.discovererId}") conflicts with ` +
|
|
336
|
+
`new kind "${discovered.kind}" from ` +
|
|
337
|
+
`${controllerClass.name}.${discovered.methodName} ` +
|
|
338
|
+
`(discoverer "${discovererId}").`);
|
|
339
|
+
}
|
|
340
|
+
// Reject duplicate request/response handlers.
|
|
341
|
+
if (firstExisting && !isEvent) {
|
|
342
|
+
throw new errors_1.TransportConfigError(`Duplicate request handler for pattern "${keyOrPattern}" on transport "${rawTransport}": ` +
|
|
343
|
+
`pattern is already bound to ` +
|
|
344
|
+
`${firstExisting.controllerClass.name}.${firstExisting.methodName} ` +
|
|
345
|
+
`contributed by discoverer "${firstExisting.discovererId}".`);
|
|
346
|
+
}
|
|
347
|
+
const handler = this.createHandler(controllerBindingKey, discovered.methodName, app);
|
|
348
|
+
handler.isEventHandler = isEvent;
|
|
349
|
+
if (transportForKey !== undefined) {
|
|
350
|
+
handler.transport = transportForKey;
|
|
351
|
+
}
|
|
352
|
+
if (discovered.options?.extras !== undefined) {
|
|
353
|
+
handler.extras = discovered.options.extras;
|
|
354
|
+
}
|
|
355
|
+
const entry = {
|
|
356
|
+
pattern: discovered.pattern,
|
|
357
|
+
transport: rawTransport,
|
|
358
|
+
kind: discovered.kind,
|
|
359
|
+
controllerClass,
|
|
360
|
+
methodName: discovered.methodName,
|
|
361
|
+
discovererId,
|
|
362
|
+
...(discovered.options !== undefined
|
|
363
|
+
? { options: discovered.options }
|
|
364
|
+
: {}),
|
|
365
|
+
handler,
|
|
366
|
+
};
|
|
367
|
+
const decoratorLabel = `@${discovererId}Handler`;
|
|
368
|
+
if (existing) {
|
|
369
|
+
if (firstExisting && firstExisting.discovererId !== entry.discovererId) {
|
|
370
|
+
debug('event handler chain on pattern %s (transport %s) now spans discoverers: %s + %s', keyOrPattern, rawTransport, firstExisting.discovererId, entry.discovererId);
|
|
188
371
|
}
|
|
372
|
+
existing.push(entry);
|
|
373
|
+
debug('chained %s [%s] on %s.%s', decoratorLabel, keyOrPattern, controllerClass.name, discovered.methodName);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
this.entries.set(key, [entry]);
|
|
377
|
+
debug('discovered %s [%s] on %s.%s', decoratorLabel, (0, utils_1.normalizePattern)(discovered.pattern), controllerClass.name, discovered.methodName);
|
|
189
378
|
}
|
|
190
379
|
}
|
|
191
380
|
/**
|
|
192
|
-
* Create a handler function that invokes the controller method
|
|
193
|
-
* LoopBack's native invocation pipeline.
|
|
381
|
+
* Create a handler function that invokes the controller method
|
|
382
|
+
* through LoopBack's native invocation pipeline.
|
|
194
383
|
*
|
|
195
384
|
* Each invocation creates a uniquely named child Context from the
|
|
196
|
-
* application context. Per-message bindings (payload, transport
|
|
197
|
-
* are scoped to the child context and cannot collide with
|
|
198
|
-
* messages.
|
|
385
|
+
* application context. Per-message bindings (payload, transport
|
|
386
|
+
* context) are scoped to the child context and cannot collide with
|
|
387
|
+
* concurrent messages.
|
|
199
388
|
*
|
|
200
389
|
* The child context lifetime extends until the handler result fully
|
|
201
390
|
* resolves. For Observable results, the context stays open until the
|
|
@@ -205,7 +394,8 @@ let HandlerRegistry = class HandlerRegistry {
|
|
|
205
394
|
const handler = async (data, context) => {
|
|
206
395
|
const invocationCtx = new core_1.Context(app, `transport-invocation-${(0, crypto_1.randomUUID)()}`);
|
|
207
396
|
// Idempotent cleanup guard: Context.close() is synchronous but
|
|
208
|
-
// may be reached from multiple paths (error + teardown) for
|
|
397
|
+
// may be reached from multiple paths (error + teardown) for
|
|
398
|
+
// Observables.
|
|
209
399
|
let closed = false;
|
|
210
400
|
const cleanup = () => {
|
|
211
401
|
if (!closed) {
|
|
@@ -219,13 +409,13 @@ let HandlerRegistry = class HandlerRegistry {
|
|
|
219
409
|
const controller = await invocationCtx.get(controllerBindingKey);
|
|
220
410
|
const method = controller[methodName];
|
|
221
411
|
if (typeof method !== 'function') {
|
|
222
|
-
throw new
|
|
412
|
+
throw new errors_1.TransportConfigError(`Transport handler method "${methodName}" not found on controller "${controllerBindingKey}"`);
|
|
223
413
|
}
|
|
224
|
-
// Invoke through LoopBack's method invocation pipeline
|
|
225
|
-
//
|
|
226
|
-
//
|
|
414
|
+
// Invoke through LoopBack's method invocation pipeline so we
|
|
415
|
+
// honour parameter injection, interceptors, and other framework
|
|
416
|
+
// invocation semantics.
|
|
227
417
|
const result = await (0, core_1.invokeMethod)(controller, methodName, invocationCtx, [data, context]);
|
|
228
|
-
// For Observable results, defer context cleanup until stream completes
|
|
418
|
+
// For Observable results, defer context cleanup until stream completes.
|
|
229
419
|
if ((0, rxjs_1.isObservable)(result)) {
|
|
230
420
|
const obs = result;
|
|
231
421
|
return new rxjs_1.Observable(subscriber => {
|
|
@@ -246,7 +436,7 @@ let HandlerRegistry = class HandlerRegistry {
|
|
|
246
436
|
};
|
|
247
437
|
});
|
|
248
438
|
}
|
|
249
|
-
// For non-Observable results, close context immediately
|
|
439
|
+
// For non-Observable results, close context immediately.
|
|
250
440
|
cleanup();
|
|
251
441
|
return result;
|
|
252
442
|
}
|