@olane/o-node 0.7.12-alpha.9 → 0.7.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts +1 -0
  2. package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts.map +1 -1
  3. package/dist/src/connection/interfaces/o-node-connection.config.d.ts +1 -0
  4. package/dist/src/connection/interfaces/o-node-connection.config.d.ts.map +1 -1
  5. package/dist/src/connection/o-node-connection.d.ts +7 -2
  6. package/dist/src/connection/o-node-connection.d.ts.map +1 -1
  7. package/dist/src/connection/o-node-connection.js +56 -32
  8. package/dist/src/connection/o-node-connection.manager.d.ts +20 -5
  9. package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
  10. package/dist/src/connection/o-node-connection.manager.js +95 -65
  11. package/dist/src/connection/o-stream.request.d.ts +11 -0
  12. package/dist/src/connection/o-stream.request.d.ts.map +1 -0
  13. package/dist/src/connection/o-stream.request.js +7 -0
  14. package/dist/src/connection/stream-handler.config.d.ts +46 -0
  15. package/dist/src/connection/stream-handler.config.d.ts.map +1 -0
  16. package/dist/src/connection/stream-handler.config.js +1 -0
  17. package/dist/src/connection/stream-handler.d.ts +98 -0
  18. package/dist/src/connection/stream-handler.d.ts.map +1 -0
  19. package/dist/src/connection/stream-handler.js +310 -0
  20. package/dist/src/index.d.ts +2 -1
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/index.js +2 -1
  23. package/dist/src/interfaces/i-heartbeatable-node.d.ts +49 -0
  24. package/dist/src/interfaces/i-heartbeatable-node.d.ts.map +1 -0
  25. package/dist/src/interfaces/i-heartbeatable-node.js +1 -0
  26. package/dist/src/interfaces/i-reconnectable-node.d.ts +5 -0
  27. package/dist/src/interfaces/i-reconnectable-node.d.ts.map +1 -1
  28. package/dist/src/interfaces/o-node.config.d.ts +16 -22
  29. package/dist/src/interfaces/o-node.config.d.ts.map +1 -1
  30. package/dist/src/managers/o-connection-heartbeat.manager.d.ts +17 -17
  31. package/dist/src/managers/o-connection-heartbeat.manager.d.ts.map +1 -1
  32. package/dist/src/managers/o-connection-heartbeat.manager.js +83 -78
  33. package/dist/src/managers/o-reconnection.manager.d.ts +12 -0
  34. package/dist/src/managers/o-reconnection.manager.d.ts.map +1 -1
  35. package/dist/src/managers/o-reconnection.manager.js +138 -22
  36. package/dist/src/o-node.d.ts +23 -12
  37. package/dist/src/o-node.d.ts.map +1 -1
  38. package/dist/src/o-node.js +227 -72
  39. package/dist/src/o-node.notification-manager.d.ts.map +1 -1
  40. package/dist/src/o-node.notification-manager.js +17 -12
  41. package/dist/src/o-node.tool.d.ts +1 -0
  42. package/dist/src/o-node.tool.d.ts.map +1 -1
  43. package/dist/src/o-node.tool.js +18 -34
  44. package/dist/src/router/o-node.router.d.ts +1 -0
  45. package/dist/src/router/o-node.router.d.ts.map +1 -1
  46. package/dist/src/router/o-node.router.js +62 -9
  47. package/dist/src/router/o-node.routing-policy.d.ts.map +1 -1
  48. package/dist/src/router/o-node.routing-policy.js +7 -2
  49. package/dist/src/router/resolvers/o-node.resolver.d.ts.map +1 -1
  50. package/dist/src/router/resolvers/o-node.resolver.js +5 -1
  51. package/dist/src/router/resolvers/o-node.search-resolver.d.ts.map +1 -1
  52. package/dist/src/router/resolvers/o-node.search-resolver.js +34 -9
  53. package/dist/src/utils/index.d.ts +3 -0
  54. package/dist/src/utils/index.d.ts.map +1 -0
  55. package/dist/src/utils/index.js +2 -0
  56. package/dist/src/utils/stream.utils.d.ts +6 -0
  57. package/dist/src/utils/stream.utils.d.ts.map +1 -0
  58. package/dist/src/utils/stream.utils.js +31 -0
  59. package/dist/test/helpers/test-node.tool.d.ts +15 -0
  60. package/dist/test/helpers/test-node.tool.d.ts.map +1 -0
  61. package/dist/test/helpers/test-node.tool.js +27 -0
  62. package/package.json +6 -6
  63. package/dist/src/router/resolvers/o-node.child-resolver.d.ts +0 -11
  64. package/dist/src/router/resolvers/o-node.child-resolver.d.ts.map +0 -1
  65. package/dist/src/router/resolvers/o-node.child-resolver.js +0 -58
  66. package/dist/src/utils/leader-request-wrapper.d.ts +0 -45
  67. package/dist/src/utils/leader-request-wrapper.d.ts.map +0 -1
  68. package/dist/src/utils/leader-request-wrapper.js +0 -89
  69. package/dist/test/o-node.spec.d.ts +0 -2
  70. package/dist/test/o-node.spec.d.ts.map +0 -1
  71. package/dist/test/o-node.spec.js +0 -20
  72. package/dist/test/search-resolver.spec.d.ts +0 -2
  73. package/dist/test/search-resolver.spec.d.ts.map +0 -1
  74. 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;CACjB"}
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;CAC3B"}
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
- read(source: Stream): Promise<any>;
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,EACL,UAAU,EAIV,MAAM,EAEP,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,WAAW,EAGX,QAAQ,EACR,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AAEjF,qBAAa,eAAgB,SAAQ,WAAW;IAGlC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IAFrD,aAAa,EAAE,UAAU,CAAC;gBAEF,MAAM,EAAE,qBAAqB;IAKtD,IAAI,CAAC,MAAM,EAAE,MAAM;IAWzB,QAAQ;IAOF,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAiD/C,KAAK;CAKZ"}
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 { Uint8ArrayList, byteStream, } from '@olane/o-config';
2
- import { oConnection, oError, oErrorCodes, oResponse, } from '@olane/o-core';
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
- async read(source) {
10
- const bytes = byteStream(source);
11
- const output = await bytes.read({
12
- signal: AbortSignal.timeout(120000), // 2 min timeout
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
- const stream = await this.p2pConnection.newStream(this.nextHopAddress.protocol, {
27
- maxOutboundStreams: Infinity,
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
- // Send the data with backpressure handling (libp2p v3 best practice)
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
- const sent = stream.send(data);
39
- // If send() returns false, wait for the stream to drain before continuing
40
- if (!sent) {
41
- this.logger.debug('Stream buffer full, waiting for drain...');
42
- await stream.onDrain({ signal: AbortSignal.timeout(30000) }); // 30 second timeout
43
- }
44
- const res = await this.read(stream);
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 { oConnection } from '@olane/o-core';
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
- private p2pNode;
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 with exponential backoff retry
10
- * @param address - The address to connect to
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
- getCachedConnection(address: oAddress): oConnection | null;
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,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,4BAA4B,EAAE,MAAM,kDAAkD,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,qBAAa,sBAAuB,SAAQ,kBAAkB;IAC5D,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,EAAE,4BAA4B;IAKhD;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IAqFlE,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IAIpC,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,WAAW,GAAG,IAAI;CAe3D"}
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 with exponential backoff retry
10
- * @param address - The address to connect to
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
- // check if we already have a connection to this address
16
- // TODO: how can we enable caching of connections & connection lifecycles
17
- if (this.isCached(nextHopAddress)) {
18
- const cachedConnection = this.getCachedConnection(nextHopAddress);
19
- if (cachedConnection &&
20
- cachedConnection.p2pConnection.status === 'open') {
21
- this.logger.debug('Using cached connection for address: ' + address.toString());
22
- return cachedConnection;
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
- else {
25
- // cached item is not valid, remove it
26
- this.cache.delete(nextHopAddress.toString());
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
- // Retry configuration for handling transient connection failures
30
- const MAX_RETRIES = 3;
31
- const BASE_DELAY_MS = 1000; // Start with 1 second
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
- isCached(address) {
70
- return this.cache.has(address.toString());
71
- }
72
- getCachedConnection(address) {
73
- const key = address.toString();
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 connection = this.cache.get(key);
76
- if (!connection) {
77
- throw new Error('Connection not found in cache');
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.validate();
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.cache.delete(key);
84
- this.logger.error('Error getting cached connection:', error);
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,7 @@
1
+ import { oRequest } from '@olane/o-core';
2
+ export class oStreamRequest extends oRequest {
3
+ constructor(config) {
4
+ super(config);
5
+ this.stream = config.stream;
6
+ }
7
+ }
@@ -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"}