@fluojs/microservices 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.ko.md +182 -0
- package/README.md +179 -0
- package/dist/decorators.d.ts +51 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +106 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/metadata.d.ts +9 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +48 -0
- package/dist/module.d.ts +23 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +55 -0
- package/dist/service.d.ts +116 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +550 -0
- package/dist/status.d.ts +30 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +79 -0
- package/dist/tokens.d.ts +7 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +4 -0
- package/dist/transports/event-handler-logger.d.ts +3 -0
- package/dist/transports/event-handler-logger.d.ts.map +1 -0
- package/dist/transports/event-handler-logger.js +3 -0
- package/dist/transports/grpc-transport.d.ts +193 -0
- package/dist/transports/grpc-transport.d.ts.map +1 -0
- package/dist/transports/grpc-transport.js +1035 -0
- package/dist/transports/kafka-transport.d.ts +77 -0
- package/dist/transports/kafka-transport.d.ts.map +1 -0
- package/dist/transports/kafka-transport.js +289 -0
- package/dist/transports/mqtt-transport.d.ts +124 -0
- package/dist/transports/mqtt-transport.d.ts.map +1 -0
- package/dist/transports/mqtt-transport.js +460 -0
- package/dist/transports/nats-transport.d.ts +92 -0
- package/dist/transports/nats-transport.d.ts.map +1 -0
- package/dist/transports/nats-transport.js +218 -0
- package/dist/transports/rabbitmq-transport.d.ts +77 -0
- package/dist/transports/rabbitmq-transport.d.ts.map +1 -0
- package/dist/transports/rabbitmq-transport.js +263 -0
- package/dist/transports/redis-streams-transport.d.ts +136 -0
- package/dist/transports/redis-streams-transport.d.ts.map +1 -0
- package/dist/transports/redis-streams-transport.js +482 -0
- package/dist/transports/redis-transport.d.ts +73 -0
- package/dist/transports/redis-transport.d.ts.map +1 -0
- package/dist/transports/redis-transport.js +152 -0
- package/dist/transports/tcp-transport.d.ts +66 -0
- package/dist/transports/tcp-transport.d.ts.map +1 -0
- package/dist/transports/tcp-transport.js +283 -0
- package/dist/types.d.ts +105 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { logTransportEventHandlerFailure } from './event-handler-logger.js';
|
|
2
|
+
const mqttKinds = {
|
|
3
|
+
event: 'event',
|
|
4
|
+
message: 'message',
|
|
5
|
+
response: 'response'
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/** Options for configuring the MQTT microservice transport. */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* MQTT transport for topic-based request-response messages and event delivery.
|
|
12
|
+
*
|
|
13
|
+
* The adapter can either own its MQTT client or reuse one supplied by the application while
|
|
14
|
+
* keeping Fluo's message, event, and response channels isolated by topic.
|
|
15
|
+
*/
|
|
16
|
+
export class MqttMicroserviceTransport {
|
|
17
|
+
client;
|
|
18
|
+
closing = false;
|
|
19
|
+
handler;
|
|
20
|
+
internallyOwnedClient;
|
|
21
|
+
logger;
|
|
22
|
+
listening = false;
|
|
23
|
+
listenPromise;
|
|
24
|
+
messageListener = (topic, payload) => {
|
|
25
|
+
void this.handleInbound(topic, payload.toString('utf8')).catch(() => undefined);
|
|
26
|
+
};
|
|
27
|
+
eventQos;
|
|
28
|
+
eventRetain;
|
|
29
|
+
eventTopic;
|
|
30
|
+
messageQos;
|
|
31
|
+
messageRetain;
|
|
32
|
+
messageTopic;
|
|
33
|
+
pending = new Map();
|
|
34
|
+
replyTopic;
|
|
35
|
+
requestTimeoutMs;
|
|
36
|
+
responseQos;
|
|
37
|
+
responseRetain;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates an MQTT transport with topic, QoS, retain, and timeout settings.
|
|
41
|
+
*
|
|
42
|
+
* @param options MQTT client/module wiring and topic behavior for the transport.
|
|
43
|
+
*/
|
|
44
|
+
constructor(options) {
|
|
45
|
+
this.options = options;
|
|
46
|
+
const namespace = options.namespace ?? 'fluo.microservices';
|
|
47
|
+
this.eventTopic = options.eventTopic ?? `${namespace}.events`;
|
|
48
|
+
this.messageTopic = options.messageTopic ?? `${namespace}.messages`;
|
|
49
|
+
this.replyTopic = options.replyTopic ?? `${namespace}.responses.${crypto.randomUUID()}`;
|
|
50
|
+
this.eventQos = options.eventQos ?? 0;
|
|
51
|
+
this.messageQos = options.messageQos ?? 1;
|
|
52
|
+
this.responseQos = options.responseQos ?? 1;
|
|
53
|
+
this.eventRetain = options.eventRetain ?? false;
|
|
54
|
+
this.messageRetain = options.messageRetain ?? false;
|
|
55
|
+
this.responseRetain = options.responseRetain ?? false;
|
|
56
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 3_000;
|
|
57
|
+
this.client = options.client;
|
|
58
|
+
this.internallyOwnedClient = !options.client;
|
|
59
|
+
}
|
|
60
|
+
setLogger(logger) {
|
|
61
|
+
this.logger = logger;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Subscribes to the configured MQTT event, message, and reply topics.
|
|
66
|
+
*
|
|
67
|
+
* @param handler Runtime callback invoked for inbound event and message packets.
|
|
68
|
+
* @returns A promise that resolves once all subscriptions are active.
|
|
69
|
+
*/
|
|
70
|
+
async listen(handler) {
|
|
71
|
+
this.closing = false;
|
|
72
|
+
this.handler = handler;
|
|
73
|
+
if (this.listening) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (this.listenPromise) {
|
|
77
|
+
await this.listenPromise;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.listenPromise = (async () => {
|
|
81
|
+
const client = await this.resolveClient();
|
|
82
|
+
const subscribed = [];
|
|
83
|
+
client.on('message', this.messageListener);
|
|
84
|
+
try {
|
|
85
|
+
await this.subscribeTopic(client, this.eventTopic, this.eventQos);
|
|
86
|
+
subscribed.push(this.eventTopic);
|
|
87
|
+
await this.subscribeTopic(client, this.messageTopic, this.messageQos);
|
|
88
|
+
subscribed.push(this.messageTopic);
|
|
89
|
+
await this.subscribeTopic(client, this.replyTopic, this.responseQos);
|
|
90
|
+
subscribed.push(this.replyTopic);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
for (const topic of subscribed) {
|
|
93
|
+
await this.unsubscribeTopic(client, topic).catch(() => undefined);
|
|
94
|
+
}
|
|
95
|
+
client.off?.('message', this.messageListener);
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
this.listening = true;
|
|
99
|
+
})();
|
|
100
|
+
try {
|
|
101
|
+
await this.listenPromise;
|
|
102
|
+
} finally {
|
|
103
|
+
this.listenPromise = undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Sends one request-response message through MQTT.
|
|
109
|
+
*
|
|
110
|
+
* @param pattern Pattern identifying the remote message handler.
|
|
111
|
+
* @param payload Serializable request payload.
|
|
112
|
+
* @param signal Optional abort signal used to cancel the request.
|
|
113
|
+
* @returns The remote handler response payload.
|
|
114
|
+
*/
|
|
115
|
+
async send(pattern, payload, signal) {
|
|
116
|
+
if (this.closing) {
|
|
117
|
+
throw new Error('MqttMicroserviceTransport is closing. Wait for close() to complete before send().');
|
|
118
|
+
}
|
|
119
|
+
if (!this.listening) {
|
|
120
|
+
throw new Error('MqttMicroserviceTransport is not listening. Call listen() before send().');
|
|
121
|
+
}
|
|
122
|
+
const requestId = crypto.randomUUID();
|
|
123
|
+
const frame = {
|
|
124
|
+
kind: mqttKinds.message,
|
|
125
|
+
pattern,
|
|
126
|
+
payload,
|
|
127
|
+
requestId,
|
|
128
|
+
replyTopic: this.replyTopic
|
|
129
|
+
};
|
|
130
|
+
return await new Promise((resolve, reject) => {
|
|
131
|
+
let abortHandler;
|
|
132
|
+
let timeout;
|
|
133
|
+
let settled = false;
|
|
134
|
+
const cleanup = () => {
|
|
135
|
+
if (timeout) {
|
|
136
|
+
clearTimeout(timeout);
|
|
137
|
+
}
|
|
138
|
+
if (signal && abortHandler) {
|
|
139
|
+
signal.removeEventListener('abort', abortHandler);
|
|
140
|
+
}
|
|
141
|
+
this.pending.delete(requestId);
|
|
142
|
+
};
|
|
143
|
+
const entry = {
|
|
144
|
+
resolve: value => {
|
|
145
|
+
if (settled) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
settled = true;
|
|
149
|
+
cleanup();
|
|
150
|
+
resolve(value);
|
|
151
|
+
},
|
|
152
|
+
reject: error => {
|
|
153
|
+
if (settled) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
settled = true;
|
|
157
|
+
cleanup();
|
|
158
|
+
reject(error);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
this.pending.set(requestId, entry);
|
|
162
|
+
timeout = setTimeout(() => {
|
|
163
|
+
entry.reject(new Error(`MQTT request timed out after ${String(this.requestTimeoutMs)}ms waiting for pattern "${pattern}".`));
|
|
164
|
+
}, this.requestTimeoutMs);
|
|
165
|
+
if (signal) {
|
|
166
|
+
if (signal.aborted) {
|
|
167
|
+
entry.reject(new Error('MQTT request aborted before publish.'));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
abortHandler = () => {
|
|
171
|
+
entry.reject(new Error('MQTT request aborted.'));
|
|
172
|
+
};
|
|
173
|
+
signal.addEventListener('abort', abortHandler, {
|
|
174
|
+
once: true
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
void Promise.resolve().then(async () => {
|
|
178
|
+
if (this.closing) {
|
|
179
|
+
entry.reject(new Error('MQTT microservice transport closed before response.'));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const client = await this.resolveClient();
|
|
183
|
+
await this.publish(client, this.messageTopic, frame, {
|
|
184
|
+
qos: this.messageQos,
|
|
185
|
+
retain: this.messageRetain
|
|
186
|
+
});
|
|
187
|
+
}).catch(error => {
|
|
188
|
+
entry.reject(error instanceof Error ? error : new Error('Failed to publish MQTT request.'));
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Emits one fire-and-forget event through MQTT.
|
|
195
|
+
*
|
|
196
|
+
* @param pattern Pattern identifying the remote event handler.
|
|
197
|
+
* @param payload Serializable event payload.
|
|
198
|
+
* @returns A promise that resolves once the event is published.
|
|
199
|
+
*/
|
|
200
|
+
async emit(pattern, payload) {
|
|
201
|
+
if (this.closing) {
|
|
202
|
+
throw new Error('MqttMicroserviceTransport is closing. Wait for close() to complete before emit().');
|
|
203
|
+
}
|
|
204
|
+
const client = await this.resolveClient();
|
|
205
|
+
const frame = {
|
|
206
|
+
kind: mqttKinds.event,
|
|
207
|
+
pattern,
|
|
208
|
+
payload
|
|
209
|
+
};
|
|
210
|
+
await this.publish(client, this.eventTopic, frame, {
|
|
211
|
+
qos: this.eventQos,
|
|
212
|
+
retain: this.eventRetain
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Unsubscribes from MQTT topics and closes the owned client when applicable.
|
|
218
|
+
*
|
|
219
|
+
* @returns A promise that resolves once shutdown cleanup completes.
|
|
220
|
+
*/
|
|
221
|
+
async close() {
|
|
222
|
+
this.closing = true;
|
|
223
|
+
let closeError;
|
|
224
|
+
if (this.listenPromise) {
|
|
225
|
+
await this.listenPromise;
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
const client = this.client;
|
|
229
|
+
if (client && this.listening) {
|
|
230
|
+
for (const topic of [this.eventTopic, this.messageTopic, this.replyTopic]) {
|
|
231
|
+
try {
|
|
232
|
+
await this.unsubscribeTopic(client, topic);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
closeError ??= error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (client) {
|
|
239
|
+
client.off?.('message', this.messageListener);
|
|
240
|
+
if (this.internallyOwnedClient) {
|
|
241
|
+
try {
|
|
242
|
+
await this.endClient(client);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
closeError ??= error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} finally {
|
|
249
|
+
this.handler = undefined;
|
|
250
|
+
this.listening = false;
|
|
251
|
+
if (this.internallyOwnedClient) {
|
|
252
|
+
this.client = undefined;
|
|
253
|
+
}
|
|
254
|
+
for (const pending of [...this.pending.values()]) {
|
|
255
|
+
pending.reject(new Error('MQTT microservice transport closed before response.'));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (closeError) {
|
|
259
|
+
throw closeError;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async handleInbound(topic, rawMessage) {
|
|
263
|
+
const frame = parseEnvelope(rawMessage);
|
|
264
|
+
if (!frame) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (topic === this.replyTopic) {
|
|
268
|
+
this.handleInboundResponse(frame);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (!this.handler) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (topic === this.eventTopic && frame.kind === mqttKinds.event) {
|
|
275
|
+
await this.handleInboundEvent(frame);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (topic === this.messageTopic && frame.kind === mqttKinds.message && frame.requestId) {
|
|
279
|
+
await this.handleInboundRequest(frame);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async handleInboundEvent(frame) {
|
|
283
|
+
if (!this.handler) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
await this.handler({
|
|
288
|
+
kind: 'event',
|
|
289
|
+
pattern: frame.pattern,
|
|
290
|
+
payload: frame.payload
|
|
291
|
+
});
|
|
292
|
+
} catch (error) {
|
|
293
|
+
this.logEventHandlerFailure(error);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async handleInboundRequest(frame) {
|
|
297
|
+
if (!this.handler || !frame.requestId) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const client = this.client;
|
|
301
|
+
if (!client) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const replyTopic = frame.replyTopic && frame.replyTopic.length > 0 ? frame.replyTopic : this.replyTopic;
|
|
305
|
+
try {
|
|
306
|
+
const payload = await this.handler({
|
|
307
|
+
kind: 'message',
|
|
308
|
+
pattern: frame.pattern,
|
|
309
|
+
payload: frame.payload,
|
|
310
|
+
requestId: frame.requestId
|
|
311
|
+
});
|
|
312
|
+
await this.publish(client, replyTopic, {
|
|
313
|
+
kind: mqttKinds.response,
|
|
314
|
+
pattern: frame.pattern,
|
|
315
|
+
payload,
|
|
316
|
+
requestId: frame.requestId
|
|
317
|
+
}, {
|
|
318
|
+
qos: this.responseQos,
|
|
319
|
+
retain: this.responseRetain
|
|
320
|
+
});
|
|
321
|
+
} catch (error) {
|
|
322
|
+
const errorMessage = error instanceof Error ? error.message : 'Unhandled microservice error';
|
|
323
|
+
await this.publish(client, replyTopic, {
|
|
324
|
+
error: errorMessage,
|
|
325
|
+
kind: mqttKinds.response,
|
|
326
|
+
pattern: frame.pattern,
|
|
327
|
+
requestId: frame.requestId
|
|
328
|
+
}, {
|
|
329
|
+
qos: this.responseQos,
|
|
330
|
+
retain: this.responseRetain
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
handleInboundResponse(frame) {
|
|
335
|
+
if (frame.kind !== mqttKinds.response || !frame.requestId) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const pending = this.pending.get(frame.requestId);
|
|
339
|
+
if (!pending) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (frame.error) {
|
|
343
|
+
pending.reject(new Error(frame.error));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
pending.resolve(frame.payload);
|
|
347
|
+
}
|
|
348
|
+
async resolveClient() {
|
|
349
|
+
if (this.client) {
|
|
350
|
+
return this.client;
|
|
351
|
+
}
|
|
352
|
+
const mqtt = await this.resolveMqttModule();
|
|
353
|
+
const url = this.options.url;
|
|
354
|
+
if (!url || url.length === 0) {
|
|
355
|
+
throw new Error('MqttMicroserviceTransport requires options.url when options.client is not provided.');
|
|
356
|
+
}
|
|
357
|
+
this.client = mqtt.connect(url, this.options.connectOptions);
|
|
358
|
+
return this.client;
|
|
359
|
+
}
|
|
360
|
+
async resolveMqttModule() {
|
|
361
|
+
if (this.options.mqtt) {
|
|
362
|
+
return this.options.mqtt;
|
|
363
|
+
}
|
|
364
|
+
const moduleLoader = this.options.moduleLoader ?? defaultDynamicImport;
|
|
365
|
+
try {
|
|
366
|
+
const loaded = await moduleLoader('mqtt');
|
|
367
|
+
const mqtt = loaded.default ?? loaded;
|
|
368
|
+
if (!mqtt || typeof mqtt !== 'object' || typeof mqtt.connect !== 'function') {
|
|
369
|
+
throw new Error('Failed to load mqtt runtime module.');
|
|
370
|
+
}
|
|
371
|
+
return mqtt;
|
|
372
|
+
} catch (error) {
|
|
373
|
+
throw createMissingPeerDependencyError('mqtt', error);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async publish(client, topic, message, options) {
|
|
377
|
+
await new Promise((resolve, reject) => {
|
|
378
|
+
client.publish(topic, JSON.stringify(message), options, error => {
|
|
379
|
+
if (error) {
|
|
380
|
+
reject(error);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
resolve();
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
async subscribeTopic(client, topic, qos) {
|
|
388
|
+
await new Promise((resolve, reject) => {
|
|
389
|
+
client.subscribe(topic, {
|
|
390
|
+
qos
|
|
391
|
+
}, error => {
|
|
392
|
+
if (error) {
|
|
393
|
+
reject(error);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
resolve();
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
async unsubscribeTopic(client, topic) {
|
|
401
|
+
await new Promise((resolve, reject) => {
|
|
402
|
+
client.unsubscribe(topic, error => {
|
|
403
|
+
if (error) {
|
|
404
|
+
reject(error);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
resolve();
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
async endClient(client) {
|
|
412
|
+
await new Promise((resolve, reject) => {
|
|
413
|
+
client.end(false, error => {
|
|
414
|
+
if (error) {
|
|
415
|
+
reject(error);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
resolve();
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
logEventHandlerFailure(error) {
|
|
423
|
+
logTransportEventHandlerFailure(this.logger, 'MqttMicroserviceTransport', error);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function parseEnvelope(rawMessage) {
|
|
427
|
+
let parsed;
|
|
428
|
+
try {
|
|
429
|
+
parsed = JSON.parse(rawMessage);
|
|
430
|
+
} catch {
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
const candidate = parsed;
|
|
437
|
+
if (candidate.kind !== mqttKinds.event && candidate.kind !== mqttKinds.message && candidate.kind !== mqttKinds.response) {
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
440
|
+
if (typeof candidate.pattern !== 'string') {
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
if (candidate.error !== undefined && typeof candidate.error !== 'string') {
|
|
444
|
+
return undefined;
|
|
445
|
+
}
|
|
446
|
+
if (candidate.requestId !== undefined && typeof candidate.requestId !== 'string') {
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
if (candidate.replyTopic !== undefined && typeof candidate.replyTopic !== 'string') {
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
452
|
+
return candidate;
|
|
453
|
+
}
|
|
454
|
+
function createMissingPeerDependencyError(specifier, originalError) {
|
|
455
|
+
const details = originalError instanceof Error && typeof originalError.message === 'string' ? ` (${originalError.message})` : '';
|
|
456
|
+
return new Error(`Missing optional peer dependency "${specifier}" required by MqttMicroserviceTransport. Install it with "pnpm add ${specifier}" in your application.${details}`);
|
|
457
|
+
}
|
|
458
|
+
const defaultDynamicImport = async specifier => {
|
|
459
|
+
return await import(specifier);
|
|
460
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { MicroserviceTransport, MicroserviceTransportLogger, TransportHandler } from '../types.js';
|
|
2
|
+
interface NatsMessageLike {
|
|
3
|
+
readonly data: Uint8Array;
|
|
4
|
+
respond(data: Uint8Array): void;
|
|
5
|
+
}
|
|
6
|
+
interface NatsCodecLike {
|
|
7
|
+
decode(data: Uint8Array): string;
|
|
8
|
+
encode(value: string): Uint8Array;
|
|
9
|
+
}
|
|
10
|
+
interface NatsSubscriptionLike {
|
|
11
|
+
unsubscribe(): void;
|
|
12
|
+
}
|
|
13
|
+
interface NatsLike {
|
|
14
|
+
close?(): void;
|
|
15
|
+
publish(subject: string, payload: Uint8Array): void;
|
|
16
|
+
request(subject: string, payload: Uint8Array, options?: {
|
|
17
|
+
timeout?: number;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
data: Uint8Array;
|
|
20
|
+
}>;
|
|
21
|
+
subscribe(subject: string, handler: (message: NatsMessageLike) => void): NatsSubscriptionLike;
|
|
22
|
+
}
|
|
23
|
+
/** Options for configuring the NATS microservice transport. */
|
|
24
|
+
export interface NatsMicroserviceTransportOptions {
|
|
25
|
+
client: NatsLike;
|
|
26
|
+
codec: NatsCodecLike;
|
|
27
|
+
eventSubject?: string;
|
|
28
|
+
messageSubject?: string;
|
|
29
|
+
requestTimeoutMs?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* NATS transport for request-response messages and fire-and-forget event delivery.
|
|
33
|
+
*
|
|
34
|
+
* The adapter maps Fluo message traffic onto separate event and request subjects while
|
|
35
|
+
* preserving JSON framing and NATS request timeout behavior.
|
|
36
|
+
*/
|
|
37
|
+
export declare class NatsMicroserviceTransport implements MicroserviceTransport {
|
|
38
|
+
private readonly options;
|
|
39
|
+
private closing;
|
|
40
|
+
private handler;
|
|
41
|
+
private logger;
|
|
42
|
+
private listening;
|
|
43
|
+
private readonly eventSubject;
|
|
44
|
+
private readonly messageSubject;
|
|
45
|
+
private readonly pending;
|
|
46
|
+
private readonly requestTimeoutMs;
|
|
47
|
+
private subscriptions;
|
|
48
|
+
private logEventHandlerFailure;
|
|
49
|
+
private handleEventMessageSafely;
|
|
50
|
+
/**
|
|
51
|
+
* Creates a NATS transport using a client and codec supplied by the application.
|
|
52
|
+
*
|
|
53
|
+
* @param options Subject names, codec, client, and request-timeout settings.
|
|
54
|
+
*/
|
|
55
|
+
constructor(options: NatsMicroserviceTransportOptions);
|
|
56
|
+
setLogger(logger: MicroserviceTransportLogger): void;
|
|
57
|
+
/**
|
|
58
|
+
* Subscribes to the configured NATS event and message subjects.
|
|
59
|
+
*
|
|
60
|
+
* @param handler Runtime callback invoked for inbound event and message packets.
|
|
61
|
+
* @returns A promise that resolves once subscriptions are active.
|
|
62
|
+
*/
|
|
63
|
+
listen(handler: TransportHandler): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Sends one request-response message through NATS request/reply.
|
|
66
|
+
*
|
|
67
|
+
* @param pattern Pattern identifying the remote message handler.
|
|
68
|
+
* @param payload Serializable request payload.
|
|
69
|
+
* @returns The decoded remote handler response payload.
|
|
70
|
+
*/
|
|
71
|
+
send(pattern: string, payload: unknown): Promise<unknown>;
|
|
72
|
+
/**
|
|
73
|
+
* Emits one fire-and-forget event through the configured NATS event subject.
|
|
74
|
+
*
|
|
75
|
+
* @param pattern Pattern identifying the remote event handler.
|
|
76
|
+
* @param payload Serializable event payload.
|
|
77
|
+
* @returns A promise that resolves once the event is published.
|
|
78
|
+
*/
|
|
79
|
+
emit(pattern: string, payload: unknown): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Unsubscribes from NATS subjects and closes the client when supported.
|
|
82
|
+
*
|
|
83
|
+
* @returns A promise that resolves once shutdown cleanup completes.
|
|
84
|
+
*/
|
|
85
|
+
close(): Promise<void>;
|
|
86
|
+
private handleEventMessage;
|
|
87
|
+
private handleRequestMessage;
|
|
88
|
+
private decode;
|
|
89
|
+
private encode;
|
|
90
|
+
}
|
|
91
|
+
export {};
|
|
92
|
+
//# sourceMappingURL=nats-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nats-transport.d.ts","sourceRoot":"","sources":["../../src/transports/nats-transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGxG,UAAU,eAAe;IACvB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;CACjC;AAED,UAAU,aAAa;IACrB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC;IACjC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC;CACnC;AAED,UAAU,oBAAoB;IAC5B,WAAW,IAAI,IAAI,CAAC;CACrB;AAED,UAAU,QAAQ;IAChB,KAAK,CAAC,IAAI,IAAI,CAAC;IACf,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IACpD,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;IAC7G,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,GAAG,oBAAoB,CAAC;CAC/F;AAED,+DAA+D;AAC/D,MAAM,WAAW,gCAAgC;IAC/C,MAAM,EAAE,QAAQ,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAkBD;;;;;GAKG;AACH,qBAAa,yBAA0B,YAAW,qBAAqB;IA0BzD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAzBpC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,aAAa,CAA8B;IAEnD,OAAO,CAAC,sBAAsB;IAI9B,OAAO,CAAC,wBAAwB;IAMhC;;;;OAIG;gBAC0B,OAAO,EAAE,gCAAgC;IAMtE,SAAS,CAAC,MAAM,EAAE,2BAA2B,GAAG,IAAI;IAIpD;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBtD;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAmE/D;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAU5D;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YA2Bd,kBAAkB;YAsBlB,oBAAoB;IAwBlC,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,MAAM;CAGf"}
|