@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,152 @@
|
|
|
1
|
+
import { logTransportEventHandlerFailure } from './event-handler-logger.js';
|
|
2
|
+
|
|
3
|
+
/** Options for configuring the Redis Pub/Sub microservice transport. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Redis Pub/Sub transport for fire-and-forget microservice events.
|
|
7
|
+
*
|
|
8
|
+
* This adapter intentionally supports `emit()` only. Request-response flows must use
|
|
9
|
+
* a transport with durable reply semantics such as TCP, Kafka, or Redis Streams.
|
|
10
|
+
*/
|
|
11
|
+
export class RedisPubSubMicroserviceTransport {
|
|
12
|
+
handler;
|
|
13
|
+
logger;
|
|
14
|
+
listening = false;
|
|
15
|
+
listenPromise;
|
|
16
|
+
messageListener = (channel, message) => {
|
|
17
|
+
void this.handleIncoming(channel, message);
|
|
18
|
+
};
|
|
19
|
+
namespace;
|
|
20
|
+
logEventHandlerFailure(error) {
|
|
21
|
+
logTransportEventHandlerFailure(this.logger, 'RedisPubSubMicroserviceTransport', error);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates a Redis Pub/Sub transport using dedicated publish and subscribe clients.
|
|
26
|
+
*
|
|
27
|
+
* @param options Namespace and Redis client settings for the transport.
|
|
28
|
+
*/
|
|
29
|
+
constructor(options) {
|
|
30
|
+
this.options = options;
|
|
31
|
+
this.namespace = options.namespace ?? 'fluo:microservices';
|
|
32
|
+
}
|
|
33
|
+
setLogger(logger) {
|
|
34
|
+
this.logger = logger;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Subscribes to the namespaced event channel and registers the runtime handler.
|
|
39
|
+
*
|
|
40
|
+
* @param handler Runtime callback invoked for inbound event packets.
|
|
41
|
+
* @returns A promise that resolves once the Redis subscription is active.
|
|
42
|
+
*/
|
|
43
|
+
async listen(handler) {
|
|
44
|
+
this.handler = handler;
|
|
45
|
+
if (this.listening) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (this.listenPromise) {
|
|
49
|
+
await this.listenPromise;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.listenPromise = (async () => {
|
|
53
|
+
this.options.subscribeClient.on('message', this.messageListener);
|
|
54
|
+
try {
|
|
55
|
+
await this.options.subscribeClient.subscribe(this.eventChannel);
|
|
56
|
+
this.listening = true;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
this.options.subscribeClient.off?.('message', this.messageListener);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
try {
|
|
63
|
+
await this.listenPromise;
|
|
64
|
+
} finally {
|
|
65
|
+
this.listenPromise = undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Publishes one fire-and-forget event through Redis Pub/Sub.
|
|
71
|
+
*
|
|
72
|
+
* @param pattern Pattern identifying the remote event handler.
|
|
73
|
+
* @param payload Serializable payload to publish.
|
|
74
|
+
* @returns A promise that resolves once Redis accepts the publication.
|
|
75
|
+
*/
|
|
76
|
+
async emit(pattern, payload) {
|
|
77
|
+
const message = {
|
|
78
|
+
kind: 'event',
|
|
79
|
+
pattern,
|
|
80
|
+
payload
|
|
81
|
+
};
|
|
82
|
+
await this.options.publishClient.publish(this.eventChannel, JSON.stringify(message));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Rejects request-response usage for the Pub/Sub transport.
|
|
87
|
+
*
|
|
88
|
+
* @param pattern Unused request pattern.
|
|
89
|
+
* @param payload Unused request payload.
|
|
90
|
+
* @param signal Unused abort signal.
|
|
91
|
+
* @returns Never resolves successfully.
|
|
92
|
+
* @throws {Error} Always, because Pub/Sub has no reply channel contract.
|
|
93
|
+
*/
|
|
94
|
+
async send(pattern, payload, signal) {
|
|
95
|
+
void pattern;
|
|
96
|
+
void payload;
|
|
97
|
+
void signal;
|
|
98
|
+
throw new Error('RedisPubSubMicroserviceTransport does not support request/reply send(). Use emit() or a transport with durable request/reply semantics.');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Unsubscribes from the event channel and detaches the Redis message listener.
|
|
103
|
+
*
|
|
104
|
+
* @returns A promise that resolves once shutdown cleanup completes.
|
|
105
|
+
*/
|
|
106
|
+
async close() {
|
|
107
|
+
let closeError;
|
|
108
|
+
if (this.listenPromise) {
|
|
109
|
+
await this.listenPromise;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
if (this.listening) {
|
|
113
|
+
await this.options.subscribeClient.unsubscribe(this.eventChannel);
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
closeError = error;
|
|
117
|
+
} finally {
|
|
118
|
+
this.options.subscribeClient.off?.('message', this.messageListener);
|
|
119
|
+
this.listening = false;
|
|
120
|
+
this.handler = undefined;
|
|
121
|
+
}
|
|
122
|
+
if (closeError) {
|
|
123
|
+
throw closeError;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async handleIncoming(channel, rawMessage) {
|
|
127
|
+
let message;
|
|
128
|
+
try {
|
|
129
|
+
message = JSON.parse(rawMessage);
|
|
130
|
+
} catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (!this.handler) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (channel === this.eventChannel && message.kind === 'event') {
|
|
137
|
+
try {
|
|
138
|
+
await this.handler({
|
|
139
|
+
kind: 'event',
|
|
140
|
+
pattern: message.pattern,
|
|
141
|
+
payload: message.payload
|
|
142
|
+
});
|
|
143
|
+
} catch (error) {
|
|
144
|
+
this.logEventHandlerFailure(error);
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
get eventChannel() {
|
|
150
|
+
return `${this.namespace}:events`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { MicroserviceTransport, TransportHandler } from '../types.js';
|
|
2
|
+
interface TcpMicroserviceTransportOptions {
|
|
3
|
+
host?: string;
|
|
4
|
+
maxFrameBytes?: number;
|
|
5
|
+
port: number;
|
|
6
|
+
requestTimeoutMs?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Lightweight TCP transport for request-response messages and fire-and-forget events.
|
|
10
|
+
*
|
|
11
|
+
* This adapter uses newline-delimited JSON frames over raw sockets and is intended for
|
|
12
|
+
* simple first-party microservice setups where both sides can share the same framing contract.
|
|
13
|
+
*/
|
|
14
|
+
export declare class TcpMicroserviceTransport implements MicroserviceTransport {
|
|
15
|
+
private readonly options;
|
|
16
|
+
private handler;
|
|
17
|
+
private listenPromise;
|
|
18
|
+
private readonly sockets;
|
|
19
|
+
private readonly host;
|
|
20
|
+
private readonly maxFrameBytes;
|
|
21
|
+
private readonly requestTimeoutMs;
|
|
22
|
+
private readonly server;
|
|
23
|
+
/**
|
|
24
|
+
* Creates a TCP transport bound to one host/port pair.
|
|
25
|
+
*
|
|
26
|
+
* @param options TCP host, port, and request-timeout settings.
|
|
27
|
+
*/
|
|
28
|
+
constructor(options: TcpMicroserviceTransportOptions);
|
|
29
|
+
/**
|
|
30
|
+
* Starts the TCP server and registers the runtime packet handler.
|
|
31
|
+
*
|
|
32
|
+
* @param handler Runtime callback invoked for inbound event and message packets.
|
|
33
|
+
* @returns A promise that resolves once the TCP server is listening.
|
|
34
|
+
*/
|
|
35
|
+
listen(handler: TransportHandler): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Emits one fire-and-forget event over the TCP transport.
|
|
38
|
+
*
|
|
39
|
+
* @param pattern Pattern identifying the remote event handler.
|
|
40
|
+
* @param payload Serializable payload sent to the remote runtime.
|
|
41
|
+
* @returns A promise that resolves after the event frame is written.
|
|
42
|
+
*/
|
|
43
|
+
emit(pattern: string, payload: unknown): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Sends one request-response message over the TCP transport.
|
|
46
|
+
*
|
|
47
|
+
* @param pattern Pattern identifying the remote message handler.
|
|
48
|
+
* @param payload Serializable request payload.
|
|
49
|
+
* @param signal Optional abort signal used to cancel the request.
|
|
50
|
+
* @returns The remote handler response payload.
|
|
51
|
+
*/
|
|
52
|
+
send(pattern: string, payload: unknown, signal?: AbortSignal): Promise<unknown>;
|
|
53
|
+
/**
|
|
54
|
+
* Closes the TCP server and destroys any active sockets.
|
|
55
|
+
*
|
|
56
|
+
* @returns A promise that resolves once the server is fully closed.
|
|
57
|
+
*/
|
|
58
|
+
close(): Promise<void>;
|
|
59
|
+
private handleInboundPacket;
|
|
60
|
+
private sendWirePacket;
|
|
61
|
+
private bindSocketParser;
|
|
62
|
+
private writeLine;
|
|
63
|
+
private serializeFrame;
|
|
64
|
+
}
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=tcp-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tcp-transport.d.ts","sourceRoot":"","sources":["../../src/transports/tcp-transport.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAmB,MAAM,aAAa,CAAC;AAQ5F,UAAU,+BAA+B;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAID;;;;;GAKG;AACH,qBAAa,wBAAyB,YAAW,qBAAqB;IAkBxD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAjBpC,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAIpB;IAEH;;;;OAIG;gBAC0B,OAAO,EAAE,+BAA+B;IAMrE;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BtD;;;;;;OAMG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D;;;;;;;OAOG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAKrF;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YA2Bd,mBAAmB;YA8BnB,cAAc;IAmG5B,OAAO,CAAC,gBAAgB;IAyCxB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,cAAc;CASvB"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { createServer, Socket } from 'node:net';
|
|
2
|
+
const DEFAULT_MAX_FRAME_BYTES = 1_048_576;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lightweight TCP transport for request-response messages and fire-and-forget events.
|
|
6
|
+
*
|
|
7
|
+
* This adapter uses newline-delimited JSON frames over raw sockets and is intended for
|
|
8
|
+
* simple first-party microservice setups where both sides can share the same framing contract.
|
|
9
|
+
*/
|
|
10
|
+
export class TcpMicroserviceTransport {
|
|
11
|
+
handler;
|
|
12
|
+
listenPromise;
|
|
13
|
+
sockets = new Set();
|
|
14
|
+
host;
|
|
15
|
+
maxFrameBytes;
|
|
16
|
+
requestTimeoutMs;
|
|
17
|
+
server = createServer(socket => {
|
|
18
|
+
this.sockets.add(socket);
|
|
19
|
+
this.bindSocketParser(socket, async packet => this.handleInboundPacket(socket, packet));
|
|
20
|
+
socket.once('close', () => this.sockets.delete(socket));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a TCP transport bound to one host/port pair.
|
|
25
|
+
*
|
|
26
|
+
* @param options TCP host, port, and request-timeout settings.
|
|
27
|
+
*/
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this.options = options;
|
|
30
|
+
this.host = options.host ?? '127.0.0.1';
|
|
31
|
+
this.maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
|
|
32
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 3_000;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Starts the TCP server and registers the runtime packet handler.
|
|
37
|
+
*
|
|
38
|
+
* @param handler Runtime callback invoked for inbound event and message packets.
|
|
39
|
+
* @returns A promise that resolves once the TCP server is listening.
|
|
40
|
+
*/
|
|
41
|
+
async listen(handler) {
|
|
42
|
+
this.handler = handler;
|
|
43
|
+
if (this.server.listening) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (this.listenPromise) {
|
|
47
|
+
await this.listenPromise;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.listenPromise = new Promise((resolve, reject) => {
|
|
51
|
+
this.server.once('error', reject);
|
|
52
|
+
this.server.listen(this.options.port, this.host, () => {
|
|
53
|
+
this.server.off('error', reject);
|
|
54
|
+
resolve();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
await this.listenPromise;
|
|
59
|
+
} finally {
|
|
60
|
+
this.listenPromise = undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Emits one fire-and-forget event over the TCP transport.
|
|
66
|
+
*
|
|
67
|
+
* @param pattern Pattern identifying the remote event handler.
|
|
68
|
+
* @param payload Serializable payload sent to the remote runtime.
|
|
69
|
+
* @returns A promise that resolves after the event frame is written.
|
|
70
|
+
*/
|
|
71
|
+
async emit(pattern, payload) {
|
|
72
|
+
await this.sendWirePacket({
|
|
73
|
+
kind: 'event',
|
|
74
|
+
pattern,
|
|
75
|
+
payload
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Sends one request-response message over the TCP transport.
|
|
81
|
+
*
|
|
82
|
+
* @param pattern Pattern identifying the remote message handler.
|
|
83
|
+
* @param payload Serializable request payload.
|
|
84
|
+
* @param signal Optional abort signal used to cancel the request.
|
|
85
|
+
* @returns The remote handler response payload.
|
|
86
|
+
*/
|
|
87
|
+
async send(pattern, payload, signal) {
|
|
88
|
+
const requestId = randomRequestId();
|
|
89
|
+
return await this.sendWirePacket({
|
|
90
|
+
kind: 'message',
|
|
91
|
+
pattern,
|
|
92
|
+
payload,
|
|
93
|
+
requestId
|
|
94
|
+
}, signal);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Closes the TCP server and destroys any active sockets.
|
|
99
|
+
*
|
|
100
|
+
* @returns A promise that resolves once the server is fully closed.
|
|
101
|
+
*/
|
|
102
|
+
async close() {
|
|
103
|
+
if (this.listenPromise) {
|
|
104
|
+
await this.listenPromise;
|
|
105
|
+
}
|
|
106
|
+
for (const socket of this.sockets) {
|
|
107
|
+
socket.destroy();
|
|
108
|
+
}
|
|
109
|
+
this.sockets.clear();
|
|
110
|
+
if (!this.server.listening) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
await new Promise((resolve, reject) => {
|
|
114
|
+
this.server.close(error => {
|
|
115
|
+
if (error) {
|
|
116
|
+
reject(error);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
resolve();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async handleInboundPacket(socket, packet) {
|
|
124
|
+
if (!this.handler) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (packet.kind === 'event') {
|
|
128
|
+
await this.handler(packet);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const requestId = packet.requestId;
|
|
132
|
+
if (!requestId) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const payload = await this.handler(packet);
|
|
137
|
+
this.writeLine(socket, this.serializeFrame({
|
|
138
|
+
payload,
|
|
139
|
+
requestId
|
|
140
|
+
}));
|
|
141
|
+
} catch (error) {
|
|
142
|
+
const message = error instanceof Error ? error.message : 'Unhandled microservice error';
|
|
143
|
+
try {
|
|
144
|
+
this.writeLine(socket, this.serializeFrame({
|
|
145
|
+
error: message,
|
|
146
|
+
requestId
|
|
147
|
+
}));
|
|
148
|
+
} catch {
|
|
149
|
+
socket.destroy();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async sendWirePacket(packet, signal) {
|
|
154
|
+
const serializedPacket = this.serializeFrame(packet);
|
|
155
|
+
return await new Promise((resolve, reject) => {
|
|
156
|
+
const socket = new Socket();
|
|
157
|
+
let settled = false;
|
|
158
|
+
let timeoutId;
|
|
159
|
+
let onAbort;
|
|
160
|
+
const cleanup = () => {
|
|
161
|
+
if (timeoutId) {
|
|
162
|
+
clearTimeout(timeoutId);
|
|
163
|
+
}
|
|
164
|
+
if (signal && onAbort) {
|
|
165
|
+
signal.removeEventListener('abort', onAbort);
|
|
166
|
+
}
|
|
167
|
+
socket.removeAllListeners();
|
|
168
|
+
};
|
|
169
|
+
const settle = callback => {
|
|
170
|
+
if (settled) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
settled = true;
|
|
174
|
+
cleanup();
|
|
175
|
+
callback();
|
|
176
|
+
};
|
|
177
|
+
const fail = error => {
|
|
178
|
+
settle(() => {
|
|
179
|
+
socket.destroy();
|
|
180
|
+
reject(error);
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
if (signal) {
|
|
184
|
+
if (signal.aborted) {
|
|
185
|
+
fail(new Error('Microservice send aborted before dispatch.'));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
onAbort = () => {
|
|
189
|
+
fail(new Error('Microservice send aborted.'));
|
|
190
|
+
};
|
|
191
|
+
signal.addEventListener('abort', onAbort, {
|
|
192
|
+
once: true
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
timeoutId = setTimeout(() => {
|
|
196
|
+
fail(new Error(`Microservice TCP request timed out after ${String(this.requestTimeoutMs)}ms.`));
|
|
197
|
+
}, this.requestTimeoutMs);
|
|
198
|
+
if (packet.kind === 'event') {
|
|
199
|
+
socket.once('connect', () => {
|
|
200
|
+
try {
|
|
201
|
+
this.writeLine(socket, serializedPacket);
|
|
202
|
+
settle(() => {
|
|
203
|
+
socket.end();
|
|
204
|
+
resolve(undefined);
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
fail(error);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
} else {
|
|
211
|
+
this.bindSocketParser(socket, value => {
|
|
212
|
+
const response = value;
|
|
213
|
+
if (response.requestId !== packet.requestId) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (response.error) {
|
|
217
|
+
fail(new Error(response.error));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
settle(() => {
|
|
221
|
+
socket.end();
|
|
222
|
+
resolve(response.payload);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
socket.once('connect', () => {
|
|
226
|
+
try {
|
|
227
|
+
this.writeLine(socket, serializedPacket);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
fail(error);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
socket.once('error', fail);
|
|
234
|
+
socket.connect(this.options.port, this.host);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
bindSocketParser(socket, onPacket) {
|
|
238
|
+
let buffer = '';
|
|
239
|
+
let bufferBytes = 0;
|
|
240
|
+
socket.on('data', chunk => {
|
|
241
|
+
const chunkString = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
242
|
+
buffer += chunkString;
|
|
243
|
+
bufferBytes += Buffer.byteLength(chunkString, 'utf8');
|
|
244
|
+
if (bufferBytes > this.maxFrameBytes) {
|
|
245
|
+
socket.destroy();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
let newLineIndex = buffer.indexOf('\n');
|
|
249
|
+
while (newLineIndex >= 0) {
|
|
250
|
+
const rawLine = buffer.slice(0, newLineIndex);
|
|
251
|
+
const line = rawLine.trim();
|
|
252
|
+
buffer = buffer.slice(newLineIndex + 1);
|
|
253
|
+
bufferBytes = Buffer.byteLength(buffer, 'utf8');
|
|
254
|
+
if (Buffer.byteLength(rawLine, 'utf8') > this.maxFrameBytes) {
|
|
255
|
+
socket.destroy();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (line.length > 0) {
|
|
259
|
+
try {
|
|
260
|
+
void Promise.resolve(onPacket(JSON.parse(line))).catch(() => undefined);
|
|
261
|
+
} catch {
|
|
262
|
+
socket.destroy();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
newLineIndex = buffer.indexOf('\n');
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
writeLine(socket, line) {
|
|
271
|
+
socket.write(`${line}\n`);
|
|
272
|
+
}
|
|
273
|
+
serializeFrame(value) {
|
|
274
|
+
const line = JSON.stringify(value);
|
|
275
|
+
if (Buffer.byteLength(line, 'utf8') > this.maxFrameBytes) {
|
|
276
|
+
throw new Error(`Microservice TCP frame exceeded ${String(this.maxFrameBytes)} bytes.`);
|
|
277
|
+
}
|
|
278
|
+
return line;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function randomRequestId() {
|
|
282
|
+
return crypto.randomUUID();
|
|
283
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { MetadataPropertyKey, Token } from '@fluojs/core';
|
|
2
|
+
import type { Provider, Scope } from '@fluojs/di';
|
|
3
|
+
import type { ApplicationLogger } from '@fluojs/runtime';
|
|
4
|
+
/** Pattern matcher used to route messages and events to handler methods. */
|
|
5
|
+
export type Pattern = RegExp | string;
|
|
6
|
+
/** Supported microservice handler kinds discovered from decorators. */
|
|
7
|
+
export type HandlerKind = 'bidi-stream' | 'client-stream' | 'event' | 'message' | 'server-stream';
|
|
8
|
+
/** Metadata stored by one pattern decorator on a handler method. */
|
|
9
|
+
export interface HandlerMetadata {
|
|
10
|
+
kind: HandlerKind;
|
|
11
|
+
pattern: Pattern;
|
|
12
|
+
}
|
|
13
|
+
/** Runtime descriptor for one discovered microservice handler method. */
|
|
14
|
+
export interface HandlerDescriptor {
|
|
15
|
+
kind: HandlerKind;
|
|
16
|
+
methodKey: MetadataPropertyKey;
|
|
17
|
+
methodName: string;
|
|
18
|
+
moduleName: string;
|
|
19
|
+
pattern: Pattern;
|
|
20
|
+
scope: Scope;
|
|
21
|
+
targetName: string;
|
|
22
|
+
token: Token;
|
|
23
|
+
}
|
|
24
|
+
/** Transport payload delivered to the runtime dispatch layer. */
|
|
25
|
+
export interface TransportPacket {
|
|
26
|
+
kind: HandlerKind;
|
|
27
|
+
pattern: string;
|
|
28
|
+
payload: unknown;
|
|
29
|
+
requestId?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Async entrypoint used by transports to hand packets to the runtime. */
|
|
32
|
+
export type TransportHandler = (packet: TransportPacket) => Promise<unknown>;
|
|
33
|
+
/** Narrow logger seam transports use for non-fatal handler failure reporting. */
|
|
34
|
+
export interface MicroserviceTransportLogger extends Pick<ApplicationLogger, 'error'> {
|
|
35
|
+
}
|
|
36
|
+
/** Writer contract used for server-side and bidirectional streaming responses. */
|
|
37
|
+
export interface ServerStreamWriter {
|
|
38
|
+
write(data: unknown): void;
|
|
39
|
+
end(): void;
|
|
40
|
+
error(err: Error): void;
|
|
41
|
+
}
|
|
42
|
+
/** Handler signature for server-streaming routes. */
|
|
43
|
+
export type ServerStreamHandler = (payload: unknown, writer: ServerStreamWriter) => void | Promise<void>;
|
|
44
|
+
/** Transport callback signature for server-streaming listeners. */
|
|
45
|
+
export type TransportServerStreamHandler = (pattern: string, payload: unknown, writer: ServerStreamWriter) => void | Promise<void>;
|
|
46
|
+
/** Handler signature for client-streaming routes. */
|
|
47
|
+
export type ClientStreamHandler = (reader: AsyncIterable<unknown>) => Promise<unknown>;
|
|
48
|
+
/** Transport callback signature for client-streaming listeners. */
|
|
49
|
+
export type TransportClientStreamHandler = (pattern: string, reader: AsyncIterable<unknown>) => Promise<unknown>;
|
|
50
|
+
/** Handler signature for bidirectional streaming routes. */
|
|
51
|
+
export type BidiStreamHandler = (reader: AsyncIterable<unknown>, writer: ServerStreamWriter) => void | Promise<void>;
|
|
52
|
+
/** Transport callback signature for bidirectional streaming listeners. */
|
|
53
|
+
export type TransportBidiStreamHandler = (pattern: string, reader: AsyncIterable<unknown>, writer: ServerStreamWriter) => void | Promise<void>;
|
|
54
|
+
/** Transport adapter contract implemented by TCP, Redis, Kafka, gRPC, and other protocols. */
|
|
55
|
+
export interface MicroserviceTransport {
|
|
56
|
+
bidiStream?(pattern: string, signal?: AbortSignal): {
|
|
57
|
+
reader: AsyncIterable<unknown>;
|
|
58
|
+
writer: ServerStreamWriter;
|
|
59
|
+
};
|
|
60
|
+
clientStream?(pattern: string, signal?: AbortSignal): {
|
|
61
|
+
writer: ServerStreamWriter;
|
|
62
|
+
result: Promise<unknown>;
|
|
63
|
+
};
|
|
64
|
+
close(): Promise<void>;
|
|
65
|
+
emit(pattern: string, payload: unknown): Promise<void>;
|
|
66
|
+
listen(handler: TransportHandler): Promise<void>;
|
|
67
|
+
listenBidiStreaming?(handler: TransportBidiStreamHandler): void;
|
|
68
|
+
listenClientStreaming?(handler: TransportClientStreamHandler): void;
|
|
69
|
+
listenServerStreaming?(handler: TransportServerStreamHandler): void;
|
|
70
|
+
setLogger?(logger: MicroserviceTransportLogger): void;
|
|
71
|
+
send(pattern: string, payload: unknown, signal?: AbortSignal): Promise<unknown>;
|
|
72
|
+
serverStream?(pattern: string, payload: unknown, signal?: AbortSignal): AsyncIterable<unknown>;
|
|
73
|
+
}
|
|
74
|
+
/** Optional module-definition overrides for callers that want module-first custom registration. */
|
|
75
|
+
export interface MicroserviceModuleRegistrationOptions {
|
|
76
|
+
/** Extra tokens exported in addition to `MicroserviceLifecycleService` and `MICROSERVICE`. */
|
|
77
|
+
additionalExports?: Token[];
|
|
78
|
+
/** Whether the configured microservice module should register globally. Defaults to `true`. */
|
|
79
|
+
global?: boolean;
|
|
80
|
+
/** Additional providers appended after the built-in microservice runtime wiring. */
|
|
81
|
+
providers?: Provider[];
|
|
82
|
+
}
|
|
83
|
+
/** Module options accepted by {@link MicroservicesModule.forRoot}. */
|
|
84
|
+
export interface MicroserviceModuleOptions {
|
|
85
|
+
/** Optional module-definition overrides that provide a module-first alternative to raw provider-array composition. */
|
|
86
|
+
module?: MicroserviceModuleRegistrationOptions;
|
|
87
|
+
transport: MicroserviceTransport;
|
|
88
|
+
}
|
|
89
|
+
/** Programmatic microservice facade exposed through DI and compatibility tokens. */
|
|
90
|
+
export interface Microservice {
|
|
91
|
+
bidiStream?(pattern: string, signal?: AbortSignal): {
|
|
92
|
+
reader: AsyncIterable<unknown>;
|
|
93
|
+
writer: ServerStreamWriter;
|
|
94
|
+
};
|
|
95
|
+
clientStream?(pattern: string, signal?: AbortSignal): {
|
|
96
|
+
writer: ServerStreamWriter;
|
|
97
|
+
result: Promise<unknown>;
|
|
98
|
+
};
|
|
99
|
+
close(signal?: string): Promise<void>;
|
|
100
|
+
emit(pattern: string, payload: unknown): Promise<void>;
|
|
101
|
+
listen(): Promise<void>;
|
|
102
|
+
send(pattern: string, payload: unknown, signal?: AbortSignal): Promise<unknown>;
|
|
103
|
+
serverStream?(pattern: string, payload: unknown, signal?: AbortSignal): AsyncIterable<unknown>;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|