@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,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
|
+
};
|