@olane/o-node 0.7.12-alpha.9 → 0.7.13-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts +1 -0
- package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts.map +1 -1
- package/dist/src/connection/interfaces/o-node-connection.config.d.ts +1 -0
- package/dist/src/connection/interfaces/o-node-connection.config.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.d.ts +7 -2
- package/dist/src/connection/o-node-connection.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.js +56 -32
- package/dist/src/connection/o-node-connection.manager.d.ts +20 -5
- package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.manager.js +95 -65
- package/dist/src/connection/o-stream.request.d.ts +11 -0
- package/dist/src/connection/o-stream.request.d.ts.map +1 -0
- package/dist/src/connection/o-stream.request.js +7 -0
- package/dist/src/connection/stream-handler.config.d.ts +46 -0
- package/dist/src/connection/stream-handler.config.d.ts.map +1 -0
- package/dist/src/connection/stream-handler.config.js +1 -0
- package/dist/src/connection/stream-handler.d.ts +98 -0
- package/dist/src/connection/stream-handler.d.ts.map +1 -0
- package/dist/src/connection/stream-handler.js +310 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/interfaces/i-heartbeatable-node.d.ts +49 -0
- package/dist/src/interfaces/i-heartbeatable-node.d.ts.map +1 -0
- package/dist/src/interfaces/i-heartbeatable-node.js +1 -0
- package/dist/src/interfaces/i-reconnectable-node.d.ts +5 -0
- package/dist/src/interfaces/i-reconnectable-node.d.ts.map +1 -1
- package/dist/src/interfaces/o-node.config.d.ts +16 -22
- package/dist/src/interfaces/o-node.config.d.ts.map +1 -1
- package/dist/src/managers/o-connection-heartbeat.manager.d.ts +17 -17
- package/dist/src/managers/o-connection-heartbeat.manager.d.ts.map +1 -1
- package/dist/src/managers/o-connection-heartbeat.manager.js +83 -78
- package/dist/src/managers/o-reconnection.manager.d.ts +12 -0
- package/dist/src/managers/o-reconnection.manager.d.ts.map +1 -1
- package/dist/src/managers/o-reconnection.manager.js +138 -22
- package/dist/src/o-node.d.ts +23 -12
- package/dist/src/o-node.d.ts.map +1 -1
- package/dist/src/o-node.js +227 -72
- package/dist/src/o-node.notification-manager.d.ts.map +1 -1
- package/dist/src/o-node.notification-manager.js +17 -12
- package/dist/src/o-node.tool.d.ts +1 -0
- package/dist/src/o-node.tool.d.ts.map +1 -1
- package/dist/src/o-node.tool.js +18 -34
- package/dist/src/router/o-node.router.d.ts +1 -0
- package/dist/src/router/o-node.router.d.ts.map +1 -1
- package/dist/src/router/o-node.router.js +62 -9
- package/dist/src/router/o-node.routing-policy.d.ts.map +1 -1
- package/dist/src/router/o-node.routing-policy.js +7 -2
- package/dist/src/router/resolvers/o-node.resolver.d.ts.map +1 -1
- package/dist/src/router/resolvers/o-node.resolver.js +5 -1
- package/dist/src/router/resolvers/o-node.search-resolver.d.ts.map +1 -1
- package/dist/src/router/resolvers/o-node.search-resolver.js +34 -9
- package/dist/src/utils/index.d.ts +3 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +2 -0
- package/dist/src/utils/stream.utils.d.ts +6 -0
- package/dist/src/utils/stream.utils.d.ts.map +1 -0
- package/dist/src/utils/stream.utils.js +31 -0
- package/dist/test/helpers/test-node.tool.d.ts +15 -0
- package/dist/test/helpers/test-node.tool.d.ts.map +1 -0
- package/dist/test/helpers/test-node.tool.js +27 -0
- package/package.json +6 -6
- package/dist/src/router/resolvers/o-node.child-resolver.d.ts +0 -11
- package/dist/src/router/resolvers/o-node.child-resolver.d.ts.map +0 -1
- package/dist/src/router/resolvers/o-node.child-resolver.js +0 -58
- package/dist/src/utils/leader-request-wrapper.d.ts +0 -45
- package/dist/src/utils/leader-request-wrapper.d.ts.map +0 -1
- package/dist/src/utils/leader-request-wrapper.js +0 -89
- package/dist/test/o-node.spec.d.ts +0 -2
- package/dist/test/o-node.spec.d.ts.map +0 -1
- package/dist/test/o-node.spec.js +0 -20
- package/dist/test/search-resolver.spec.d.ts +0 -2
- package/dist/test/search-resolver.spec.d.ts.map +0 -1
- package/dist/test/search-resolver.spec.js +0 -693
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { oRequest, CoreUtils, oError, oErrorCodes, Logger, ResponseBuilder, } from '@olane/o-core';
|
|
2
|
+
/**
|
|
3
|
+
* StreamHandler centralizes all stream-related functionality including:
|
|
4
|
+
* - Message type detection (request vs response)
|
|
5
|
+
* - Stream lifecycle management (create, reuse, close)
|
|
6
|
+
* - Backpressure handling
|
|
7
|
+
* - Request/response handling
|
|
8
|
+
* - Stream routing for middleware nodes
|
|
9
|
+
*/
|
|
10
|
+
export class StreamHandler {
|
|
11
|
+
constructor(logger) {
|
|
12
|
+
this.logger = logger ?? new Logger('StreamHandler');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Detects if a decoded message is a request
|
|
16
|
+
* Requests have a 'method' field and no 'result' field
|
|
17
|
+
*/
|
|
18
|
+
isRequest(message) {
|
|
19
|
+
return typeof message?.method === 'string' && message.result === undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Detects if a decoded message is a response
|
|
23
|
+
* Responses have a 'result' field and no 'method' field
|
|
24
|
+
*/
|
|
25
|
+
isResponse(message) {
|
|
26
|
+
return message?.result !== undefined && message.method === undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Decodes a stream message event into a JSON object
|
|
30
|
+
*/
|
|
31
|
+
async decodeMessage(event) {
|
|
32
|
+
return CoreUtils.processStream(event);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Gets an existing open stream or creates a new one based on reuse policy
|
|
36
|
+
*
|
|
37
|
+
* @param connection - The libp2p connection
|
|
38
|
+
* @param protocol - The protocol to use for the stream
|
|
39
|
+
* @param config - Stream handler configuration
|
|
40
|
+
*/
|
|
41
|
+
async getOrCreateStream(connection, protocol, config = {}) {
|
|
42
|
+
if (connection.status !== 'open') {
|
|
43
|
+
throw new oError(oErrorCodes.INVALID_STATE, 'Connection not open');
|
|
44
|
+
}
|
|
45
|
+
const reusePolicy = config.reusePolicy ?? 'none';
|
|
46
|
+
this.logger.debug('Reuse policy:', reusePolicy);
|
|
47
|
+
// Check for existing stream if reuse is enabled
|
|
48
|
+
if (reusePolicy === 'reuse') {
|
|
49
|
+
const existingStream = connection.streams.find((stream) => stream.status === 'open' &&
|
|
50
|
+
stream.protocol === protocol &&
|
|
51
|
+
stream.writeStatus === 'writable' &&
|
|
52
|
+
stream.remoteReadStatus === 'readable');
|
|
53
|
+
if (existingStream) {
|
|
54
|
+
this.logger.debug('Reusing existing stream', existingStream.id, existingStream.direction);
|
|
55
|
+
return existingStream;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Create new stream
|
|
59
|
+
this.logger.debug('Creating new stream');
|
|
60
|
+
const stream = await connection.newStream(protocol, {
|
|
61
|
+
signal: config.signal,
|
|
62
|
+
maxOutboundStreams: config.maxOutboundStreams ?? 1000,
|
|
63
|
+
runOnLimitedConnection: config.runOnLimitedConnection ?? false,
|
|
64
|
+
});
|
|
65
|
+
return stream;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Sends data through a stream with backpressure handling
|
|
69
|
+
*
|
|
70
|
+
* @param stream - The stream to send data through
|
|
71
|
+
* @param data - The data to send
|
|
72
|
+
* @param config - Configuration for timeout and other options
|
|
73
|
+
*/
|
|
74
|
+
async send(stream, data, config = {}) {
|
|
75
|
+
// Send the data with backpressure handling (libp2p v3 best practice)
|
|
76
|
+
const sent = stream.send(data);
|
|
77
|
+
// If send() returns false, buffer is full - wait for drain
|
|
78
|
+
if (!sent) {
|
|
79
|
+
this.logger.debug('Stream buffer full, waiting for drain...');
|
|
80
|
+
const drainTimeout = config.drainTimeoutMs ?? 30000;
|
|
81
|
+
await stream.onDrain({
|
|
82
|
+
signal: AbortSignal.timeout(drainTimeout),
|
|
83
|
+
});
|
|
84
|
+
this.logger.debug('Stream drained successfully');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Closes a stream safely with error handling
|
|
89
|
+
*
|
|
90
|
+
* @param stream - The stream to close
|
|
91
|
+
* @param config - Configuration including reuse policy
|
|
92
|
+
*/
|
|
93
|
+
async close(stream, config = {}) {
|
|
94
|
+
// Don't close if reuse policy is enabled
|
|
95
|
+
if (config.reusePolicy === 'reuse') {
|
|
96
|
+
this.logger.debug('Stream reuse enabled, not closing stream');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (stream.status === 'open') {
|
|
100
|
+
try {
|
|
101
|
+
// force the close for now until we can implement a proper close
|
|
102
|
+
await stream.abort(new Error('Stream closed'));
|
|
103
|
+
this.logger.debug('Stream closed successfully');
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
this.logger.debug('Error closing stream:', error.message);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Handles an incoming stream on the server side
|
|
112
|
+
* Attaches message listener immediately (libp2p v3 best practice)
|
|
113
|
+
* Routes requests or executes tools based on the message
|
|
114
|
+
*
|
|
115
|
+
* @param stream - The incoming stream
|
|
116
|
+
* @param connection - The connection the stream belongs to
|
|
117
|
+
* @param toolExecutor - Function to execute tools for requests
|
|
118
|
+
*/
|
|
119
|
+
async handleIncomingStream(stream, connection, toolExecutor) {
|
|
120
|
+
// CRITICAL: Attach message listener immediately to prevent buffer overflow (libp2p v3)
|
|
121
|
+
const messageHandler = async (event) => {
|
|
122
|
+
try {
|
|
123
|
+
// avoid processing non-olane messages
|
|
124
|
+
if (!event.data) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const message = await this.decodeMessage(event);
|
|
128
|
+
if (typeof message === 'string') {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (this.isRequest(message)) {
|
|
132
|
+
await this.handleRequestMessage(message, stream, toolExecutor);
|
|
133
|
+
}
|
|
134
|
+
else if (this.isResponse(message)) {
|
|
135
|
+
this.logger.warn('Received response message on server-side stream, ignoring', message);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
this.logger.warn('Received unknown message type', message);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
this.logger.error('Error handling stream message:', error);
|
|
143
|
+
// Error already logged, stream will be closed by remote peer or timeout
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const closeHandler = () => {
|
|
147
|
+
this.logger.debug('Stream closed by remote peer');
|
|
148
|
+
stream.removeEventListener('message', messageHandler);
|
|
149
|
+
stream.removeEventListener('close', closeHandler);
|
|
150
|
+
};
|
|
151
|
+
stream.addEventListener('message', messageHandler);
|
|
152
|
+
stream.addEventListener('close', closeHandler);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Handles a request message by executing the tool and sending response
|
|
156
|
+
*
|
|
157
|
+
* @param message - The decoded request message
|
|
158
|
+
* @param stream - The stream to send the response on
|
|
159
|
+
* @param toolExecutor - Function to execute the tool
|
|
160
|
+
*/
|
|
161
|
+
async handleRequestMessage(message, stream, toolExecutor) {
|
|
162
|
+
const request = new oRequest(message);
|
|
163
|
+
const responseBuilder = ResponseBuilder.create();
|
|
164
|
+
try {
|
|
165
|
+
this.logger.debug(`Processing request on stream: method=${request.method}, id=${request.id}`);
|
|
166
|
+
const result = await toolExecutor(request, stream);
|
|
167
|
+
const response = await responseBuilder.build(request, result, null);
|
|
168
|
+
await CoreUtils.sendResponse(response, stream);
|
|
169
|
+
this.logger.debug(`Successfully processed request: method=${request.method}, id=${request.id}`);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
this.logger.error(`Error processing request: method=${request.method}, id=${request.id}`, error);
|
|
173
|
+
const errorResponse = await responseBuilder.buildError(request, error);
|
|
174
|
+
await CoreUtils.sendResponse(errorResponse, stream);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Handles an outgoing stream on the client side
|
|
179
|
+
* Listens for response messages and emits them via the event emitter
|
|
180
|
+
* If requestHandler is provided, also processes incoming router requests
|
|
181
|
+
*
|
|
182
|
+
* @param stream - The outgoing stream
|
|
183
|
+
* @param emitter - Event emitter for chunk events
|
|
184
|
+
* @param config - Configuration including abort signal
|
|
185
|
+
* @param requestHandler - Optional handler for processing router requests received on this stream
|
|
186
|
+
* @param requestId - Optional request ID to filter responses (for stream reuse scenarios)
|
|
187
|
+
* @returns Promise that resolves with the final response
|
|
188
|
+
*/
|
|
189
|
+
async handleOutgoingStream(stream, emitter, config = {}, requestHandler, requestId) {
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
let lastResponse;
|
|
192
|
+
const messageHandler = async (event) => {
|
|
193
|
+
// avoid processing non-olane messages
|
|
194
|
+
if (!event.data) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const message = await this.decodeMessage(event);
|
|
199
|
+
if (typeof message === 'string') {
|
|
200
|
+
// this.logger.warn(
|
|
201
|
+
// 'Received string message on server-side stream, ignoring',
|
|
202
|
+
// message,
|
|
203
|
+
// );
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (this.isResponse(message)) {
|
|
207
|
+
const response = await CoreUtils.processStreamResponse(event);
|
|
208
|
+
// If requestId is provided, filter responses to only process those matching our request
|
|
209
|
+
// This prevents premature termination when multiple requests share the same stream
|
|
210
|
+
if (requestId !== undefined && response.id !== requestId) {
|
|
211
|
+
this.logger.debug(`Ignoring response for different request (expected: ${requestId}, received: ${response.id})`);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// Emit chunk for streaming responses
|
|
215
|
+
emitter.emit('chunk', response);
|
|
216
|
+
// Check if this is the last chunk for THIS request
|
|
217
|
+
if (response.result._last || !response.result._isStreaming) {
|
|
218
|
+
lastResponse = response;
|
|
219
|
+
cleanup();
|
|
220
|
+
resolve(response);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else if (this.isRequest(message)) {
|
|
224
|
+
// Process incoming router requests if handler is provided
|
|
225
|
+
if (requestHandler) {
|
|
226
|
+
this.logger.debug('Received router request on client-side stream, processing...', message);
|
|
227
|
+
await this.handleRequestMessage(message, stream, requestHandler);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
this.logger.warn('Received request message on client-side stream, ignoring (no handler)', message);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
this.logger.warn('Received unknown message type', message);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
this.logger.error('Error handling response message:', error);
|
|
239
|
+
cleanup();
|
|
240
|
+
reject(error);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
const closeHandler = () => {
|
|
244
|
+
this.logger.debug('Stream closed by remote peer');
|
|
245
|
+
cleanup();
|
|
246
|
+
if (lastResponse) {
|
|
247
|
+
resolve(lastResponse);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
reject(new oError(oErrorCodes.TIMEOUT, 'Stream closed before response received'));
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const abortHandler = () => {
|
|
254
|
+
this.logger.debug('Request aborted');
|
|
255
|
+
cleanup();
|
|
256
|
+
try {
|
|
257
|
+
stream.abort(new Error('Request aborted'));
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
this.logger.debug('Error aborting stream:', error.message);
|
|
261
|
+
}
|
|
262
|
+
reject(new oError(oErrorCodes.TIMEOUT, 'Request aborted'));
|
|
263
|
+
};
|
|
264
|
+
const cleanup = () => {
|
|
265
|
+
stream.removeEventListener('message', messageHandler);
|
|
266
|
+
stream.removeEventListener('close', closeHandler);
|
|
267
|
+
if (config.signal) {
|
|
268
|
+
config.signal.removeEventListener('abort', abortHandler);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
stream.addEventListener('message', messageHandler);
|
|
272
|
+
stream.addEventListener('close', closeHandler);
|
|
273
|
+
if (config.signal) {
|
|
274
|
+
config.signal.addEventListener('abort', abortHandler);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Forwards a request to the next hop and relays response chunks back
|
|
280
|
+
* This implements the middleware/proxy pattern for intermediate nodes
|
|
281
|
+
*
|
|
282
|
+
* @param request - The router request to forward
|
|
283
|
+
* @param incomingStream - The stream to send responses back on
|
|
284
|
+
* @param dialFn - Function to dial the next hop connection
|
|
285
|
+
*/
|
|
286
|
+
async forwardRequest(request, incomingStream, dialFn) {
|
|
287
|
+
try {
|
|
288
|
+
// Connect to next hop
|
|
289
|
+
const nextHopConnection = await dialFn(request.params.address);
|
|
290
|
+
// Set up chunk relay - forward responses from next hop back to incoming stream
|
|
291
|
+
nextHopConnection.onChunk(async (response) => {
|
|
292
|
+
try {
|
|
293
|
+
await CoreUtils.sendStreamResponse(response, incomingStream);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
this.logger.error('Error forwarding chunk:', error);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
// Transmit the request to next hop
|
|
300
|
+
await nextHopConnection.transmit(request);
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
this.logger.error('Error forwarding request:', error);
|
|
304
|
+
// Send error response back on incoming stream using ResponseBuilder
|
|
305
|
+
const responseBuilder = ResponseBuilder.create();
|
|
306
|
+
const errorResponse = await responseBuilder.buildError(request, error);
|
|
307
|
+
await CoreUtils.sendResponse(errorResponse, incomingStream);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './o-node.js';
|
|
2
|
-
export * from './utils/
|
|
2
|
+
export * from './utils/index.js';
|
|
3
3
|
export * from './o-node.hierarchy-manager.js';
|
|
4
4
|
export * from './interfaces/o-node.config.js';
|
|
5
5
|
export * from './connection/index.js';
|
|
@@ -7,4 +7,5 @@ export * from './o-node.tool.js';
|
|
|
7
7
|
export * from './nodes/index.js';
|
|
8
8
|
export * from './interfaces/o-node.tool-config.js';
|
|
9
9
|
export * from './router/index.js';
|
|
10
|
+
export * from './connection/o-stream.request.js';
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oCAAoC,CAAC;AACnD,cAAc,mBAAmB,CAAC;AAClC,cAAc,kCAAkC,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './o-node.js';
|
|
2
|
-
export * from './utils/
|
|
2
|
+
export * from './utils/index.js';
|
|
3
3
|
export * from './o-node.hierarchy-manager.js';
|
|
4
4
|
export * from './interfaces/o-node.config.js';
|
|
5
5
|
export * from './connection/index.js';
|
|
@@ -7,3 +7,4 @@ export * from './o-node.tool.js';
|
|
|
7
7
|
export * from './nodes/index.js';
|
|
8
8
|
export * from './interfaces/o-node.tool-config.js';
|
|
9
9
|
export * from './router/index.js';
|
|
10
|
+
export * from './connection/o-stream.request.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { oNotificationManager } from '@olane/o-core';
|
|
2
|
+
import { Libp2p } from '@olane/o-config';
|
|
3
|
+
import { oNodeAddress } from '../router/o-node.address.js';
|
|
4
|
+
/**
|
|
5
|
+
* Interface for nodes that support connection heartbeat monitoring.
|
|
6
|
+
* This interface defines the contract that oConnectionHeartbeatManager needs
|
|
7
|
+
* to access live hierarchy state without holding stale references.
|
|
8
|
+
*/
|
|
9
|
+
export interface IHeartbeatableNode {
|
|
10
|
+
/**
|
|
11
|
+
* The node's current address
|
|
12
|
+
*/
|
|
13
|
+
address: oNodeAddress;
|
|
14
|
+
/**
|
|
15
|
+
* The notification manager for emitting heartbeat events
|
|
16
|
+
*/
|
|
17
|
+
notificationManager: oNotificationManager;
|
|
18
|
+
/**
|
|
19
|
+
* The underlying libp2p node for ping operations
|
|
20
|
+
*/
|
|
21
|
+
p2pNode: Libp2p;
|
|
22
|
+
/**
|
|
23
|
+
* The current parent address (with live transport updates)
|
|
24
|
+
* @returns Parent address or null if no parent
|
|
25
|
+
*/
|
|
26
|
+
parent: oNodeAddress | null;
|
|
27
|
+
/**
|
|
28
|
+
* Get the current list of leader addresses
|
|
29
|
+
* @returns Array of leader addresses (empty if this node is the leader)
|
|
30
|
+
*/
|
|
31
|
+
getLeaders(): oNodeAddress[];
|
|
32
|
+
/**
|
|
33
|
+
* Get the current list of parent addresses
|
|
34
|
+
* @returns Array of parent addresses
|
|
35
|
+
*/
|
|
36
|
+
getParents(): oNodeAddress[];
|
|
37
|
+
/**
|
|
38
|
+
* Get the current list of child addresses
|
|
39
|
+
* @returns Array of child addresses
|
|
40
|
+
*/
|
|
41
|
+
getChildren(): oNodeAddress[];
|
|
42
|
+
/**
|
|
43
|
+
* Remove a child from the hierarchy
|
|
44
|
+
* @param childAddress The address of the child to remove
|
|
45
|
+
*/
|
|
46
|
+
removeChild(childAddress: oNodeAddress): void;
|
|
47
|
+
use(param1: any, param2: any): Promise<any>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=i-heartbeatable-node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i-heartbeatable-node.d.ts","sourceRoot":"","sources":["../../../src/interfaces/i-heartbeatable-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,OAAO,EAAE,YAAY,CAAC;IAEtB;;OAEG;IACH,mBAAmB,EAAE,oBAAoB,CAAC;IAE1C;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAE5B;;;OAGG;IACH,UAAU,IAAI,YAAY,EAAE,CAAC;IAE7B;;;OAGG;IACH,UAAU,IAAI,YAAY,EAAE,CAAC;IAE7B;;;OAGG;IACH,WAAW,IAAI,YAAY,EAAE,CAAC;IAE9B;;;OAGG;IACH,WAAW,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAE9C,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;CAC7C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -27,6 +27,11 @@ export interface IReconnectableNode {
|
|
|
27
27
|
* Register with the parent node
|
|
28
28
|
*/
|
|
29
29
|
registerParent(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Register with the leader's global registry
|
|
32
|
+
*/
|
|
33
|
+
register(): Promise<void>;
|
|
34
|
+
useSelf(request?: any): Promise<any>;
|
|
30
35
|
/**
|
|
31
36
|
* Execute a method on another node
|
|
32
37
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i-reconnectable-node.d.ts","sourceRoot":"","sources":["../../../src/interfaces/i-reconnectable-node.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"i-reconnectable-node.d.ts","sourceRoot":"","sources":["../../../src/interfaces/i-reconnectable-node.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,SAAS,EACT,oBAAoB,EAErB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,MAAM,EAAE,WAAW,CAAC;IAEpB;;OAEG;IACH,OAAO,EAAE,YAAY,CAAC;IAEtB;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IAEjB;;OAEG;IACH,mBAAmB,EAAE,oBAAoB,CAAC;IAE1C;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;OAEG;IACH,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1B,OAAO,CAAC,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAErC;;OAEG;IACH,GAAG,CACD,OAAO,EAAE,QAAQ,EACjB,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;SAAE,CAAC;QAChC,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,GACA,OAAO,CAAC,GAAG,CAAC,CAAC;CACjB"}
|
|
@@ -4,39 +4,33 @@ export interface oNodeConfig extends oCoreConfig {
|
|
|
4
4
|
leader: oNodeAddress | null;
|
|
5
5
|
parent: oNodeAddress | null;
|
|
6
6
|
/**
|
|
7
|
-
* Connection
|
|
8
|
-
* Detects dead connections
|
|
7
|
+
* Connection health monitoring configuration
|
|
8
|
+
* Detects dead connections by checking libp2p connection state
|
|
9
9
|
*/
|
|
10
10
|
connectionHeartbeat?: {
|
|
11
11
|
enabled?: boolean;
|
|
12
12
|
intervalMs?: number;
|
|
13
|
-
timeoutMs?: number;
|
|
14
13
|
failureThreshold?: number;
|
|
15
14
|
checkChildren?: boolean;
|
|
16
15
|
checkParent?: boolean;
|
|
17
16
|
checkLeader?: boolean;
|
|
18
17
|
};
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* Connection timeout configuration
|
|
20
|
+
* Controls timeouts for stream read and drain operations in connections
|
|
22
21
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
leaderRetry?: {
|
|
35
|
-
enabled?: boolean;
|
|
36
|
-
maxAttempts?: number;
|
|
37
|
-
baseDelayMs?: number;
|
|
38
|
-
maxDelayMs?: number;
|
|
39
|
-
timeoutMs?: number;
|
|
22
|
+
connectionTimeouts?: {
|
|
23
|
+
/**
|
|
24
|
+
* Timeout in milliseconds for reading response data from a stream
|
|
25
|
+
* Default: 120000 (2 minutes)
|
|
26
|
+
*/
|
|
27
|
+
readTimeoutMs?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Timeout in milliseconds for waiting for stream buffer to drain when backpressure occurs
|
|
30
|
+
* Default: 30000 (30 seconds)
|
|
31
|
+
*/
|
|
32
|
+
drainTimeoutMs?: number;
|
|
40
33
|
};
|
|
34
|
+
runOnLimitedConnection?: boolean;
|
|
41
35
|
}
|
|
42
36
|
//# sourceMappingURL=o-node.config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node.config.d.ts","sourceRoot":"","sources":["../../../src/interfaces/o-node.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAE5B;;;OAGG;IACH,mBAAmB,CAAC,EAAE;QACpB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,
|
|
1
|
+
{"version":3,"file":"o-node.config.d.ts","sourceRoot":"","sources":["../../../src/interfaces/o-node.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAE5B;;;OAGG;IACH,mBAAmB,CAAC,EAAE;QACpB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB,CAAC;IAEF;;;OAGG;IACH,kBAAkB,CAAC,EAAE;QACnB;;;WAGG;QACH,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB;;;WAGG;QACH,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IAEF,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC"}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { Libp2p } from '@olane/o-config';
|
|
2
1
|
import { oObject } from '@olane/o-core';
|
|
3
2
|
import { oNodeAddress } from '../router/o-node.address.js';
|
|
4
|
-
import {
|
|
5
|
-
import { oNotificationManager } from '@olane/o-core';
|
|
3
|
+
import { IHeartbeatableNode } from '../interfaces/i-heartbeatable-node.js';
|
|
6
4
|
export interface HeartbeatConfig {
|
|
7
5
|
enabled: boolean;
|
|
8
6
|
intervalMs: number;
|
|
9
|
-
timeoutMs: number;
|
|
10
7
|
failureThreshold: number;
|
|
11
8
|
checkChildren: boolean;
|
|
12
9
|
checkParent: boolean;
|
|
@@ -21,36 +18,39 @@ export interface ConnectionHealth {
|
|
|
21
18
|
status: 'healthy' | 'degraded' | 'dead';
|
|
22
19
|
}
|
|
23
20
|
/**
|
|
24
|
-
* Connection
|
|
21
|
+
* Connection Health Monitor
|
|
25
22
|
*
|
|
26
|
-
*
|
|
27
|
-
* Continuously
|
|
23
|
+
* Monitors connection health by checking libp2p's connection state directly.
|
|
24
|
+
* Continuously checks parent and children connections to detect failures early.
|
|
28
25
|
*
|
|
29
26
|
* How it works:
|
|
30
|
-
* - Every `intervalMs`,
|
|
31
|
-
* -
|
|
27
|
+
* - Every `intervalMs`, checks connection status of all tracked connections
|
|
28
|
+
* - Uses libp2p's connection state (no network overhead from pings)
|
|
29
|
+
* - If connection is not open, increments failure counter
|
|
32
30
|
* - After `failureThreshold` failures, marks connection as dead
|
|
33
31
|
* - Emits events for degraded/recovered/dead connections
|
|
34
32
|
* - Automatically removes dead children from hierarchy
|
|
35
33
|
* - Emits ParentDisconnectedEvent when parent dies (triggers reconnection)
|
|
36
34
|
*/
|
|
37
35
|
export declare class oConnectionHeartbeatManager extends oObject {
|
|
38
|
-
private
|
|
39
|
-
private hierarchyManager;
|
|
40
|
-
private notificationManager;
|
|
41
|
-
private address;
|
|
36
|
+
private node;
|
|
42
37
|
private config;
|
|
43
38
|
private heartbeatInterval?;
|
|
44
39
|
private healthMap;
|
|
45
|
-
|
|
40
|
+
private isRunning;
|
|
41
|
+
constructor(node: IHeartbeatableNode, config: HeartbeatConfig);
|
|
46
42
|
start(): Promise<void>;
|
|
47
43
|
stop(): Promise<void>;
|
|
48
|
-
private
|
|
49
|
-
|
|
44
|
+
private performHealthCheckCycle;
|
|
45
|
+
/**
|
|
46
|
+
* Check if a connection to the given address is open by examining libp2p's connection state
|
|
47
|
+
* @returns true if an open connection exists, false otherwise
|
|
48
|
+
*/
|
|
49
|
+
private checkConnectionStatus;
|
|
50
|
+
private checkTarget;
|
|
50
51
|
private handleConnectionDead;
|
|
51
52
|
private emitConnectionDegradedEvent;
|
|
52
53
|
private emitConnectionRecoveredEvent;
|
|
53
|
-
private extractPeerIdFromAddress;
|
|
54
54
|
/**
|
|
55
55
|
* Get current health status of all connections
|
|
56
56
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-connection-heartbeat.manager.d.ts","sourceRoot":"","sources":["../../../src/managers/o-connection-heartbeat.manager.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"o-connection-heartbeat.manager.d.ts","sourceRoot":"","sources":["../../../src/managers/o-connection-heartbeat.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EAOR,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAE3E,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;CACzC;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,2BAA4B,SAAQ,OAAO;IAMpD,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,MAAM;IANhB,OAAO,CAAC,iBAAiB,CAAC,CAAiB;IAC3C,OAAO,CAAC,SAAS,CAAuC;IACxD,OAAO,CAAC,SAAS,CAAS;gBAGhB,IAAI,EAAE,kBAAkB,EACxB,MAAM,EAAE,eAAe;IAK3B,KAAK;IAqBL,IAAI;YAQI,uBAAuB;IAgDrC;;;OAGG;IACH,OAAO,CAAC,qBAAqB;YA8Bf,WAAW;IAoEzB,OAAO,CAAC,oBAAoB;IAyD5B,OAAO,CAAC,2BAA2B;IAmBnC,OAAO,CAAC,4BAA4B;IAiBpC;;OAEG;IACH,eAAe,IAAI,gBAAgB,EAAE;IAIrC;;OAEG;IACH,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,gBAAgB,GAAG,SAAS;IAIxE;;OAEG;IACH,SAAS,IAAI,eAAe;CAG7B"}
|