@ebarahona/loopback-transport-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +297 -0
  3. package/dist/client/client-proxy.d.ts +147 -0
  4. package/dist/client/client-proxy.js +225 -0
  5. package/dist/client/client-proxy.js.map +1 -0
  6. package/dist/client/index.d.ts +1 -0
  7. package/dist/client/index.js +18 -0
  8. package/dist/client/index.js.map +1 -0
  9. package/dist/context/execution-context.d.ts +102 -0
  10. package/dist/context/execution-context.js +110 -0
  11. package/dist/context/execution-context.js.map +1 -0
  12. package/dist/context/index.d.ts +1 -0
  13. package/dist/context/index.js +18 -0
  14. package/dist/context/index.js.map +1 -0
  15. package/dist/decorators/constants.d.ts +41 -0
  16. package/dist/decorators/constants.js +13 -0
  17. package/dist/decorators/constants.js.map +1 -0
  18. package/dist/decorators/event-handler.decorator.d.ts +28 -0
  19. package/dist/decorators/event-handler.decorator.js +42 -0
  20. package/dist/decorators/event-handler.decorator.js.map +1 -0
  21. package/dist/decorators/index.d.ts +4 -0
  22. package/dist/decorators/index.js +21 -0
  23. package/dist/decorators/index.js.map +1 -0
  24. package/dist/decorators/message-handler.decorator.d.ts +26 -0
  25. package/dist/decorators/message-handler.decorator.js +40 -0
  26. package/dist/decorators/message-handler.decorator.js.map +1 -0
  27. package/dist/decorators/payload.decorator.d.ts +30 -0
  28. package/dist/decorators/payload.decorator.js +42 -0
  29. package/dist/decorators/payload.decorator.js.map +1 -0
  30. package/dist/index.d.ts +11 -0
  31. package/dist/index.js +42 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/interfaces/index.d.ts +4 -0
  34. package/dist/interfaces/index.js +21 -0
  35. package/dist/interfaces/index.js.map +1 -0
  36. package/dist/interfaces/message-handler.interface.d.ts +21 -0
  37. package/dist/interfaces/message-handler.interface.js +3 -0
  38. package/dist/interfaces/message-handler.interface.js.map +1 -0
  39. package/dist/interfaces/packet.interface.d.ts +42 -0
  40. package/dist/interfaces/packet.interface.js +3 -0
  41. package/dist/interfaces/packet.interface.js.map +1 -0
  42. package/dist/interfaces/transport-client.interface.d.ts +41 -0
  43. package/dist/interfaces/transport-client.interface.js +3 -0
  44. package/dist/interfaces/transport-client.interface.js.map +1 -0
  45. package/dist/interfaces/transport-server.interface.d.ts +44 -0
  46. package/dist/interfaces/transport-server.interface.js +3 -0
  47. package/dist/interfaces/transport-server.interface.js.map +1 -0
  48. package/dist/keys.d.ts +88 -0
  49. package/dist/keys.js +123 -0
  50. package/dist/keys.js.map +1 -0
  51. package/dist/registry/handler-registry.d.ts +77 -0
  52. package/dist/registry/handler-registry.js +265 -0
  53. package/dist/registry/handler-registry.js.map +1 -0
  54. package/dist/registry/index.d.ts +1 -0
  55. package/dist/registry/index.js +18 -0
  56. package/dist/registry/index.js.map +1 -0
  57. package/dist/serializers/index.d.ts +1 -0
  58. package/dist/serializers/index.js +18 -0
  59. package/dist/serializers/index.js.map +1 -0
  60. package/dist/serializers/serializer.interface.d.ts +62 -0
  61. package/dist/serializers/serializer.interface.js +52 -0
  62. package/dist/serializers/serializer.interface.js.map +1 -0
  63. package/dist/server/index.d.ts +1 -0
  64. package/dist/server/index.js +18 -0
  65. package/dist/server/index.js.map +1 -0
  66. package/dist/server/server-base.d.ts +154 -0
  67. package/dist/server/server-base.js +287 -0
  68. package/dist/server/server-base.js.map +1 -0
  69. package/dist/transport-booter.d.ts +31 -0
  70. package/dist/transport-booter.js +117 -0
  71. package/dist/transport-booter.js.map +1 -0
  72. package/dist/transport.component.d.ts +26 -0
  73. package/dist/transport.component.js +42 -0
  74. package/dist/transport.component.js.map +1 -0
  75. package/dist/utils/index.d.ts +1 -0
  76. package/dist/utils/index.js +6 -0
  77. package/dist/utils/index.js.map +1 -0
  78. package/dist/utils/normalize-pattern.d.ts +18 -0
  79. package/dist/utils/normalize-pattern.js +75 -0
  80. package/dist/utils/normalize-pattern.js.map +1 -0
  81. package/package.json +73 -0
@@ -0,0 +1,287 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ServerBase = void 0;
4
+ const rxjs_1 = require("rxjs");
5
+ const debug_1 = require("debug");
6
+ const serializers_1 = require("../serializers");
7
+ const utils_1 = require("../utils");
8
+ const debug = (0, debug_1.default)('loopback:transport:server');
9
+ const DEFAULT_HANDLER_TIMEOUT_MS = 30000;
10
+ /**
11
+ * Abstract base class for transport servers.
12
+ *
13
+ * 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.
16
+ *
17
+ * 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
24
+ */
25
+ class ServerBase {
26
+ constructor(options) {
27
+ this.messageHandlers = new Map();
28
+ this.statusSubject = new rxjs_1.ReplaySubject(1);
29
+ this.status$ = this.statusSubject.asObservable();
30
+ this.serializer = options?.serializer ?? new serializers_1.JsonSerializer();
31
+ this.deserializer = options?.deserializer ?? new serializers_1.JsonDeserializer();
32
+ this.handlerTimeoutMs = options?.handlerTimeoutMs ?? DEFAULT_HANDLER_TIMEOUT_MS;
33
+ }
34
+ /**
35
+ * Emit a status change. Called by transport adapters in listen() and close().
36
+ */
37
+ setStatus(status) {
38
+ this.statusSubject.next(status);
39
+ }
40
+ /**
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.
44
+ *
45
+ * Call only when the server is being permanently disposed.
46
+ * Normal close() should call setStatus('disconnected'), NOT dispose().
47
+ */
48
+ dispose() {
49
+ this.statusSubject.next('disconnected');
50
+ this.statusSubject.complete();
51
+ }
52
+ /**
53
+ * Register a handler for a message pattern.
54
+ *
55
+ * For request/response handlers (@messageHandler): duplicate patterns
56
+ * throw an error. Only one handler per pattern is allowed.
57
+ *
58
+ * For event handlers (@eventHandler): multiple handlers on the same
59
+ * pattern are stored in an array and all execute.
60
+ *
61
+ * Mixing @messageHandler and @eventHandler on the same pattern throws.
62
+ *
63
+ * Handlers are never mutated. Each server owns its own handler array,
64
+ * so the same handler object can be safely shared across servers.
65
+ */
66
+ addHandler(pattern, handler) {
67
+ const normalized = this.normalizePattern(pattern);
68
+ const existing = this.messageHandlers.get(normalized);
69
+ 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}. ` +
73
+ 'A pattern must be exclusively request/response or event, not both.');
74
+ }
75
+ // Reject duplicate request/response handlers
76
+ if (!handler.isEventHandler) {
77
+ throw new Error(`Handler already registered for pattern: ${normalized}. ` +
78
+ 'Only one @messageHandler per pattern is allowed. ' +
79
+ 'Use @eventHandler for multiple handlers on the same pattern.');
80
+ }
81
+ // Append event handler to the array
82
+ existing.push(handler);
83
+ return;
84
+ }
85
+ this.messageHandlers.set(normalized, [handler]);
86
+ }
87
+ /**
88
+ * Get all registered handlers. Returns a defensive copy.
89
+ */
90
+ getHandlers() {
91
+ const copy = new Map();
92
+ for (const [key, handlers] of this.messageHandlers) {
93
+ copy.set(key, [...handlers]);
94
+ }
95
+ return copy;
96
+ }
97
+ /**
98
+ * Remove all registered handlers.
99
+ * Called before rebinding on restart to prevent duplicate registration.
100
+ */
101
+ clearHandlers() {
102
+ this.messageHandlers.clear();
103
+ }
104
+ /**
105
+ * Get handlers by pattern. Normalizes the pattern before lookup.
106
+ * Returns a defensive copy for external consumers.
107
+ */
108
+ getHandlersByPattern(pattern) {
109
+ const handlers = this.lookupHandlers(pattern);
110
+ return handlers ? [...handlers] : undefined;
111
+ }
112
+ /**
113
+ * Internal handler lookup. Returns the live array for dispatch performance.
114
+ */
115
+ lookupHandlers(pattern) {
116
+ return this.messageHandlers.get(this.normalizePattern(pattern));
117
+ }
118
+ /**
119
+ * Handle a request/response message.
120
+ *
121
+ * Invokes the handler and calls respond() with each result value.
122
+ * Observable subscriptions are tracked and cleaned up on completion,
123
+ * error, or timeout.
124
+ *
125
+ * Returns a HandlerResult that separates response semantics from
126
+ * broker settlement semantics:
127
+ *
128
+ * - `success`: Handler completed normally. Ack the message.
129
+ * - `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.
132
+ * - `infrastructure-error`: No handler found or framework failure.
133
+ * Nack/dead-letter the message.
134
+ *
135
+ * Observable handlers that do not complete within handlerTimeoutMs
136
+ * are terminated with a timeout error (handler-error).
137
+ *
138
+ * If respond() throws (adapter publication failure), the error is
139
+ * caught and returned as infrastructure-error.
140
+ */
141
+ async handleMessage(request, respond, context) {
142
+ // Wrap respond to catch adapter publication failures
143
+ const safeRespond = (packet) => {
144
+ try {
145
+ respond(packet);
146
+ }
147
+ catch (err) {
148
+ debug('respond() failed for pattern [%s]: %O', request.pattern, err);
149
+ throw err;
150
+ }
151
+ };
152
+ try {
153
+ return await this.executeHandler(request, safeRespond, context);
154
+ }
155
+ catch (err) {
156
+ return { outcome: 'infrastructure-error', error: err };
157
+ }
158
+ }
159
+ /**
160
+ * Core handler execution. Separated from handleMessage so that
161
+ * respond() failures bubble up and are caught by the outer wrapper.
162
+ */
163
+ async executeHandler(request, respond, context) {
164
+ const handlers = this.lookupHandlers(request.pattern);
165
+ if (!handlers || handlers.length === 0) {
166
+ const err = `No handler for pattern: ${request.pattern}`;
167
+ respond({ err: this.serializeError(err), isDisposed: true });
168
+ return { outcome: 'infrastructure-error', error: new Error(err) };
169
+ }
170
+ const handler = handlers[0];
171
+ let result;
172
+ try {
173
+ result = await handler(request.data, context);
174
+ }
175
+ catch (err) {
176
+ respond({ err: this.serializeError(err), isDisposed: true });
177
+ return { outcome: 'handler-error', error: err };
178
+ }
179
+ if ((0, rxjs_1.isObservable)(result)) {
180
+ const obs = result.pipe((0, rxjs_1.timeout)(this.handlerTimeoutMs));
181
+ return new Promise((resolve) => {
182
+ let settled = false;
183
+ const settle = (result) => {
184
+ if (settled)
185
+ return;
186
+ settled = true;
187
+ subscription?.unsubscribe();
188
+ resolve(result);
189
+ };
190
+ let subscription;
191
+ subscription = obs.subscribe({
192
+ next: value => {
193
+ if (settled)
194
+ return;
195
+ try {
196
+ respond({ response: value });
197
+ }
198
+ catch (respondErr) {
199
+ settle({ outcome: 'infrastructure-error', error: respondErr });
200
+ }
201
+ },
202
+ error: err => {
203
+ if (settled)
204
+ return;
205
+ if (err instanceof rxjs_1.TimeoutError) {
206
+ debug('handler timeout after %dms for pattern [%s]', this.handlerTimeoutMs, request.pattern);
207
+ }
208
+ try {
209
+ respond({ err: this.serializeError(err), isDisposed: true });
210
+ }
211
+ catch (respondErr) {
212
+ settle({ outcome: 'infrastructure-error', error: respondErr });
213
+ return;
214
+ }
215
+ settle({ outcome: 'handler-error', error: err });
216
+ },
217
+ complete: () => {
218
+ if (settled)
219
+ return;
220
+ try {
221
+ respond({ isDisposed: true });
222
+ }
223
+ catch (respondErr) {
224
+ settle({ outcome: 'infrastructure-error', error: respondErr });
225
+ return;
226
+ }
227
+ settle({ outcome: 'success' });
228
+ },
229
+ });
230
+ });
231
+ }
232
+ respond({ response: result, isDisposed: true });
233
+ return { outcome: 'success' };
234
+ }
235
+ /**
236
+ * Handle a fire-and-forget event.
237
+ *
238
+ * Invokes all chained handlers for the pattern.
239
+ * Errors are logged but not propagated (fire-and-forget semantics).
240
+ *
241
+ * Observable event handlers that do not complete within handlerTimeoutMs
242
+ * are terminated and logged.
243
+ */
244
+ async handleEvent(event, context) {
245
+ const handlers = this.lookupHandlers(event.pattern);
246
+ if (!handlers || handlers.length === 0)
247
+ return;
248
+ for (const handler of handlers) {
249
+ try {
250
+ const result = await handler(event.data, context);
251
+ if ((0, rxjs_1.isObservable)(result)) {
252
+ const obs = result.pipe((0, rxjs_1.timeout)(this.handlerTimeoutMs));
253
+ await (0, rxjs_1.lastValueFrom)(obs, { defaultValue: undefined });
254
+ }
255
+ }
256
+ catch (err) {
257
+ if (err instanceof rxjs_1.TimeoutError) {
258
+ debug('event handler timeout after %dms for pattern [%s]', this.handlerTimeoutMs, event.pattern);
259
+ }
260
+ else {
261
+ debug('event handler failed for pattern [%s]: %O', event.pattern, err);
262
+ }
263
+ }
264
+ }
265
+ }
266
+ /**
267
+ * Normalize a pattern to a consistent string key.
268
+ * Delegates to the shared normalizePattern utility.
269
+ */
270
+ normalizePattern(pattern) {
271
+ return (0, utils_1.normalizePattern)(pattern);
272
+ }
273
+ /**
274
+ * Serialize an error to a string for transport.
275
+ */
276
+ serializeError(err) {
277
+ if (err instanceof rxjs_1.TimeoutError)
278
+ return 'Handler timeout';
279
+ if (err instanceof Error)
280
+ return `${err.name}: ${err.message}`;
281
+ if (typeof err === 'string')
282
+ return err;
283
+ return String(err);
284
+ }
285
+ }
286
+ exports.ServerBase = ServerBase;
287
+ //# sourceMappingURL=server-base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-base.js","sourceRoot":"","sources":["../../src/server/server-base.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { Application, LifeCycleObserver } from '@loopback/core';
2
+ import { HandlerRegistry } from './registry';
3
+ /**
4
+ * Lifecycle observer that discovers transport handlers after boot
5
+ * and starts/stops transport servers.
6
+ *
7
+ * Runs after all controllers are registered (boot phase complete).
8
+ * Scans controllers for @messageHandler/@eventHandler metadata,
9
+ * registers handlers with transport servers, then starts listeners.
10
+ *
11
+ * Startup behavior: sequential with deterministic rollback.
12
+ * Servers start one at a time. If any fails, all previously started
13
+ * servers are rolled back. No race between concurrent starts.
14
+ *
15
+ * Shutdown behavior: best-effort parallel. All servers stop concurrently.
16
+ * Individual errors are logged but don't prevent other servers from stopping.
17
+ *
18
+ * Restart safety: servers are cleared of handlers before rebinding
19
+ * to prevent duplicate registration on stop/start cycles.
20
+ */
21
+ export declare class TransportBooter implements LifeCycleObserver {
22
+ private app;
23
+ private registry;
24
+ constructor(app: Application, registry: HandlerRegistry);
25
+ start(): Promise<void>;
26
+ stop(): Promise<void>;
27
+ /**
28
+ * Resolve all registered transport servers with their names.
29
+ */
30
+ private getServerEntries;
31
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.TransportBooter = void 0;
16
+ const core_1 = require("@loopback/core");
17
+ const debug_1 = require("debug");
18
+ const registry_1 = require("./registry");
19
+ const keys_1 = require("./keys");
20
+ const debug = (0, debug_1.default)('loopback:transport:booter');
21
+ /**
22
+ * Lifecycle observer that discovers transport handlers after boot
23
+ * and starts/stops transport servers.
24
+ *
25
+ * Runs after all controllers are registered (boot phase complete).
26
+ * Scans controllers for @messageHandler/@eventHandler metadata,
27
+ * registers handlers with transport servers, then starts listeners.
28
+ *
29
+ * Startup behavior: sequential with deterministic rollback.
30
+ * Servers start one at a time. If any fails, all previously started
31
+ * servers are rolled back. No race between concurrent starts.
32
+ *
33
+ * Shutdown behavior: best-effort parallel. All servers stop concurrently.
34
+ * Individual errors are logged but don't prevent other servers from stopping.
35
+ *
36
+ * Restart safety: servers are cleared of handlers before rebinding
37
+ * to prevent duplicate registration on stop/start cycles.
38
+ */
39
+ let TransportBooter = class TransportBooter {
40
+ constructor(app, registry) {
41
+ this.app = app;
42
+ this.registry = registry;
43
+ }
44
+ async start() {
45
+ debug('transport booter starting');
46
+ // Discover handlers from controllers (idempotent: clears first)
47
+ await this.registry.discoverHandlers(this.app);
48
+ // Clear existing handlers from servers before rebinding
49
+ const entries = await this.getServerEntries();
50
+ for (const { server } of entries) {
51
+ server.clearHandlers();
52
+ }
53
+ // Bind handlers to transport servers
54
+ await this.registry.bindToServers(this.app);
55
+ // Start servers sequentially for deterministic rollback
56
+ const started = [];
57
+ try {
58
+ for (const { name, server } of entries) {
59
+ await server.listen();
60
+ started.push({ name, server });
61
+ debug('transport server [%s] started', name);
62
+ }
63
+ }
64
+ catch (err) {
65
+ debug('startup failed, rolling back %d started servers', started.length);
66
+ for (const { name, server } of started.reverse()) {
67
+ try {
68
+ await server.close();
69
+ debug('transport server [%s] rolled back', name);
70
+ }
71
+ catch (closeErr) {
72
+ debug('transport server [%s] rollback failed: %O', name, closeErr);
73
+ }
74
+ }
75
+ throw err;
76
+ }
77
+ debug('transport booter started (%d handlers, %d servers)', this.registry.getHandlers().size, entries.length);
78
+ }
79
+ async stop() {
80
+ debug('transport booter stopping');
81
+ const entries = await this.getServerEntries();
82
+ const results = await Promise.allSettled(entries.map(async ({ name, server }) => {
83
+ await server.close();
84
+ debug('transport server [%s] stopped', name);
85
+ }));
86
+ const failures = results.filter((r) => r.status === 'rejected');
87
+ if (failures.length > 0) {
88
+ debug('%d transport server(s) failed to stop cleanly', failures.length);
89
+ for (const failure of failures) {
90
+ debug('transport server shutdown failure: %O', failure.reason);
91
+ }
92
+ }
93
+ debug('transport booter stopped');
94
+ }
95
+ /**
96
+ * Resolve all registered transport servers with their names.
97
+ */
98
+ async getServerEntries() {
99
+ const bindings = this.app.findByTag(keys_1.TRANSPORT_SERVER_TAG);
100
+ const entries = [];
101
+ for (const binding of bindings) {
102
+ const server = await this.app.get(binding.key);
103
+ const name = String(binding.tagMap?.[keys_1.TRANSPORT_NAME_TAG] ?? binding.key);
104
+ entries.push({ name, server });
105
+ }
106
+ return entries;
107
+ }
108
+ };
109
+ exports.TransportBooter = TransportBooter;
110
+ exports.TransportBooter = TransportBooter = __decorate([
111
+ (0, core_1.lifeCycleObserver)('transport'),
112
+ __param(0, (0, core_1.inject)(core_1.CoreBindings.APPLICATION_INSTANCE)),
113
+ __param(1, (0, core_1.inject)(keys_1.TransportBindings.HANDLER_REGISTRY)),
114
+ __metadata("design:paramtypes", [core_1.Application,
115
+ registry_1.HandlerRegistry])
116
+ ], TransportBooter);
117
+ //# sourceMappingURL=transport-booter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport-booter.js","sourceRoot":"","sources":["../src/transport-booter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { Binding, Component, Constructor, LifeCycleObserver } from '@loopback/core';
2
+ /**
3
+ * LoopBack 4 component that enables transport-agnostic microservices.
4
+ *
5
+ * Registers the core transport extension points:
6
+ * - HandlerRegistry (singleton): discovers and manages transport handlers
7
+ * - TransportBooter (lifecycle observer): wires handlers to servers at boot
8
+ *
9
+ * The component intentionally stays thin. Discovery, validation,
10
+ * server binding, and listener lifecycle management live in the
11
+ * TransportBooter/HandlerRegistry layer so the component remains a
12
+ * stable package boundary for transport adapters.
13
+ *
14
+ * Usage:
15
+ * ```typescript
16
+ * const app = new Application();
17
+ * app.component(TransportComponent);
18
+ * ```
19
+ *
20
+ * Transport adapters can build on top of this component by contributing
21
+ * server bindings and configuration under TransportBindings.
22
+ */
23
+ export declare class TransportComponent implements Component {
24
+ readonly bindings: Binding<unknown>[];
25
+ readonly lifeCycleObservers: Constructor<LifeCycleObserver>[];
26
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransportComponent = void 0;
4
+ const core_1 = require("@loopback/core");
5
+ const keys_1 = require("./keys");
6
+ const registry_1 = require("./registry");
7
+ const transport_booter_1 = require("./transport-booter");
8
+ /**
9
+ * LoopBack 4 component that enables transport-agnostic microservices.
10
+ *
11
+ * Registers the core transport extension points:
12
+ * - HandlerRegistry (singleton): discovers and manages transport handlers
13
+ * - TransportBooter (lifecycle observer): wires handlers to servers at boot
14
+ *
15
+ * The component intentionally stays thin. Discovery, validation,
16
+ * server binding, and listener lifecycle management live in the
17
+ * TransportBooter/HandlerRegistry layer so the component remains a
18
+ * stable package boundary for transport adapters.
19
+ *
20
+ * Usage:
21
+ * ```typescript
22
+ * const app = new Application();
23
+ * app.component(TransportComponent);
24
+ * ```
25
+ *
26
+ * Transport adapters can build on top of this component by contributing
27
+ * server bindings and configuration under TransportBindings.
28
+ */
29
+ class TransportComponent {
30
+ constructor() {
31
+ this.bindings = [
32
+ core_1.Binding.bind(keys_1.TransportBindings.HANDLER_REGISTRY)
33
+ .toClass(registry_1.HandlerRegistry)
34
+ .inScope(core_1.BindingScope.SINGLETON),
35
+ ];
36
+ this.lifeCycleObservers = [
37
+ transport_booter_1.TransportBooter,
38
+ ];
39
+ }
40
+ }
41
+ exports.TransportComponent = TransportComponent;
42
+ //# sourceMappingURL=transport.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.component.js","sourceRoot":"","sources":["../src/transport.component.ts"],"names":[],"mappings":";;;AAAA,yCAMwB;AACxB,iCAAyC;AACzC,yCAA2C;AAC3C,yDAAmD;AAEnD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAa,kBAAkB;IAA/B;QACW,aAAQ,GAAuB;YACtC,cAAO,CAAC,IAAI,CAAC,wBAAiB,CAAC,gBAAgB,CAAC;iBAC7C,OAAO,CAAC,0BAAe,CAAC;iBACxB,OAAO,CAAC,mBAAY,CAAC,SAAS,CAAC;SACnC,CAAC;QAEO,uBAAkB,GAAqC;YAC9D,kCAAe;SAChB,CAAC;IACJ,CAAC;CAAA;AAVD,gDAUC"}
@@ -0,0 +1 @@
1
+ export { normalizePattern } from './normalize-pattern';
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizePattern = void 0;
4
+ var normalize_pattern_1 = require("./normalize-pattern");
5
+ Object.defineProperty(exports, "normalizePattern", { enumerable: true, get: function () { return normalize_pattern_1.normalizePattern; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;AAAA,yDAAqD;AAA7C,qHAAA,gBAAgB,OAAA"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Normalize a pattern to a consistent string key.
3
+ *
4
+ * Strings pass through as-is. Objects are deep-sorted by key
5
+ * and JSON-stringified for consistent lookup regardless of
6
+ * property insertion order.
7
+ *
8
+ * Only JSON-compatible values are accepted: strings, finite numbers,
9
+ * booleans, null, plain objects, and arrays. Non-JSON values
10
+ * (undefined, functions, symbols, NaN, Infinity, BigInt, Date,
11
+ * RegExp, Map, Set, class instances) are rejected deterministically.
12
+ *
13
+ * Shared (non-circular) object references are allowed. Only true
14
+ * cycles throw.
15
+ *
16
+ * @throws Error on circular references, non-JSON values, or non-plain objects.
17
+ */
18
+ export declare function normalizePattern(pattern: string | Record<string, unknown>): string;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizePattern = void 0;
4
+ /**
5
+ * Normalize a pattern to a consistent string key.
6
+ *
7
+ * Strings pass through as-is. Objects are deep-sorted by key
8
+ * and JSON-stringified for consistent lookup regardless of
9
+ * property insertion order.
10
+ *
11
+ * Only JSON-compatible values are accepted: strings, finite numbers,
12
+ * booleans, null, plain objects, and arrays. Non-JSON values
13
+ * (undefined, functions, symbols, NaN, Infinity, BigInt, Date,
14
+ * RegExp, Map, Set, class instances) are rejected deterministically.
15
+ *
16
+ * Shared (non-circular) object references are allowed. Only true
17
+ * cycles throw.
18
+ *
19
+ * @throws Error on circular references, non-JSON values, or non-plain objects.
20
+ */
21
+ function normalizePattern(pattern) {
22
+ if (typeof pattern === 'string')
23
+ return pattern;
24
+ return stableStringify(pattern, new Set());
25
+ }
26
+ exports.normalizePattern = normalizePattern;
27
+ function isPlainObject(obj) {
28
+ if (typeof obj !== 'object' || obj === null)
29
+ return false;
30
+ const proto = Object.getPrototypeOf(obj);
31
+ return proto === Object.prototype || proto === null;
32
+ }
33
+ function stableStringify(obj, seen) {
34
+ if (obj === null)
35
+ return 'null';
36
+ const type = typeof obj;
37
+ if (type === 'string' || type === 'boolean') {
38
+ return JSON.stringify(obj);
39
+ }
40
+ if (type === 'number') {
41
+ const n = obj;
42
+ if (!Number.isFinite(n)) {
43
+ throw new Error(`Pattern contains ${String(n)}, which is not JSON-serializable`);
44
+ }
45
+ return JSON.stringify(n);
46
+ }
47
+ if (type === 'undefined' ||
48
+ type === 'function' ||
49
+ type === 'symbol' ||
50
+ type === 'bigint') {
51
+ throw new Error(`Pattern contains ${type} value, which is not JSON-serializable`);
52
+ }
53
+ // At this point, type === 'object' and obj !== null
54
+ const o = obj;
55
+ if (seen.has(o)) {
56
+ throw new Error('Circular reference detected in pattern object');
57
+ }
58
+ if (Array.isArray(o)) {
59
+ seen.add(o);
60
+ const result = '[' + o.map(item => stableStringify(item, seen)).join(',') + ']';
61
+ seen.delete(o);
62
+ return result;
63
+ }
64
+ if (!isPlainObject(o)) {
65
+ const name = o.constructor?.name ?? 'unknown';
66
+ throw new Error(`Pattern contains ${name} instance, which is not a plain object`);
67
+ }
68
+ seen.add(o);
69
+ const keys = Object.keys(o).sort();
70
+ const pairs = keys.map(key => `${JSON.stringify(key)}:${stableStringify(o[key], seen)}`);
71
+ const result = '{' + pairs.join(',') + '}';
72
+ seen.delete(o);
73
+ return result;
74
+ }
75
+ //# sourceMappingURL=normalize-pattern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-pattern.js","sourceRoot":"","sources":["../../src/utils/normalize-pattern.ts"],"names":[],"mappings":"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"}