@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
|
@@ -2,5 +2,6 @@ import { oConnectionManagerConfig } from '@olane/o-core';
|
|
|
2
2
|
import { Libp2p } from '@olane/o-config';
|
|
3
3
|
export interface oNodeConnectionManagerConfig extends oConnectionManagerConfig {
|
|
4
4
|
p2pNode: Libp2p;
|
|
5
|
+
runOnLimitedConnection?: boolean;
|
|
5
6
|
}
|
|
6
7
|
//# sourceMappingURL=o-node-connection-manager.config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection-manager.config.d.ts","sourceRoot":"","sources":["../../../../src/connection/interfaces/o-node-connection-manager.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,MAAM,WAAW,4BAA6B,SAAQ,wBAAwB;IAC5E,OAAO,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"o-node-connection-manager.config.d.ts","sourceRoot":"","sources":["../../../../src/connection/interfaces/o-node-connection-manager.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,MAAM,WAAW,4BAA6B,SAAQ,wBAAwB;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC"}
|
|
@@ -2,5 +2,6 @@ import { Connection } from '@olane/o-config';
|
|
|
2
2
|
import { oConnectionConfig } from '@olane/o-core';
|
|
3
3
|
export interface oNodeConnectionConfig extends oConnectionConfig {
|
|
4
4
|
p2pConnection: Connection;
|
|
5
|
+
runOnLimitedConnection?: boolean;
|
|
5
6
|
}
|
|
6
7
|
//# sourceMappingURL=o-node-connection.config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection.config.d.ts","sourceRoot":"","sources":["../../../../src/connection/interfaces/o-node-connection.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;IAC9D,aAAa,EAAE,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"o-node-connection.config.d.ts","sourceRoot":"","sources":["../../../../src/connection/interfaces/o-node-connection.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;IAC9D,aAAa,EAAE,UAAU,CAAC;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC"}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { Connection, Stream } from '@olane/o-config';
|
|
2
2
|
import { oConnection, oRequest, oResponse } from '@olane/o-core';
|
|
3
3
|
import { oNodeConnectionConfig } from './interfaces/o-node-connection.config.js';
|
|
4
|
+
import { StreamHandler } from './stream-handler.js';
|
|
4
5
|
export declare class oNodeConnection extends oConnection {
|
|
5
6
|
protected readonly config: oNodeConnectionConfig;
|
|
6
7
|
p2pConnection: Connection;
|
|
8
|
+
protected streamHandler: StreamHandler;
|
|
7
9
|
constructor(config: oNodeConnectionConfig);
|
|
8
|
-
|
|
9
|
-
validate(): void;
|
|
10
|
+
setupConnectionListeners(): void;
|
|
11
|
+
validate(stream?: Stream): void;
|
|
12
|
+
getOrCreateStream(): Promise<Stream>;
|
|
10
13
|
transmit(request: oRequest): Promise<oResponse>;
|
|
14
|
+
postTransmit(stream: Stream): Promise<void>;
|
|
15
|
+
abort(error: Error): Promise<void>;
|
|
11
16
|
close(): Promise<void>;
|
|
12
17
|
}
|
|
13
18
|
//# sourceMappingURL=o-node-connection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"o-node-connection.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAEL,WAAW,EAGX,QAAQ,EACR,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,qBAAa,eAAgB,SAAQ,WAAW;IAIlC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IAHrD,aAAa,EAAE,UAAU,CAAC;IACjC,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC;gBAER,MAAM,EAAE,qBAAqB;IAO5D,wBAAwB;IAYxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IAmBlB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAkBpC,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IA0C/C,YAAY,CAAC,MAAM,EAAE,MAAM;IAQ3B,KAAK,CAAC,KAAK,EAAE,KAAK;IAMlB,KAAK;CAKZ"}
|
|
@@ -1,52 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { oConnection, oError, oErrorCodes, } from '@olane/o-core';
|
|
2
|
+
import { StreamHandler } from './stream-handler.js';
|
|
3
3
|
export class oNodeConnection extends oConnection {
|
|
4
4
|
constructor(config) {
|
|
5
5
|
super(config);
|
|
6
6
|
this.config = config;
|
|
7
7
|
this.p2pConnection = config.p2pConnection;
|
|
8
|
+
this.streamHandler = new StreamHandler(this.logger);
|
|
9
|
+
this.setupConnectionListeners();
|
|
8
10
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
setupConnectionListeners() {
|
|
12
|
+
this.logger.debug('Setting up connection listeners for address: ' +
|
|
13
|
+
this.nextHopAddress.toString());
|
|
14
|
+
this.p2pConnection?.addEventListener('close', () => {
|
|
15
|
+
this.logger.debug('Connection closed for address: ' + this.nextHopAddress.toString());
|
|
13
16
|
});
|
|
14
|
-
const outputObj = output instanceof Uint8ArrayList ? output.subarray() : output;
|
|
15
|
-
const jsonStr = new TextDecoder().decode(outputObj);
|
|
16
|
-
return JSON.parse(jsonStr);
|
|
17
17
|
}
|
|
18
|
-
validate() {
|
|
18
|
+
validate(stream) {
|
|
19
19
|
if (this.config.p2pConnection.status !== 'open') {
|
|
20
20
|
throw new Error('Connection is not valid');
|
|
21
21
|
}
|
|
22
22
|
// do nothing
|
|
23
|
+
if (!stream || (stream.status !== 'open' && stream.status !== 'reset')) {
|
|
24
|
+
throw new oError(oErrorCodes.FAILED_TO_DIAL_TARGET, 'Failed to dial target');
|
|
25
|
+
}
|
|
26
|
+
if (stream.status === 'reset') {
|
|
27
|
+
throw new oError(oErrorCodes.CONNECTION_LIMIT_REACHED, 'Connection limit reached');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async getOrCreateStream() {
|
|
31
|
+
const streamConfig = {
|
|
32
|
+
signal: this.abortSignal,
|
|
33
|
+
maxOutboundStreams: process.env.MAX_OUTBOUND_STREAMS
|
|
34
|
+
? parseInt(process.env.MAX_OUTBOUND_STREAMS)
|
|
35
|
+
: 1000,
|
|
36
|
+
runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
|
|
37
|
+
reusePolicy: 'none', // Default policy, can be overridden in subclasses
|
|
38
|
+
drainTimeoutMs: this.config.drainTimeoutMs,
|
|
39
|
+
};
|
|
40
|
+
return this.streamHandler.getOrCreateStream(this.p2pConnection, this.nextHopAddress.protocol, streamConfig);
|
|
23
41
|
}
|
|
24
42
|
async transmit(request) {
|
|
25
43
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
runOnLimitedConnection: true, // TODO: should this be configurable?
|
|
29
|
-
});
|
|
30
|
-
if (!stream || (stream.status !== 'open' && stream.status !== 'reset')) {
|
|
31
|
-
throw new oError(oErrorCodes.FAILED_TO_DIAL_TARGET, 'Failed to dial target');
|
|
32
|
-
}
|
|
33
|
-
if (stream.status === 'reset') {
|
|
34
|
-
throw new oError(oErrorCodes.CONNECTION_LIMIT_REACHED, 'Connection limit reached');
|
|
44
|
+
if (this.config.runOnLimitedConnection) {
|
|
45
|
+
this.logger.debug('Running on limited connection...');
|
|
35
46
|
}
|
|
36
|
-
|
|
47
|
+
const stream = await this.getOrCreateStream();
|
|
48
|
+
this.validate(stream);
|
|
49
|
+
const streamConfig = {
|
|
50
|
+
signal: this.abortSignal,
|
|
51
|
+
drainTimeoutMs: this.config.drainTimeoutMs,
|
|
52
|
+
reusePolicy: 'none', // Default policy
|
|
53
|
+
};
|
|
54
|
+
// Send the request with backpressure handling
|
|
37
55
|
const data = new TextEncoder().encode(request.toString());
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
if
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
await stream.close();
|
|
46
|
-
// process the response
|
|
47
|
-
const response = new oResponse({
|
|
48
|
-
...res.result,
|
|
49
|
-
});
|
|
56
|
+
await this.streamHandler.send(stream, data, streamConfig);
|
|
57
|
+
// Handle response using StreamHandler
|
|
58
|
+
// Pass request handler if configured to enable bidirectional stream processing
|
|
59
|
+
// Pass request ID to enable proper response correlation on shared streams
|
|
60
|
+
const response = await this.streamHandler.handleOutgoingStream(stream, this.emitter, streamConfig, this.config.requestHandler, request.id);
|
|
61
|
+
// Handle cleanup of the stream
|
|
62
|
+
await this.postTransmit(stream);
|
|
50
63
|
return response;
|
|
51
64
|
}
|
|
52
65
|
catch (error) {
|
|
@@ -56,6 +69,17 @@ export class oNodeConnection extends oConnection {
|
|
|
56
69
|
throw error;
|
|
57
70
|
}
|
|
58
71
|
}
|
|
72
|
+
async postTransmit(stream) {
|
|
73
|
+
const streamConfig = {
|
|
74
|
+
reusePolicy: 'none', // Default policy, can be overridden in subclasses
|
|
75
|
+
};
|
|
76
|
+
await this.streamHandler.close(stream, streamConfig);
|
|
77
|
+
}
|
|
78
|
+
async abort(error) {
|
|
79
|
+
this.logger.debug('Aborting connection');
|
|
80
|
+
await this.p2pConnection.abort(error);
|
|
81
|
+
this.logger.debug('Connection aborted');
|
|
82
|
+
}
|
|
59
83
|
async close() {
|
|
60
84
|
this.logger.debug('Closing connection');
|
|
61
85
|
await this.p2pConnection.close();
|
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
import { oAddress, oConnectionConfig, oConnectionManager } from '@olane/o-core';
|
|
2
|
-
import {
|
|
2
|
+
import { Libp2p, Connection } from '@olane/o-config';
|
|
3
3
|
import { oNodeConnectionManagerConfig } from './interfaces/o-node-connection-manager.config.js';
|
|
4
4
|
import { oNodeConnection } from './o-node-connection.js';
|
|
5
5
|
export declare class oNodeConnectionManager extends oConnectionManager {
|
|
6
|
-
|
|
6
|
+
readonly config: oNodeConnectionManagerConfig;
|
|
7
|
+
protected p2pNode: Libp2p;
|
|
8
|
+
private defaultReadTimeoutMs?;
|
|
9
|
+
private defaultDrainTimeoutMs?;
|
|
10
|
+
private connections;
|
|
7
11
|
constructor(config: oNodeConnectionManagerConfig);
|
|
12
|
+
getOrCreateConnection(nextHopAddress: oAddress, address: oAddress): Promise<Connection>;
|
|
8
13
|
/**
|
|
9
|
-
* Connect to a given address
|
|
10
|
-
* @param
|
|
14
|
+
* Connect to a given address, reusing libp2p connections when possible
|
|
15
|
+
* @param config - Connection configuration
|
|
11
16
|
* @returns The connection object
|
|
12
17
|
*/
|
|
13
18
|
connect(config: oConnectionConfig): Promise<oNodeConnection>;
|
|
19
|
+
/**
|
|
20
|
+
* Check if libp2p has an active connection to the target peer
|
|
21
|
+
* @param address - The address to check
|
|
22
|
+
* @returns true if an active connection exists
|
|
23
|
+
*/
|
|
14
24
|
isCached(address: oAddress): boolean;
|
|
15
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Get an existing libp2p connection to the target peer
|
|
27
|
+
* @param address - The address to get a connection for
|
|
28
|
+
* @returns The libp2p Connection object or null if not found
|
|
29
|
+
*/
|
|
30
|
+
getCachedLibp2pConnection(address: oAddress): Connection | null;
|
|
16
31
|
}
|
|
17
32
|
//# sourceMappingURL=o-node-connection.manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection.manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"o-node-connection.manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAU,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,4BAA4B,EAAE,MAAM,kDAAkD,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,qBAAa,sBAAuB,SAAQ,kBAAkB;IAMhD,QAAQ,CAAC,MAAM,EAAE,4BAA4B;IALzD,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,OAAO,CAAC,qBAAqB,CAAC,CAAS;IACvC,OAAO,CAAC,WAAW,CAAsC;gBAEpC,MAAM,EAAE,4BAA4B;IAOnD,qBAAqB,CACzB,cAAc,EAAE,QAAQ,EACxB,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,UAAU,CAAC;IAoCtB;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IA8BlE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IA4BpC;;;;OAIG;IACH,yBAAyB,CAAC,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,IAAI;CAmChE"}
|
|
@@ -3,86 +3,116 @@ import { oNodeConnection } from './o-node-connection.js';
|
|
|
3
3
|
export class oNodeConnectionManager extends oConnectionManager {
|
|
4
4
|
constructor(config) {
|
|
5
5
|
super(config);
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.connections = new Map();
|
|
6
8
|
this.p2pNode = config.p2pNode;
|
|
9
|
+
this.defaultReadTimeoutMs = config.defaultReadTimeoutMs;
|
|
10
|
+
this.defaultDrainTimeoutMs = config.defaultDrainTimeoutMs;
|
|
11
|
+
}
|
|
12
|
+
async getOrCreateConnection(nextHopAddress, address) {
|
|
13
|
+
// Check if libp2p already has an active connection to this peer
|
|
14
|
+
const existingConnection = this.getCachedLibp2pConnection(nextHopAddress);
|
|
15
|
+
let p2pConnection;
|
|
16
|
+
if (existingConnection && existingConnection.status === 'open') {
|
|
17
|
+
this.logger.debug('Reusing existing libp2p connection for address: ' +
|
|
18
|
+
nextHopAddress.toString());
|
|
19
|
+
p2pConnection = existingConnection;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
// No existing connection or connection is closed, dial a new one
|
|
23
|
+
this.logger.debug('Dialing new connection for address: ' + address.toString());
|
|
24
|
+
p2pConnection = await this.p2pNode.dial(nextHopAddress.libp2pTransports.map((ma) => ma.toMultiaddr()));
|
|
25
|
+
}
|
|
26
|
+
if (nextHopAddress.libp2pTransports.length) {
|
|
27
|
+
const peerIdString = nextHopAddress.libp2pTransports[0].toPeerId();
|
|
28
|
+
if (peerIdString) {
|
|
29
|
+
this.connections.set(peerIdString, p2pConnection);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return p2pConnection;
|
|
7
33
|
}
|
|
8
34
|
/**
|
|
9
|
-
* Connect to a given address
|
|
10
|
-
* @param
|
|
35
|
+
* Connect to a given address, reusing libp2p connections when possible
|
|
36
|
+
* @param config - Connection configuration
|
|
11
37
|
* @returns The connection object
|
|
12
38
|
*/
|
|
13
39
|
async connect(config) {
|
|
14
|
-
const { address, nextHopAddress, callerAddress } = config;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, } = config;
|
|
41
|
+
const p2pConnection = await this.getOrCreateConnection(nextHopAddress, address);
|
|
42
|
+
const connection = new oNodeConnection({
|
|
43
|
+
nextHopAddress: nextHopAddress,
|
|
44
|
+
address: address,
|
|
45
|
+
p2pConnection: p2pConnection,
|
|
46
|
+
callerAddress: callerAddress,
|
|
47
|
+
readTimeoutMs: readTimeoutMs ?? this.defaultReadTimeoutMs,
|
|
48
|
+
drainTimeoutMs: drainTimeoutMs ?? this.defaultDrainTimeoutMs,
|
|
49
|
+
isStream: config.isStream ?? false,
|
|
50
|
+
abortSignal: config.abortSignal,
|
|
51
|
+
runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
|
|
52
|
+
requestHandler: config.requestHandler ?? undefined,
|
|
53
|
+
});
|
|
54
|
+
return connection;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if libp2p has an active connection to the target peer
|
|
58
|
+
* @param address - The address to check
|
|
59
|
+
* @returns true if an active connection exists
|
|
60
|
+
*/
|
|
61
|
+
isCached(address) {
|
|
62
|
+
try {
|
|
63
|
+
const nodeAddress = address;
|
|
64
|
+
if (!nodeAddress.libp2pTransports ||
|
|
65
|
+
nodeAddress.libp2pTransports.length === 0) {
|
|
66
|
+
return false;
|
|
23
67
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
68
|
+
// Extract peer ID from the first transport
|
|
69
|
+
const peerIdString = nodeAddress.libp2pTransports[0].toPeerId();
|
|
70
|
+
if (!peerIdString) {
|
|
71
|
+
return false;
|
|
27
72
|
}
|
|
73
|
+
this.logger.debug('Peer ID string:', peerIdString);
|
|
74
|
+
// the following works since the peer id param is not really required: https://github.com/libp2p/js-libp2p/blob/0bbf5021b53938b2bffcffca6c13c479a95c2a60/packages/libp2p/src/connection-manager/index.ts#L508
|
|
75
|
+
const connections = this.p2pNode.getConnections(peerIdString); // ignore since converting to a proper peer id breaks the browser implementation
|
|
76
|
+
// Check if we have at least one open connection
|
|
77
|
+
return connections.some((conn) => conn.status === 'open');
|
|
28
78
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const MAX_DELAY_MS = 10000; // Cap at 10 seconds
|
|
33
|
-
// first time setup connection with retry logic
|
|
34
|
-
let lastError;
|
|
35
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
36
|
-
try {
|
|
37
|
-
if (attempt > 0) {
|
|
38
|
-
// Calculate exponential backoff delay: 1s, 2s, 4s, 8s (capped at MAX_DELAY_MS)
|
|
39
|
-
const delay = Math.min(BASE_DELAY_MS * Math.pow(2, attempt - 1), MAX_DELAY_MS);
|
|
40
|
-
this.logger.debug(`Retry attempt ${attempt}/${MAX_RETRIES} for ${nextHopAddress.toString()} after ${delay}ms delay`);
|
|
41
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
42
|
-
}
|
|
43
|
-
const p2pConnection = await this.p2pNode.dial(nextHopAddress.libp2pTransports.map((ma) => ma.toMultiaddr()));
|
|
44
|
-
const connection = new oNodeConnection({
|
|
45
|
-
nextHopAddress: nextHopAddress,
|
|
46
|
-
address: address,
|
|
47
|
-
p2pConnection: p2pConnection,
|
|
48
|
-
callerAddress: callerAddress,
|
|
49
|
-
});
|
|
50
|
-
if (attempt > 0) {
|
|
51
|
-
this.logger.info(`Successfully connected to ${nextHopAddress.toString()} on retry attempt ${attempt}`);
|
|
52
|
-
}
|
|
53
|
-
// this.cache.set(nextHopAddress.toString(), connection);
|
|
54
|
-
return connection;
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
lastError = error;
|
|
58
|
-
this.logger.warn(`[${callerAddress?.toString() || 'unknown'}] Connection attempt ${attempt + 1}/${MAX_RETRIES + 1} failed for ${nextHopAddress.toString()}: ${error instanceof Error ? error.message : String(error)}`);
|
|
59
|
-
// Don't retry on the last attempt
|
|
60
|
-
if (attempt === MAX_RETRIES) {
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
this.logger.debug('Error checking cached connection:', error);
|
|
81
|
+
return false;
|
|
64
82
|
}
|
|
65
|
-
// All retries exhausted
|
|
66
|
-
this.logger.error(`[${callerAddress?.toString() || 'unknown'}] Failed to connect after ${MAX_RETRIES + 1} attempts to address! Next hop: ${nextHopAddress} With Address: ${address.toString()}`, lastError);
|
|
67
|
-
throw lastError;
|
|
68
83
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Get an existing libp2p connection to the target peer
|
|
86
|
+
* @param address - The address to get a connection for
|
|
87
|
+
* @returns The libp2p Connection object or null if not found
|
|
88
|
+
*/
|
|
89
|
+
getCachedLibp2pConnection(address) {
|
|
74
90
|
try {
|
|
75
|
-
const
|
|
76
|
-
if (!
|
|
77
|
-
|
|
91
|
+
const nodeAddress = address;
|
|
92
|
+
if (!nodeAddress.libp2pTransports.length) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const peerIdString = nodeAddress.libp2pTransports[0].toPeerId();
|
|
96
|
+
if (!peerIdString) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const connections = this.p2pNode.getConnections(); // ignore since converting to a proper peer id breaks the browser implementation
|
|
100
|
+
const filteredConnections = connections.filter((conn) => conn.remotePeer?.toString() === peerIdString);
|
|
101
|
+
// Return the first open connection, or null if none exist
|
|
102
|
+
const openConnection = filteredConnections.find((conn) => conn.status === 'open');
|
|
103
|
+
if (openConnection) {
|
|
104
|
+
return openConnection;
|
|
105
|
+
}
|
|
106
|
+
const connection = this.connections.get(peerIdString);
|
|
107
|
+
if (connection?.status === 'open') {
|
|
108
|
+
return connection;
|
|
78
109
|
}
|
|
79
|
-
connection
|
|
80
|
-
return connection;
|
|
110
|
+
// Return the first open connection, or null if none exist
|
|
111
|
+
return connection ?? null;
|
|
81
112
|
}
|
|
82
113
|
catch (error) {
|
|
83
|
-
this.
|
|
84
|
-
|
|
114
|
+
this.logger.debug('Error getting cached connection:', error);
|
|
115
|
+
return null;
|
|
85
116
|
}
|
|
86
|
-
return null;
|
|
87
117
|
}
|
|
88
118
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Request, RequestId } from '@olane/o-protocol';
|
|
2
|
+
import { oRequest } from '@olane/o-core';
|
|
3
|
+
import { Stream } from '@olane/o-config';
|
|
4
|
+
export declare class oStreamRequest extends oRequest {
|
|
5
|
+
stream: Stream;
|
|
6
|
+
constructor(config: Request & {
|
|
7
|
+
id: RequestId;
|
|
8
|
+
stream: Stream;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=o-stream.request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"o-stream.request.d.ts","sourceRoot":"","sources":["../../../src/connection/o-stream.request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,MAAM,EAAE,MAAM,CAAC;gBACH,MAAM,EAAE,OAAO,GAAG;QAAE,EAAE,EAAE,SAAS,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;CAIhE"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { Stream } from '@libp2p/interface';
|
|
3
|
+
/**
|
|
4
|
+
* Stream reuse policy determines how streams are managed across multiple requests
|
|
5
|
+
*/
|
|
6
|
+
export type StreamReusePolicy = 'none' | 'reuse' | 'pool';
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for StreamHandler behavior
|
|
9
|
+
*/
|
|
10
|
+
export interface StreamHandlerConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Stream reuse policy:
|
|
13
|
+
* - 'none': Create new stream for each request (default)
|
|
14
|
+
* - 'reuse': Reuse existing open streams
|
|
15
|
+
* - 'pool': (Future) Maintain a pool of streams
|
|
16
|
+
*/
|
|
17
|
+
reusePolicy?: StreamReusePolicy;
|
|
18
|
+
/**
|
|
19
|
+
* Timeout in milliseconds to wait for stream drain when buffer is full
|
|
20
|
+
* @default 30000 (30 seconds)
|
|
21
|
+
*/
|
|
22
|
+
drainTimeoutMs?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Whether this connection can run on limited connections
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
runOnLimitedConnection?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Maximum number of outbound streams
|
|
30
|
+
* @default 1000
|
|
31
|
+
*/
|
|
32
|
+
maxOutboundStreams?: number;
|
|
33
|
+
/**
|
|
34
|
+
* AbortSignal for cancellation
|
|
35
|
+
*/
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Context for stream lifecycle operations
|
|
40
|
+
*/
|
|
41
|
+
export interface StreamContext {
|
|
42
|
+
stream: Stream;
|
|
43
|
+
protocol: string;
|
|
44
|
+
config: StreamHandlerConfig;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=stream-handler.config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-handler.config.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-handler.config.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAEhC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,mBAAmB,CAAC;CAC7B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { Connection, Stream } from '@libp2p/interface';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import { oRequest, oResponse, Logger } from '@olane/o-core';
|
|
5
|
+
import type { oRouterRequest } from '@olane/o-core';
|
|
6
|
+
import type { oConnection } from '@olane/o-core';
|
|
7
|
+
import type { RunResult } from '@olane/o-tool';
|
|
8
|
+
import type { StreamHandlerConfig } from './stream-handler.config.js';
|
|
9
|
+
/**
|
|
10
|
+
* StreamHandler centralizes all stream-related functionality including:
|
|
11
|
+
* - Message type detection (request vs response)
|
|
12
|
+
* - Stream lifecycle management (create, reuse, close)
|
|
13
|
+
* - Backpressure handling
|
|
14
|
+
* - Request/response handling
|
|
15
|
+
* - Stream routing for middleware nodes
|
|
16
|
+
*/
|
|
17
|
+
export declare class StreamHandler {
|
|
18
|
+
private logger;
|
|
19
|
+
constructor(logger?: Logger);
|
|
20
|
+
/**
|
|
21
|
+
* Detects if a decoded message is a request
|
|
22
|
+
* Requests have a 'method' field and no 'result' field
|
|
23
|
+
*/
|
|
24
|
+
isRequest(message: any): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Detects if a decoded message is a response
|
|
27
|
+
* Responses have a 'result' field and no 'method' field
|
|
28
|
+
*/
|
|
29
|
+
isResponse(message: any): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Decodes a stream message event into a JSON object
|
|
32
|
+
*/
|
|
33
|
+
decodeMessage(event: any): Promise<any>;
|
|
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
|
+
getOrCreateStream(connection: Connection, protocol: string, config?: StreamHandlerConfig): Promise<Stream>;
|
|
42
|
+
/**
|
|
43
|
+
* Sends data through a stream with backpressure handling
|
|
44
|
+
*
|
|
45
|
+
* @param stream - The stream to send data through
|
|
46
|
+
* @param data - The data to send
|
|
47
|
+
* @param config - Configuration for timeout and other options
|
|
48
|
+
*/
|
|
49
|
+
send(stream: Stream, data: Uint8Array, config?: StreamHandlerConfig): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Closes a stream safely with error handling
|
|
52
|
+
*
|
|
53
|
+
* @param stream - The stream to close
|
|
54
|
+
* @param config - Configuration including reuse policy
|
|
55
|
+
*/
|
|
56
|
+
close(stream: Stream, config?: StreamHandlerConfig): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Handles an incoming stream on the server side
|
|
59
|
+
* Attaches message listener immediately (libp2p v3 best practice)
|
|
60
|
+
* Routes requests or executes tools based on the message
|
|
61
|
+
*
|
|
62
|
+
* @param stream - The incoming stream
|
|
63
|
+
* @param connection - The connection the stream belongs to
|
|
64
|
+
* @param toolExecutor - Function to execute tools for requests
|
|
65
|
+
*/
|
|
66
|
+
handleIncomingStream(stream: Stream, connection: Connection, toolExecutor: (request: oRequest, stream: Stream) => Promise<RunResult>): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Handles a request message by executing the tool and sending response
|
|
69
|
+
*
|
|
70
|
+
* @param message - The decoded request message
|
|
71
|
+
* @param stream - The stream to send the response on
|
|
72
|
+
* @param toolExecutor - Function to execute the tool
|
|
73
|
+
*/
|
|
74
|
+
private handleRequestMessage;
|
|
75
|
+
/**
|
|
76
|
+
* Handles an outgoing stream on the client side
|
|
77
|
+
* Listens for response messages and emits them via the event emitter
|
|
78
|
+
* If requestHandler is provided, also processes incoming router requests
|
|
79
|
+
*
|
|
80
|
+
* @param stream - The outgoing stream
|
|
81
|
+
* @param emitter - Event emitter for chunk events
|
|
82
|
+
* @param config - Configuration including abort signal
|
|
83
|
+
* @param requestHandler - Optional handler for processing router requests received on this stream
|
|
84
|
+
* @param requestId - Optional request ID to filter responses (for stream reuse scenarios)
|
|
85
|
+
* @returns Promise that resolves with the final response
|
|
86
|
+
*/
|
|
87
|
+
handleOutgoingStream(stream: Stream, emitter: EventEmitter, config?: StreamHandlerConfig, requestHandler?: (request: oRequest, stream: Stream) => Promise<RunResult>, requestId?: string | number): Promise<oResponse>;
|
|
88
|
+
/**
|
|
89
|
+
* Forwards a request to the next hop and relays response chunks back
|
|
90
|
+
* This implements the middleware/proxy pattern for intermediate nodes
|
|
91
|
+
*
|
|
92
|
+
* @param request - The router request to forward
|
|
93
|
+
* @param incomingStream - The stream to send responses back on
|
|
94
|
+
* @param dialFn - Function to dial the next hop connection
|
|
95
|
+
*/
|
|
96
|
+
forwardRequest(request: oRouterRequest, incomingStream: Stream, dialFn: (address: string) => Promise<oConnection>): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=stream-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-handler.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-handler.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EACL,QAAQ,EACR,SAAS,EAIT,MAAM,EAEP,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE;;;;;;;GAOG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,CAAC,EAAE,MAAM;IAI3B;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAIhC;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAIjC;;OAEG;IACG,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAI7C;;;;;;OAMG;IACG,iBAAiB,CACrB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,mBAAwB,GAC/B,OAAO,CAAC,MAAM,CAAC;IAsClB;;;;;;OAMG;IACG,IAAI,CACR,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,MAAM,GAAE,mBAAwB,GAC/B,OAAO,CAAC,IAAI,CAAC;IAiBhB;;;;;OAKG;IACG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB5E;;;;;;;;OAQG;IACG,oBAAoB,CACxB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,GACtE,OAAO,CAAC,IAAI,CAAC;IAuChB;;;;;;OAMG;YACW,oBAAoB;IA4BlC;;;;;;;;;;;OAWG;IACG,oBAAoB,CACxB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,YAAY,EACrB,MAAM,GAAE,mBAAwB,EAChC,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,EAC1E,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAC1B,OAAO,CAAC,SAAS,CAAC;IA+GrB;;;;;;;OAOG;IACG,cAAc,CAClB,OAAO,EAAE,cAAc,EACvB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,GAChD,OAAO,CAAC,IAAI,CAAC;CAyBjB"}
|