@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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +182 -0
  3. package/README.md +179 -0
  4. package/dist/decorators.d.ts +51 -0
  5. package/dist/decorators.d.ts.map +1 -0
  6. package/dist/decorators.js +106 -0
  7. package/dist/index.d.ts +15 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +13 -0
  10. package/dist/metadata.d.ts +9 -0
  11. package/dist/metadata.d.ts.map +1 -0
  12. package/dist/metadata.js +48 -0
  13. package/dist/module.d.ts +23 -0
  14. package/dist/module.d.ts.map +1 -0
  15. package/dist/module.js +55 -0
  16. package/dist/service.d.ts +116 -0
  17. package/dist/service.d.ts.map +1 -0
  18. package/dist/service.js +550 -0
  19. package/dist/status.d.ts +30 -0
  20. package/dist/status.d.ts.map +1 -0
  21. package/dist/status.js +79 -0
  22. package/dist/tokens.d.ts +7 -0
  23. package/dist/tokens.d.ts.map +1 -0
  24. package/dist/tokens.js +4 -0
  25. package/dist/transports/event-handler-logger.d.ts +3 -0
  26. package/dist/transports/event-handler-logger.d.ts.map +1 -0
  27. package/dist/transports/event-handler-logger.js +3 -0
  28. package/dist/transports/grpc-transport.d.ts +193 -0
  29. package/dist/transports/grpc-transport.d.ts.map +1 -0
  30. package/dist/transports/grpc-transport.js +1035 -0
  31. package/dist/transports/kafka-transport.d.ts +77 -0
  32. package/dist/transports/kafka-transport.d.ts.map +1 -0
  33. package/dist/transports/kafka-transport.js +289 -0
  34. package/dist/transports/mqtt-transport.d.ts +124 -0
  35. package/dist/transports/mqtt-transport.d.ts.map +1 -0
  36. package/dist/transports/mqtt-transport.js +460 -0
  37. package/dist/transports/nats-transport.d.ts +92 -0
  38. package/dist/transports/nats-transport.d.ts.map +1 -0
  39. package/dist/transports/nats-transport.js +218 -0
  40. package/dist/transports/rabbitmq-transport.d.ts +77 -0
  41. package/dist/transports/rabbitmq-transport.d.ts.map +1 -0
  42. package/dist/transports/rabbitmq-transport.js +263 -0
  43. package/dist/transports/redis-streams-transport.d.ts +136 -0
  44. package/dist/transports/redis-streams-transport.d.ts.map +1 -0
  45. package/dist/transports/redis-streams-transport.js +482 -0
  46. package/dist/transports/redis-transport.d.ts +73 -0
  47. package/dist/transports/redis-transport.d.ts.map +1 -0
  48. package/dist/transports/redis-transport.js +152 -0
  49. package/dist/transports/tcp-transport.d.ts +66 -0
  50. package/dist/transports/tcp-transport.d.ts.map +1 -0
  51. package/dist/transports/tcp-transport.js +283 -0
  52. package/dist/types.d.ts +105 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/dist/types.js +1 -0
  55. package/package.json +105 -0
@@ -0,0 +1,1035 @@
1
+ import { logTransportEventHandlerFailure } from './event-handler-logger.js';
2
+ const DEFAULT_KIND_METADATA_KEY = 'x-fluo-kind';
3
+ const DEFAULT_MESSAGE_KIND_VALUE = 'message';
4
+ const DEFAULT_EVENT_KIND_VALUE = 'event';
5
+ const grpcKinds = {
6
+ event: 'event',
7
+ message: 'message'
8
+ };
9
+
10
+ /** Options for configuring the gRPC microservice transport. */
11
+
12
+ /**
13
+ * gRPC transport for unary, event-style unary, and all streaming microservice patterns.
14
+ *
15
+ * The adapter loads protobuf definitions at runtime, registers service implementations on a gRPC server,
16
+ * and exposes matching unary/server-stream/client-stream/bidi-stream client calls through one transport surface.
17
+ */
18
+ export class GrpcMicroserviceTransport {
19
+ bidiStreamHandler;
20
+ clientStreamHandler;
21
+ closing = false;
22
+ clients = new Map();
23
+ grpc;
24
+ handler;
25
+ logger;
26
+ listening = false;
27
+ listenPromise;
28
+ pending = new Map();
29
+ packageRoot;
30
+ requestTimeoutMs;
31
+ server;
32
+ resolvedServer;
33
+ serverStreamHandler;
34
+
35
+ /**
36
+ * Creates a gRPC transport bound to one protobuf package and server endpoint.
37
+ *
38
+ * @param options Protobuf loading, server binding, and client-call settings for the transport.
39
+ */
40
+ constructor(options) {
41
+ this.options = options;
42
+ this.requestTimeoutMs = options.requestTimeoutMs ?? 3_000;
43
+ this.server = options.server;
44
+ }
45
+ setLogger(logger) {
46
+ this.logger = logger;
47
+ }
48
+
49
+ /**
50
+ * Loads protobuf services, registers handlers, and binds the gRPC server.
51
+ *
52
+ * @param handler Runtime callback invoked for inbound unary message and event packets.
53
+ * @returns A promise that resolves once the gRPC server is bound and ready.
54
+ */
55
+ async listen(handler) {
56
+ this.closing = false;
57
+ this.handler = handler;
58
+ if (this.listening) {
59
+ return;
60
+ }
61
+ if (this.listenPromise) {
62
+ await this.listenPromise;
63
+ return;
64
+ }
65
+ this.listenPromise = (async () => {
66
+ const grpc = await this.resolveGrpc();
67
+ const protoLoader = await this.resolveProtoLoader();
68
+ const packageDefinition = protoLoader.loadSync(this.options.protoPath, this.options.loaderOptions);
69
+ const loadedDefinition = grpc.loadPackageDefinition(packageDefinition);
70
+ const packageRoot = this.resolvePackageRoot(loadedDefinition);
71
+ const targetServices = this.resolveTargetServices(packageRoot);
72
+ const server = this.server ?? new grpc.Server();
73
+ let hasRegisteredMethod = false;
74
+ try {
75
+ for (const [serviceName, serviceConstructor] of targetServices) {
76
+ const implementation = this.buildServiceImplementation(serviceName, serviceConstructor);
77
+ if (Object.keys(implementation).length === 0) {
78
+ continue;
79
+ }
80
+ hasRegisteredMethod = true;
81
+ server.addService(serviceConstructor.service, implementation);
82
+ }
83
+ if (!hasRegisteredMethod) {
84
+ throw new Error('GrpcMicroserviceTransport could not register any RPC handlers. At least one unary, server-streaming, client-streaming, or bidirectional-streaming method is required.');
85
+ }
86
+ await this.bindServer(server, grpc);
87
+ } catch (error) {
88
+ await this.shutdownServer(server);
89
+ throw error;
90
+ }
91
+ this.grpc = grpc;
92
+ this.packageRoot = packageRoot;
93
+ this.resolvedServer = server;
94
+ this.listening = true;
95
+ })();
96
+ try {
97
+ await this.listenPromise;
98
+ } finally {
99
+ this.listenPromise = undefined;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Sends one unary request-response message through gRPC.
105
+ *
106
+ * @param pattern `<Service>.<Method>` pattern identifying the remote unary RPC.
107
+ * @param payload Serializable request payload.
108
+ * @param signal Optional abort signal used to cancel the call.
109
+ * @returns The decoded unary RPC response.
110
+ */
111
+ async send(pattern, payload, signal) {
112
+ if (this.closing) {
113
+ throw new Error('GrpcMicroserviceTransport is closing. Wait for close() to complete before send().');
114
+ }
115
+ if (!this.listening) {
116
+ throw new Error('GrpcMicroserviceTransport is not listening. Call listen() before send().');
117
+ }
118
+ const parsed = parseGrpcPattern(pattern);
119
+ return await this.callUnary(parsed, payload, grpcKinds.message, signal);
120
+ }
121
+
122
+ /**
123
+ * Emits one event-style unary call through gRPC.
124
+ *
125
+ * @param pattern `<Service>.<Method>` pattern identifying the remote RPC.
126
+ * @param payload Serializable event payload.
127
+ * @returns A promise that resolves once the remote RPC acknowledges the call.
128
+ */
129
+ async emit(pattern, payload) {
130
+ if (this.closing) {
131
+ throw new Error('GrpcMicroserviceTransport is closing. Wait for close() to complete before emit().');
132
+ }
133
+ if (!this.listening) {
134
+ throw new Error('GrpcMicroserviceTransport is not listening. Call listen() before emit().');
135
+ }
136
+ const parsed = parseGrpcPattern(pattern);
137
+ await this.callUnary(parsed, payload, grpcKinds.event, undefined);
138
+ }
139
+
140
+ /**
141
+ * Registers the runtime callback used for inbound server-streaming RPCs.
142
+ *
143
+ * @param handler Runtime callback invoked for inbound server-stream requests.
144
+ */
145
+ listenServerStreaming(handler) {
146
+ this.serverStreamHandler = handler;
147
+ }
148
+
149
+ /**
150
+ * Registers the runtime callback used for inbound client-streaming RPCs.
151
+ *
152
+ * @param handler Runtime callback invoked for inbound client-stream requests.
153
+ */
154
+ listenClientStreaming(handler) {
155
+ this.clientStreamHandler = handler;
156
+ }
157
+
158
+ /**
159
+ * Registers the runtime callback used for inbound bidirectional-streaming RPCs.
160
+ *
161
+ * @param handler Runtime callback invoked for inbound bidi-stream requests.
162
+ */
163
+ listenBidiStreaming(handler) {
164
+ this.bidiStreamHandler = handler;
165
+ }
166
+
167
+ /**
168
+ * Opens an outbound server-streaming call.
169
+ *
170
+ * @param pattern `<Service>.<Method>` pattern identifying the remote server-stream RPC.
171
+ * @param payload Serializable request payload.
172
+ * @param signal Optional abort signal used to cancel the stream.
173
+ * @returns An async iterable of streamed response messages.
174
+ */
175
+ serverStream(pattern, payload, signal) {
176
+ if (this.closing) {
177
+ throw new Error('GrpcMicroserviceTransport is closing. Wait for close() to complete before serverStream().');
178
+ }
179
+ if (!this.listening) {
180
+ throw new Error('GrpcMicroserviceTransport is not listening. Call listen() before serverStream().');
181
+ }
182
+ const parsed = parseGrpcPattern(pattern);
183
+ return this.callServerStream(parsed, payload, signal);
184
+ }
185
+
186
+ /**
187
+ * Opens an outbound client-streaming call.
188
+ *
189
+ * @param pattern `<Service>.<Method>` pattern identifying the remote client-stream RPC.
190
+ * @param signal Optional abort signal used to cancel the stream.
191
+ * @returns A writer for request messages and a promise for the final response payload.
192
+ */
193
+ clientStream(pattern, signal) {
194
+ if (this.closing) {
195
+ throw new Error('GrpcMicroserviceTransport is closing. Wait for close() to complete before clientStream().');
196
+ }
197
+ if (!this.listening) {
198
+ throw new Error('GrpcMicroserviceTransport is not listening. Call listen() before clientStream().');
199
+ }
200
+ const parsed = parseGrpcPattern(pattern);
201
+ return this.callClientStream(parsed, signal);
202
+ }
203
+
204
+ /**
205
+ * Opens an outbound bidirectional-streaming call.
206
+ *
207
+ * @param pattern `<Service>.<Method>` pattern identifying the remote bidi-stream RPC.
208
+ * @param signal Optional abort signal used to cancel the stream.
209
+ * @returns A response reader paired with a request writer.
210
+ */
211
+ bidiStream(pattern, signal) {
212
+ if (this.closing) {
213
+ throw new Error('GrpcMicroserviceTransport is closing. Wait for close() to complete before bidiStream().');
214
+ }
215
+ if (!this.listening) {
216
+ throw new Error('GrpcMicroserviceTransport is not listening. Call listen() before bidiStream().');
217
+ }
218
+ const parsed = parseGrpcPattern(pattern);
219
+ return this.callBidiStream(parsed, signal);
220
+ }
221
+
222
+ /**
223
+ * Shuts down the gRPC server and closes any cached service clients.
224
+ *
225
+ * @returns A promise that resolves once shutdown cleanup completes.
226
+ */
227
+ async close() {
228
+ this.closing = true;
229
+ let closeError;
230
+ if (this.listenPromise) {
231
+ await this.listenPromise;
232
+ }
233
+ try {
234
+ if (this.resolvedServer) {
235
+ await this.shutdownServer(this.resolvedServer);
236
+ }
237
+ for (const service of this.clients.values()) {
238
+ try {
239
+ service.client.close?.();
240
+ } catch (error) {
241
+ closeError ??= error;
242
+ }
243
+ }
244
+ } catch (error) {
245
+ closeError ??= error;
246
+ } finally {
247
+ this.handler = undefined;
248
+ this.serverStreamHandler = undefined;
249
+ this.clientStreamHandler = undefined;
250
+ this.bidiStreamHandler = undefined;
251
+ this.listening = false;
252
+ this.resolvedServer = undefined;
253
+ this.clients.clear();
254
+ this.packageRoot = undefined;
255
+ for (const pending of [...this.pending.values()]) {
256
+ pending.reject(new Error('gRPC microservice transport closed before response.'));
257
+ }
258
+ }
259
+ if (closeError) {
260
+ throw closeError;
261
+ }
262
+ }
263
+ buildServiceImplementation(serviceName, serviceConstructor) {
264
+ const implementation = {};
265
+ for (const [methodName, methodDefinition] of Object.entries(serviceConstructor.service)) {
266
+ if (methodDefinition.requestStream && methodDefinition.responseStream) {
267
+ implementation[methodName] = call => {
268
+ void this.handleInboundBidiStream(serviceName, methodName, call).catch(error => {
269
+ try {
270
+ const grpcError = this.mapGrpcHandlerError(error);
271
+ const mapped = Object.assign(new Error(grpcError.message), {
272
+ code: grpcError.code
273
+ });
274
+ if (call.destroy) {
275
+ call.destroy(mapped);
276
+ } else {
277
+ call.end();
278
+ }
279
+ } catch {
280
+ call.end();
281
+ }
282
+ });
283
+ };
284
+ continue;
285
+ }
286
+ if (methodDefinition.requestStream) {
287
+ implementation[methodName] = (call, callback) => {
288
+ void this.handleInboundClientStream(serviceName, methodName, call).then(response => {
289
+ callback(null, response);
290
+ }).catch(error => {
291
+ callback(this.mapGrpcHandlerError(error));
292
+ });
293
+ };
294
+ continue;
295
+ }
296
+ if (methodDefinition.responseStream) {
297
+ implementation[methodName] = call => {
298
+ void this.handleInboundServerStream(serviceName, methodName, call).catch(error => {
299
+ try {
300
+ const grpcError = this.mapGrpcHandlerError(error);
301
+ const mapped = Object.assign(new Error(grpcError.message), {
302
+ code: grpcError.code
303
+ });
304
+ if (call.destroy) {
305
+ call.destroy(mapped);
306
+ } else {
307
+ call.end();
308
+ }
309
+ } catch {
310
+ call.end();
311
+ }
312
+ });
313
+ };
314
+ continue;
315
+ }
316
+ implementation[methodName] = (call, callback) => {
317
+ void this.handleInboundUnary(serviceName, methodName, call).then(response => {
318
+ callback(null, response);
319
+ }).catch(error => {
320
+ callback(this.mapGrpcHandlerError(error));
321
+ });
322
+ };
323
+ }
324
+ return implementation;
325
+ }
326
+ async handleInboundUnary(serviceName, methodName, call) {
327
+ const handler = this.handler;
328
+ if (!handler) {
329
+ throw this.createGrpcError(this.resolveGrpcStatusCode('UNIMPLEMENTED', 12), 'No message handler registered for pattern.');
330
+ }
331
+ const pattern = `${serviceName}.${methodName}`;
332
+ const kind = this.resolveInboundKind(call.metadata);
333
+ if (kind === grpcKinds.event) {
334
+ try {
335
+ await handler({
336
+ kind: 'event',
337
+ pattern,
338
+ payload: call.request
339
+ });
340
+ } catch (error) {
341
+ this.logEventHandlerFailure(error);
342
+ }
343
+ return {};
344
+ }
345
+ return await handler({
346
+ kind: 'message',
347
+ pattern,
348
+ payload: call.request
349
+ });
350
+ }
351
+ async handleInboundServerStream(serviceName, methodName, call) {
352
+ const handler = this.serverStreamHandler;
353
+ if (!handler) {
354
+ throw this.createGrpcError(this.resolveGrpcStatusCode('UNIMPLEMENTED', 12), 'No server-stream handler registered for pattern.');
355
+ }
356
+ const pattern = `${serviceName}.${methodName}`;
357
+ const mapHandlerError = error => {
358
+ const grpcError = this.mapGrpcHandlerError(error);
359
+ return Object.assign(new Error(grpcError.message), {
360
+ code: grpcError.code
361
+ });
362
+ };
363
+ const writer = {
364
+ write(data) {
365
+ call.write(data);
366
+ },
367
+ end() {
368
+ call.end();
369
+ },
370
+ error(err) {
371
+ const mapped = mapHandlerError(err);
372
+ if (call.destroy) {
373
+ call.destroy(mapped);
374
+ } else {
375
+ call.end();
376
+ }
377
+ }
378
+ };
379
+ await handler(pattern, call.request, writer);
380
+ }
381
+ async handleInboundClientStream(serviceName, methodName, call) {
382
+ const handler = this.clientStreamHandler;
383
+ if (!handler) {
384
+ throw this.createGrpcError(this.resolveGrpcStatusCode('UNIMPLEMENTED', 12), 'No client-stream handler registered for pattern.');
385
+ }
386
+ const pattern = `${serviceName}.${methodName}`;
387
+ const reader = grpcReadableToAsyncIterable(call);
388
+ return await handler(pattern, reader);
389
+ }
390
+ async handleInboundBidiStream(serviceName, methodName, call) {
391
+ const handler = this.bidiStreamHandler;
392
+ if (!handler) {
393
+ throw this.createGrpcError(this.resolveGrpcStatusCode('UNIMPLEMENTED', 12), 'No bidi-stream handler registered for pattern.');
394
+ }
395
+ const pattern = `${serviceName}.${methodName}`;
396
+ const reader = grpcReadableToAsyncIterable(call);
397
+ const writer = {
398
+ write(data) {
399
+ call.write(data);
400
+ },
401
+ end() {
402
+ call.end();
403
+ },
404
+ error(err) {
405
+ if (call.destroy) {
406
+ call.destroy(err);
407
+ } else {
408
+ call.end();
409
+ }
410
+ }
411
+ };
412
+ await handler(pattern, reader, writer);
413
+ }
414
+ async callUnary(parsedPattern, payload, kind, signal) {
415
+ const runtime = this.getServiceRuntime(parsedPattern.serviceName);
416
+ const method = runtime.client[parsedPattern.methodName];
417
+ if (typeof method !== 'function') {
418
+ throw new Error(`GrpcMicroserviceTransport could not resolve unary method "${parsedPattern.serviceName}.${parsedPattern.methodName}".`);
419
+ }
420
+ const requestId = crypto.randomUUID();
421
+ return await new Promise((resolve, reject) => {
422
+ let abortHandler;
423
+ let settled = false;
424
+ let timeout;
425
+ let activeCall;
426
+ const cleanup = () => {
427
+ if (timeout) {
428
+ clearTimeout(timeout);
429
+ }
430
+ if (signal && abortHandler) {
431
+ signal.removeEventListener('abort', abortHandler);
432
+ }
433
+ this.pending.delete(requestId);
434
+ };
435
+ const entry = {
436
+ resolve: value => {
437
+ if (settled) {
438
+ return;
439
+ }
440
+ settled = true;
441
+ cleanup();
442
+ resolve(value);
443
+ },
444
+ reject: error => {
445
+ if (settled) {
446
+ return;
447
+ }
448
+ settled = true;
449
+ cleanup();
450
+ reject(error);
451
+ }
452
+ };
453
+ this.pending.set(requestId, entry);
454
+ const metadata = this.createMetadata(kind);
455
+ const deadline = new Date(Date.now() + this.requestTimeoutMs);
456
+ timeout = setTimeout(() => {
457
+ entry.reject(new Error(`gRPC request timed out after ${String(this.requestTimeoutMs)}ms waiting for pattern "${parsedPattern.serviceName}.${parsedPattern.methodName}".`));
458
+ activeCall?.cancel?.();
459
+ }, this.requestTimeoutMs);
460
+ if (signal) {
461
+ if (signal.aborted) {
462
+ entry.reject(new Error('gRPC request aborted before dispatch.'));
463
+ return;
464
+ }
465
+ abortHandler = () => {
466
+ activeCall?.cancel?.();
467
+ entry.reject(new Error('gRPC request aborted.'));
468
+ };
469
+ signal.addEventListener('abort', abortHandler, {
470
+ once: true
471
+ });
472
+ }
473
+ void Promise.resolve().then(() => {
474
+ if (this.closing) {
475
+ entry.reject(new Error('gRPC microservice transport closed before response.'));
476
+ return;
477
+ }
478
+ activeCall = method.call(runtime.client, payload, metadata, {
479
+ deadline
480
+ }, (error, response) => {
481
+ if (error) {
482
+ if (signal?.aborted) {
483
+ entry.reject(new Error('gRPC request aborted.'));
484
+ return;
485
+ }
486
+ if (isGrpcStatus(error, this.resolveGrpcStatusCode('DEADLINE_EXCEEDED', 4))) {
487
+ entry.reject(new Error(`gRPC request timed out after ${String(this.requestTimeoutMs)}ms waiting for pattern "${parsedPattern.serviceName}.${parsedPattern.methodName}".`));
488
+ return;
489
+ }
490
+ const message = typeof error.message === 'string' && error.message.length > 0 ? error.message : 'Unhandled gRPC transport error';
491
+ entry.reject(new Error(message));
492
+ return;
493
+ }
494
+ entry.resolve(response);
495
+ });
496
+ }).catch(error => {
497
+ entry.reject(error instanceof Error ? error : new Error('Failed to send gRPC request.'));
498
+ });
499
+ });
500
+ }
501
+ callServerStream(parsedPattern, payload, signal) {
502
+ const runtime = this.getServiceRuntime(parsedPattern.serviceName);
503
+ const method = runtime.client[parsedPattern.methodName];
504
+ if (typeof method !== 'function') {
505
+ throw new Error(`GrpcMicroserviceTransport could not resolve server-streaming method "${parsedPattern.serviceName}.${parsedPattern.methodName}".`);
506
+ }
507
+ const metadata = this.createMetadata(grpcKinds.message);
508
+ const transport = this;
509
+ return {
510
+ [Symbol.asyncIterator]() {
511
+ const buffer = [];
512
+ let done = false;
513
+ let error;
514
+ let waiting;
515
+ let stream;
516
+ const startStream = () => {
517
+ if (stream) {
518
+ return;
519
+ }
520
+ stream = method.call(runtime.client, payload, metadata);
521
+ stream.on('data', data => {
522
+ if (waiting) {
523
+ const w = waiting;
524
+ waiting = undefined;
525
+ w.resolve({
526
+ value: data,
527
+ done: false
528
+ });
529
+ } else {
530
+ buffer.push(data);
531
+ }
532
+ });
533
+ stream.on('end', () => {
534
+ done = true;
535
+ if (waiting) {
536
+ const w = waiting;
537
+ waiting = undefined;
538
+ w.resolve({
539
+ value: undefined,
540
+ done: true
541
+ });
542
+ }
543
+ });
544
+ stream.on('error', err => {
545
+ done = true;
546
+ if (signal?.aborted) {
547
+ error = new Error('gRPC server stream aborted.');
548
+ } else {
549
+ error = err instanceof Error ? err : new Error('gRPC server stream error.');
550
+ }
551
+ if (waiting) {
552
+ const w = waiting;
553
+ waiting = undefined;
554
+ w.reject(error);
555
+ }
556
+ });
557
+ if (signal) {
558
+ if (signal.aborted) {
559
+ done = true;
560
+ error = new Error('gRPC server stream aborted.');
561
+ stream.cancel?.();
562
+ return;
563
+ }
564
+ const onAbort = () => {
565
+ done = true;
566
+ error = new Error('gRPC server stream aborted.');
567
+ stream?.cancel?.();
568
+ if (waiting) {
569
+ const w = waiting;
570
+ waiting = undefined;
571
+ w.reject(error);
572
+ }
573
+ };
574
+ signal.addEventListener('abort', onAbort, {
575
+ once: true
576
+ });
577
+ }
578
+ };
579
+ void transport;
580
+ return {
581
+ next() {
582
+ startStream();
583
+ if (buffer.length > 0) {
584
+ return Promise.resolve({
585
+ value: buffer.shift(),
586
+ done: false
587
+ });
588
+ }
589
+ if (error) {
590
+ return Promise.reject(error);
591
+ }
592
+ if (done) {
593
+ return Promise.resolve({
594
+ value: undefined,
595
+ done: true
596
+ });
597
+ }
598
+ return new Promise((resolve, reject) => {
599
+ waiting = {
600
+ resolve,
601
+ reject
602
+ };
603
+ });
604
+ },
605
+ return() {
606
+ done = true;
607
+ stream?.cancel?.();
608
+ return Promise.resolve({
609
+ value: undefined,
610
+ done: true
611
+ });
612
+ }
613
+ };
614
+ }
615
+ };
616
+ }
617
+ callClientStream(parsedPattern, signal) {
618
+ const runtime = this.getServiceRuntime(parsedPattern.serviceName);
619
+ const method = runtime.client[parsedPattern.methodName];
620
+ if (typeof method !== 'function') {
621
+ throw new Error(`GrpcMicroserviceTransport could not resolve client-streaming method "${parsedPattern.serviceName}.${parsedPattern.methodName}".`);
622
+ }
623
+ const metadata = this.createMetadata(grpcKinds.message);
624
+ let callStream;
625
+ let ended = false;
626
+ const result = new Promise((resolve, reject) => {
627
+ if (signal?.aborted) {
628
+ reject(new Error('gRPC client stream aborted before dispatch.'));
629
+ return;
630
+ }
631
+ callStream = method.call(runtime.client, metadata, (error, response) => {
632
+ if (error) {
633
+ if (signal?.aborted) {
634
+ reject(new Error('gRPC client stream aborted.'));
635
+ return;
636
+ }
637
+ const message = typeof error.message === 'string' && error.message.length > 0 ? error.message : 'Unhandled gRPC client stream error';
638
+ reject(new Error(message));
639
+ return;
640
+ }
641
+ resolve(response);
642
+ });
643
+ if (signal) {
644
+ const onAbort = () => {
645
+ if (!ended) {
646
+ ended = true;
647
+ callStream?.end();
648
+ }
649
+ reject(new Error('gRPC client stream aborted.'));
650
+ };
651
+ signal.addEventListener('abort', onAbort, {
652
+ once: true
653
+ });
654
+ }
655
+ });
656
+ const writer = {
657
+ write(data) {
658
+ if (!ended) {
659
+ callStream?.write(data);
660
+ }
661
+ },
662
+ end() {
663
+ if (!ended) {
664
+ ended = true;
665
+ callStream?.end();
666
+ }
667
+ },
668
+ error(err) {
669
+ void err;
670
+ if (!ended) {
671
+ ended = true;
672
+ callStream?.end();
673
+ }
674
+ }
675
+ };
676
+ return {
677
+ writer,
678
+ result
679
+ };
680
+ }
681
+ callBidiStream(parsedPattern, signal) {
682
+ const runtime = this.getServiceRuntime(parsedPattern.serviceName);
683
+ const method = runtime.client[parsedPattern.methodName];
684
+ if (typeof method !== 'function') {
685
+ throw new Error(`GrpcMicroserviceTransport could not resolve bidirectional-streaming method "${parsedPattern.serviceName}.${parsedPattern.methodName}".`);
686
+ }
687
+ const metadata = this.createMetadata(grpcKinds.message);
688
+ const duplexStream = method.call(runtime.client, metadata);
689
+ let writerEnded = false;
690
+ if (signal) {
691
+ if (signal.aborted) {
692
+ duplexStream.cancel?.();
693
+ } else {
694
+ const onAbort = () => {
695
+ if (!writerEnded) {
696
+ writerEnded = true;
697
+ duplexStream.end();
698
+ }
699
+ duplexStream.cancel?.();
700
+ };
701
+ signal.addEventListener('abort', onAbort, {
702
+ once: true
703
+ });
704
+ }
705
+ }
706
+ const reader = grpcReadableToAsyncIterable(duplexStream, signal);
707
+ const writer = {
708
+ write(data) {
709
+ if (!writerEnded) {
710
+ duplexStream.write(data);
711
+ }
712
+ },
713
+ end() {
714
+ if (!writerEnded) {
715
+ writerEnded = true;
716
+ duplexStream.end();
717
+ }
718
+ },
719
+ error(err) {
720
+ void err;
721
+ if (!writerEnded) {
722
+ writerEnded = true;
723
+ duplexStream.end();
724
+ }
725
+ }
726
+ };
727
+ return {
728
+ reader,
729
+ writer
730
+ };
731
+ }
732
+ getServiceRuntime(serviceName) {
733
+ const cached = this.clients.get(serviceName);
734
+ if (cached) {
735
+ return cached;
736
+ }
737
+ const grpc = this.grpc;
738
+ if (!grpc || !this.packageRoot) {
739
+ throw new Error('GrpcMicroserviceTransport is not initialized. Call listen() before send() or emit().');
740
+ }
741
+ const serviceConstructor = this.resolveServiceConstructor(this.packageRoot, serviceName);
742
+ const credentials = this.options.credentials ?? grpc.credentials.createInsecure();
743
+ const ClientConstructor = grpc.makeGenericClientConstructor(serviceConstructor.service, serviceName, {});
744
+ const client = new ClientConstructor(this.options.url, credentials, this.options.channelOptions);
745
+ const runtime = {
746
+ client,
747
+ serviceDefinition: serviceConstructor.service
748
+ };
749
+ this.clients.set(serviceName, runtime);
750
+ return runtime;
751
+ }
752
+ resolvePackageRoot(loadedDefinition) {
753
+ const packageName = this.options.packageName.trim();
754
+ if (packageName.length === 0) {
755
+ throw new Error('GrpcMicroserviceTransport requires packageName to resolve proto services.');
756
+ }
757
+ const path = packageName.split('.').filter(segment => segment.length > 0);
758
+ if (path.length === 0) {
759
+ throw new Error('GrpcMicroserviceTransport requires packageName to resolve proto services.');
760
+ }
761
+ let current = loadedDefinition;
762
+ for (const segment of path) {
763
+ if (!current || typeof current !== 'object') {
764
+ throw new Error(`GrpcMicroserviceTransport could not resolve proto package "${packageName}".`);
765
+ }
766
+ const next = current[segment];
767
+ if (!next) {
768
+ throw new Error(`GrpcMicroserviceTransport could not resolve proto package "${packageName}".`);
769
+ }
770
+ current = next;
771
+ }
772
+ if (!current || typeof current !== 'object') {
773
+ throw new Error(`GrpcMicroserviceTransport could not resolve proto package "${packageName}".`);
774
+ }
775
+ return current;
776
+ }
777
+ resolveTargetServices(packageRoot) {
778
+ const names = this.options.services && this.options.services.length > 0 ? [...this.options.services] : Object.keys(packageRoot).filter(name => this.isServiceConstructor(packageRoot[name]));
779
+ if (names.length === 0) {
780
+ throw new Error(`GrpcMicroserviceTransport found no services under proto package "${this.options.packageName}".`);
781
+ }
782
+ return names.map(name => [name, this.resolveServiceConstructor(packageRoot, name)]);
783
+ }
784
+ resolveServiceConstructor(packageRoot, serviceName) {
785
+ const candidate = packageRoot[serviceName];
786
+ if (!this.isServiceConstructor(candidate)) {
787
+ throw new Error(`GrpcMicroserviceTransport could not resolve service "${serviceName}" in package "${this.options.packageName}".`);
788
+ }
789
+ return candidate;
790
+ }
791
+ isServiceConstructor(value) {
792
+ if (!value || typeof value !== 'function') {
793
+ return false;
794
+ }
795
+ const service = value.service;
796
+ return !!service && typeof service === 'object';
797
+ }
798
+ async bindServer(server, grpc) {
799
+ const credentials = this.options.credentials ?? grpc.credentials.createInsecure();
800
+ await new Promise((resolve, reject) => {
801
+ server.bindAsync(this.options.url, credentials, error => {
802
+ if (error) {
803
+ reject(error);
804
+ return;
805
+ }
806
+ resolve();
807
+ });
808
+ });
809
+ server.start();
810
+ }
811
+ async shutdownServer(server) {
812
+ if (server.tryShutdown) {
813
+ await new Promise((resolve, reject) => {
814
+ server.tryShutdown?.(error => {
815
+ if (error) {
816
+ reject(error);
817
+ return;
818
+ }
819
+ resolve();
820
+ });
821
+ });
822
+ return;
823
+ }
824
+ server.forceShutdown?.();
825
+ }
826
+ async resolveGrpc() {
827
+ if (this.options.grpc) {
828
+ return this.options.grpc;
829
+ }
830
+ const loaded = await this.loadPeerModule('@grpc/grpc-js');
831
+ const grpc = loaded.default ?? loaded;
832
+ if (!grpc || typeof grpc !== 'object' || typeof grpc.loadPackageDefinition !== 'function') {
833
+ throw new Error('Failed to load @grpc/grpc-js runtime module.');
834
+ }
835
+ return grpc;
836
+ }
837
+ async resolveProtoLoader() {
838
+ if (this.options.protoLoader) {
839
+ return this.options.protoLoader;
840
+ }
841
+ const loaded = await this.loadPeerModule('@grpc/proto-loader');
842
+ const protoLoader = loaded.default ?? loaded;
843
+ if (!protoLoader || typeof protoLoader !== 'object' || typeof protoLoader.loadSync !== 'function') {
844
+ throw new Error('Failed to load @grpc/proto-loader runtime module.');
845
+ }
846
+ return protoLoader;
847
+ }
848
+ async loadPeerModule(specifier) {
849
+ const moduleLoader = this.options.moduleLoader ?? defaultDynamicImport;
850
+ try {
851
+ return await moduleLoader(specifier);
852
+ } catch (error) {
853
+ throw createMissingPeerDependencyError(specifier, error);
854
+ }
855
+ }
856
+ createMetadata(kind) {
857
+ const grpc = this.grpc;
858
+ if (!grpc) {
859
+ throw new Error('GrpcMicroserviceTransport is not initialized. Call listen() before send() or emit().');
860
+ }
861
+ const metadata = new grpc.Metadata();
862
+ metadata.set(this.options.kindMetadataKey ?? DEFAULT_KIND_METADATA_KEY, this.kindMetadataValue(kind));
863
+ return metadata;
864
+ }
865
+ resolveInboundKind(metadata) {
866
+ if (!metadata || typeof metadata.get !== 'function') {
867
+ return grpcKinds.message;
868
+ }
869
+ const values = metadata.get(this.options.kindMetadataKey ?? DEFAULT_KIND_METADATA_KEY);
870
+ const first = Array.isArray(values) ? values[0] : values;
871
+ const value = typeof first === 'string' ? first : String(first ?? '');
872
+ if (value === (this.options.eventKindMetadataValue ?? DEFAULT_EVENT_KIND_VALUE)) {
873
+ return grpcKinds.event;
874
+ }
875
+ return grpcKinds.message;
876
+ }
877
+ kindMetadataValue(kind) {
878
+ if (kind === grpcKinds.event) {
879
+ return this.options.eventKindMetadataValue ?? DEFAULT_EVENT_KIND_VALUE;
880
+ }
881
+ return this.options.messageKindMetadataValue ?? DEFAULT_MESSAGE_KIND_VALUE;
882
+ }
883
+ mapGrpcHandlerError(error) {
884
+ if (error && typeof error === 'object' && 'code' in error && 'message' in error) {
885
+ const candidate = error;
886
+ const message = typeof candidate.message === 'string' && candidate.message.length > 0 ? candidate.message : 'Unhandled microservice error';
887
+ return {
888
+ code: typeof candidate.code === 'number' ? candidate.code : this.resolveGrpcStatusCode('INTERNAL', 13),
889
+ message
890
+ };
891
+ }
892
+ const message = error instanceof Error ? error.message : 'Unhandled microservice error';
893
+ if (message.includes('No message handler registered for pattern') || message.includes('No server-stream handler registered for pattern')) {
894
+ return this.createGrpcError(this.resolveGrpcStatusCode('UNIMPLEMENTED', 12), message);
895
+ }
896
+ if (message.includes('Invalid gRPC pattern')) {
897
+ return this.createGrpcError(this.resolveGrpcStatusCode('INVALID_ARGUMENT', 3), message);
898
+ }
899
+ return this.createGrpcError(this.resolveGrpcStatusCode('INTERNAL', 13), message);
900
+ }
901
+ createGrpcError(code, message) {
902
+ return {
903
+ code,
904
+ message
905
+ };
906
+ }
907
+ resolveGrpcStatusCode(name, fallback) {
908
+ const status = this.grpc?.status;
909
+ const code = status?.[name];
910
+ return typeof code === 'number' ? code : fallback;
911
+ }
912
+ logEventHandlerFailure(error) {
913
+ logTransportEventHandlerFailure(this.logger, 'GrpcMicroserviceTransport', error);
914
+ }
915
+ }
916
+ function grpcReadableToAsyncIterable(stream, signal) {
917
+ return {
918
+ [Symbol.asyncIterator]() {
919
+ const buffer = [];
920
+ let done = false;
921
+ let error;
922
+ let waiting;
923
+ stream.on('data', data => {
924
+ if (waiting) {
925
+ const w = waiting;
926
+ waiting = undefined;
927
+ w.resolve({
928
+ value: data,
929
+ done: false
930
+ });
931
+ } else {
932
+ buffer.push(data);
933
+ }
934
+ });
935
+ stream.on('end', () => {
936
+ done = true;
937
+ if (waiting) {
938
+ const w = waiting;
939
+ waiting = undefined;
940
+ w.resolve({
941
+ value: undefined,
942
+ done: true
943
+ });
944
+ }
945
+ });
946
+ stream.on('error', err => {
947
+ done = true;
948
+ if (signal?.aborted) {
949
+ error = new Error('gRPC stream aborted.');
950
+ } else {
951
+ error = err instanceof Error ? err : new Error('gRPC stream error.');
952
+ }
953
+ if (waiting) {
954
+ const w = waiting;
955
+ waiting = undefined;
956
+ w.reject(error);
957
+ }
958
+ });
959
+ if (signal) {
960
+ if (signal.aborted) {
961
+ done = true;
962
+ error = new Error('gRPC stream aborted.');
963
+ stream.cancel?.();
964
+ } else {
965
+ const onAbort = () => {
966
+ done = true;
967
+ error = new Error('gRPC stream aborted.');
968
+ stream.cancel?.();
969
+ if (waiting) {
970
+ const w = waiting;
971
+ waiting = undefined;
972
+ w.reject(error);
973
+ }
974
+ };
975
+ signal.addEventListener('abort', onAbort, {
976
+ once: true
977
+ });
978
+ }
979
+ }
980
+ return {
981
+ next() {
982
+ if (buffer.length > 0) {
983
+ return Promise.resolve({
984
+ value: buffer.shift(),
985
+ done: false
986
+ });
987
+ }
988
+ if (error) {
989
+ return Promise.reject(error);
990
+ }
991
+ if (done) {
992
+ return Promise.resolve({
993
+ value: undefined,
994
+ done: true
995
+ });
996
+ }
997
+ return new Promise((resolve, reject) => {
998
+ waiting = {
999
+ resolve,
1000
+ reject
1001
+ };
1002
+ });
1003
+ },
1004
+ return() {
1005
+ done = true;
1006
+ stream.cancel?.();
1007
+ return Promise.resolve({
1008
+ value: undefined,
1009
+ done: true
1010
+ });
1011
+ }
1012
+ };
1013
+ }
1014
+ };
1015
+ }
1016
+ function parseGrpcPattern(pattern) {
1017
+ const segments = pattern.split('.');
1018
+ if (segments.length !== 2 || segments[0]?.length === 0 || segments[1]?.length === 0) {
1019
+ throw new Error(`Invalid gRPC pattern "${pattern}". Expected "<Service>.<Method>" matching proto service and method names.`);
1020
+ }
1021
+ return {
1022
+ serviceName: segments[0],
1023
+ methodName: segments[1]
1024
+ };
1025
+ }
1026
+ function isGrpcStatus(error, code) {
1027
+ return typeof error?.code === 'number' && error.code === code;
1028
+ }
1029
+ function createMissingPeerDependencyError(specifier, originalError) {
1030
+ const details = originalError instanceof Error && typeof originalError.message === 'string' ? ` (${originalError.message})` : '';
1031
+ return new Error(`Missing optional peer dependency "${specifier}" required by GrpcMicroserviceTransport. Install it with "pnpm add ${specifier}" in your application.${details}`);
1032
+ }
1033
+ const defaultDynamicImport = async specifier => {
1034
+ return await import(specifier);
1035
+ };