@olane/o-node 0.7.12-alpha.5 → 0.7.12-alpha.51

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 (60) 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 +5 -2
  6. package/dist/src/connection/o-node-connection.d.ts.map +1 -1
  7. package/dist/src/connection/o-node-connection.js +86 -26
  8. package/dist/src/connection/o-node-connection.manager.d.ts +18 -4
  9. package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
  10. package/dist/src/connection/o-node-connection.manager.js +79 -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/index.d.ts +2 -1
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/index.js +2 -1
  17. package/dist/src/interfaces/i-heartbeatable-node.d.ts +49 -0
  18. package/dist/src/interfaces/i-heartbeatable-node.d.ts.map +1 -0
  19. package/dist/src/interfaces/i-heartbeatable-node.js +1 -0
  20. package/dist/src/interfaces/i-reconnectable-node.d.ts +46 -0
  21. package/dist/src/interfaces/i-reconnectable-node.d.ts.map +1 -0
  22. package/dist/src/interfaces/i-reconnectable-node.js +1 -0
  23. package/dist/src/interfaces/o-node.config.d.ts +43 -0
  24. package/dist/src/interfaces/o-node.config.d.ts.map +1 -1
  25. package/dist/src/managers/o-connection-heartbeat.manager.d.ts +63 -0
  26. package/dist/src/managers/o-connection-heartbeat.manager.d.ts.map +1 -0
  27. package/dist/src/managers/o-connection-heartbeat.manager.js +227 -0
  28. package/dist/src/managers/o-reconnection.manager.d.ts +51 -0
  29. package/dist/src/managers/o-reconnection.manager.d.ts.map +1 -0
  30. package/dist/src/managers/o-reconnection.manager.js +266 -0
  31. package/dist/src/o-node.d.ts +30 -2
  32. package/dist/src/o-node.d.ts.map +1 -1
  33. package/dist/src/o-node.js +245 -33
  34. package/dist/src/o-node.notification-manager.d.ts +52 -0
  35. package/dist/src/o-node.notification-manager.d.ts.map +1 -0
  36. package/dist/src/o-node.notification-manager.js +188 -0
  37. package/dist/src/o-node.tool.d.ts.map +1 -1
  38. package/dist/src/o-node.tool.js +33 -22
  39. package/dist/src/router/o-node.router.d.ts +1 -0
  40. package/dist/src/router/o-node.router.d.ts.map +1 -1
  41. package/dist/src/router/o-node.router.js +62 -9
  42. package/dist/src/router/o-node.routing-policy.d.ts.map +1 -1
  43. package/dist/src/router/o-node.routing-policy.js +7 -2
  44. package/dist/src/router/resolvers/o-node.resolver.d.ts.map +1 -1
  45. package/dist/src/router/resolvers/o-node.resolver.js +5 -1
  46. package/dist/src/router/resolvers/o-node.search-resolver.d.ts.map +1 -1
  47. package/dist/src/router/resolvers/o-node.search-resolver.js +34 -9
  48. package/dist/src/utils/index.d.ts +3 -0
  49. package/dist/src/utils/index.d.ts.map +1 -0
  50. package/dist/src/utils/index.js +2 -0
  51. package/dist/src/utils/stream.utils.d.ts +6 -0
  52. package/dist/src/utils/stream.utils.d.ts.map +1 -0
  53. package/dist/src/utils/stream.utils.js +31 -0
  54. package/dist/test/helpers/test-node.tool.d.ts +15 -0
  55. package/dist/test/helpers/test-node.tool.d.ts.map +1 -0
  56. package/dist/test/helpers/test-node.tool.js +27 -0
  57. package/package.json +6 -6
  58. package/dist/src/router/resolvers/o-node.child-resolver.d.ts +0 -11
  59. package/dist/src/router/resolvers/o-node.child-resolver.d.ts.map +0 -1
  60. package/dist/src/router/resolvers/o-node.child-resolver.js +0 -58
@@ -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"}
@@ -5,9 +5,12 @@ export declare class oNodeConnection extends oConnection {
5
5
  protected readonly config: oNodeConnectionConfig;
6
6
  p2pConnection: Connection;
7
7
  constructor(config: oNodeConnectionConfig);
8
- read(source: Stream): Promise<any>;
9
- validate(): void;
8
+ setupConnectionListeners(): void;
9
+ validate(stream?: Stream): void;
10
+ getOrCreateStream(): Promise<Stream>;
10
11
  transmit(request: oRequest): Promise<oResponse>;
12
+ postTransmit(stream: Stream): Promise<void>;
13
+ abort(error: Error): Promise<void>;
11
14
  close(): Promise<void>;
12
15
  }
13
16
  //# 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;AAEjF,qBAAa,eAAgB,SAAQ,WAAW;IAGlC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IAFrD,aAAa,EAAE,UAAU,CAAC;gBAEF,MAAM,EAAE,qBAAqB;IAM5D,wBAAwB;IAYxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IAmBlB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAcpC,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IA2E/C,YAAY,CAAC,MAAM,EAAE,MAAM;IAU3B,KAAK,CAAC,KAAK,EAAE,KAAK;IAMlB,KAAK;CAKZ"}
@@ -1,52 +1,97 @@
1
- import { Uint8ArrayList, byteStream, } from '@olane/o-config';
2
- import { oConnection, oError, oErrorCodes, oResponse, } from '@olane/o-core';
1
+ import { CoreUtils, oConnection, oError, oErrorCodes, oResponse, } from '@olane/o-core';
3
2
  export class oNodeConnection extends oConnection {
4
3
  constructor(config) {
5
4
  super(config);
6
5
  this.config = config;
7
6
  this.p2pConnection = config.p2pConnection;
7
+ this.setupConnectionListeners();
8
8
  }
9
- async read(source) {
10
- const bytes = byteStream(source);
11
- const output = await bytes.read({
12
- signal: AbortSignal.timeout(120000), // 2 min timeout
9
+ setupConnectionListeners() {
10
+ this.logger.debug('Setting up connection listeners for address: ' +
11
+ this.nextHopAddress.toString());
12
+ this.p2pConnection?.addEventListener('close', () => {
13
+ this.logger.debug('Connection closed for address: ' + this.nextHopAddress.toString());
13
14
  });
14
- const outputObj = output instanceof Uint8ArrayList ? output.subarray() : output;
15
- const jsonStr = new TextDecoder().decode(outputObj);
16
- return JSON.parse(jsonStr);
17
15
  }
18
- validate() {
16
+ validate(stream) {
19
17
  if (this.config.p2pConnection.status !== 'open') {
20
18
  throw new Error('Connection is not valid');
21
19
  }
22
20
  // do nothing
21
+ if (!stream || (stream.status !== 'open' && stream.status !== 'reset')) {
22
+ throw new oError(oErrorCodes.FAILED_TO_DIAL_TARGET, 'Failed to dial target');
23
+ }
24
+ if (stream.status === 'reset') {
25
+ throw new oError(oErrorCodes.CONNECTION_LIMIT_REACHED, 'Connection limit reached');
26
+ }
27
+ }
28
+ async getOrCreateStream() {
29
+ if (this.p2pConnection.status !== 'open') {
30
+ throw new oError(oErrorCodes.INVALID_STATE, 'Connection not open');
31
+ }
32
+ return this.p2pConnection.newStream(this.nextHopAddress.protocol, {
33
+ signal: this.abortSignal,
34
+ maxOutboundStreams: process.env.MAX_OUTBOUND_STREAMS
35
+ ? parseInt(process.env.MAX_OUTBOUND_STREAMS)
36
+ : 1000,
37
+ runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
38
+ });
23
39
  }
24
40
  async transmit(request) {
25
41
  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');
42
+ if (this.config.runOnLimitedConnection) {
43
+ this.logger.debug('Running on limited connection...');
35
44
  }
45
+ const stream = await this.getOrCreateStream();
46
+ this.validate(stream);
36
47
  // Send the data with backpressure handling (libp2p v3 best practice)
37
48
  const data = new TextEncoder().encode(request.toString());
38
49
  const sent = stream.send(data);
50
+ let lastResponse;
51
+ await new Promise((resolve, reject) => {
52
+ const abortHandler = async () => {
53
+ try {
54
+ await stream.abort(new Error('Request aborted'));
55
+ }
56
+ catch (e) {
57
+ // Stream may already be closed
58
+ }
59
+ reject(new Error('Request aborted'));
60
+ };
61
+ // Listen for abort signal
62
+ if (this.abortSignal) {
63
+ this.abortSignal.addEventListener('abort', abortHandler);
64
+ }
65
+ stream.addEventListener('message', async (event) => {
66
+ const response = await CoreUtils.processStreamResponse(event);
67
+ this.emitter.emit('chunk', response);
68
+ // marked as the last chunk let's close
69
+ if (response.result._last || !response.result._isStreaming) {
70
+ lastResponse = response;
71
+ // Clean up abort listener before closing
72
+ if (this.abortSignal) {
73
+ this.abortSignal.removeEventListener('abort', abortHandler);
74
+ }
75
+ resolve(true);
76
+ }
77
+ });
78
+ });
79
+ stream.addEventListener('close', async () => {
80
+ this.logger.debug('Stream closed by remote peer, closing connection...');
81
+ stream.close().catch((error) => {
82
+ this.logger.error('Error closing stream: ', error);
83
+ });
84
+ });
39
85
  // If send() returns false, wait for the stream to drain before continuing
40
86
  if (!sent) {
41
87
  this.logger.debug('Stream buffer full, waiting for drain...');
42
- await stream.onDrain({ signal: AbortSignal.timeout(30000) }); // 30 second timeout
88
+ await stream.onDrain({
89
+ signal: AbortSignal.timeout(this.config.drainTimeoutMs ?? 30000),
90
+ }); // Default: 30 second timeout
43
91
  }
44
- const res = await this.read(stream);
45
- await stream.close();
46
- // process the response
47
- const response = new oResponse({
48
- ...res.result,
49
- });
92
+ // handle cleanup of the stream
93
+ await this.postTransmit(stream);
94
+ const response = oResponse.fromJSON(lastResponse);
50
95
  return response;
51
96
  }
52
97
  catch (error) {
@@ -56,6 +101,21 @@ export class oNodeConnection extends oConnection {
56
101
  throw error;
57
102
  }
58
103
  }
104
+ async postTransmit(stream) {
105
+ if (stream.status === 'open') {
106
+ stream.close().catch((error) => {
107
+ this.logger.error('Error closing stream after transmission: ', error);
108
+ });
109
+ }
110
+ else {
111
+ this.logger.debug('Stream is not open, skipping cleanup');
112
+ }
113
+ }
114
+ async abort(error) {
115
+ this.logger.debug('Aborting connection');
116
+ await this.p2pConnection.abort(error);
117
+ this.logger.debug('Connection aborted');
118
+ }
59
119
  async close() {
60
120
  this.logger.debug('Closing connection');
61
121
  await this.p2pConnection.close();
@@ -1,17 +1,31 @@
1
1
  import { oAddress, oConnectionConfig, oConnectionManager } from '@olane/o-core';
2
- import { oConnection } from '@olane/o-core';
2
+ import { 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
+ readonly config: oNodeConnectionManagerConfig;
6
7
  private p2pNode;
8
+ private defaultReadTimeoutMs?;
9
+ private defaultDrainTimeoutMs?;
7
10
  constructor(config: oNodeConnectionManagerConfig);
11
+ getOrCreateConnection(nextHopAddress: oAddress, address: oAddress): Promise<Connection>;
8
12
  /**
9
- * Connect to a given address with exponential backoff retry
10
- * @param address - The address to connect to
13
+ * Connect to a given address, reusing libp2p connections when possible
14
+ * @param config - Connection configuration
11
15
  * @returns The connection object
12
16
  */
13
17
  connect(config: oConnectionConfig): Promise<oNodeConnection>;
18
+ /**
19
+ * Check if libp2p has an active connection to the target peer
20
+ * @param address - The address to check
21
+ * @returns true if an active connection exists
22
+ */
14
23
  isCached(address: oAddress): boolean;
15
- getCachedConnection(address: oAddress): oConnection | null;
24
+ /**
25
+ * Get an existing libp2p connection to the target peer
26
+ * @param address - The address to get a connection for
27
+ * @returns The libp2p Connection object or null if not found
28
+ */
29
+ getCachedLibp2pConnection(address: oAddress): Connection | null;
16
30
  }
17
31
  //# 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,EAAU,UAAU,EAAU,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,4BAA4B,EAAE,MAAM,kDAAkD,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,qBAAa,sBAAuB,SAAQ,kBAAkB;IAKhD,QAAQ,CAAC,MAAM,EAAE,4BAA4B;IAJzD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,OAAO,CAAC,qBAAqB,CAAC,CAAS;gBAElB,MAAM,EAAE,4BAA4B;IAOnD,qBAAqB,CACzB,cAAc,EAAE,QAAQ,EACxB,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,UAAU,CAAC;IA2BtB;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IA6BlE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IA4BpC;;;;OAIG;IACH,yBAAyB,CAAC,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,IAAI;CA0BhE"}
@@ -3,86 +3,100 @@ 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;
6
7
  this.p2pNode = config.p2pNode;
8
+ this.defaultReadTimeoutMs = config.defaultReadTimeoutMs;
9
+ this.defaultDrainTimeoutMs = config.defaultDrainTimeoutMs;
10
+ }
11
+ async getOrCreateConnection(nextHopAddress, address) {
12
+ // Check if libp2p already has an active connection to this peer
13
+ const existingConnection = this.getCachedLibp2pConnection(nextHopAddress);
14
+ let p2pConnection;
15
+ if (existingConnection && existingConnection.status === 'open') {
16
+ this.logger.debug('Reusing existing libp2p connection for address: ' + address.toString());
17
+ p2pConnection = existingConnection;
18
+ }
19
+ else {
20
+ // No existing connection or connection is closed, dial a new one
21
+ this.logger.debug('Dialing new connection for address: ' + address.toString());
22
+ p2pConnection = await this.p2pNode.dial(nextHopAddress.libp2pTransports.map((ma) => ma.toMultiaddr()));
23
+ }
24
+ return p2pConnection;
7
25
  }
8
26
  /**
9
- * Connect to a given address with exponential backoff retry
10
- * @param address - The address to connect to
27
+ * Connect to a given address, reusing libp2p connections when possible
28
+ * @param config - Connection configuration
11
29
  * @returns The connection object
12
30
  */
13
31
  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;
32
+ const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, } = config;
33
+ const p2pConnection = await this.getOrCreateConnection(nextHopAddress, address);
34
+ const connection = new oNodeConnection({
35
+ nextHopAddress: nextHopAddress,
36
+ address: address,
37
+ p2pConnection: p2pConnection,
38
+ callerAddress: callerAddress,
39
+ readTimeoutMs: readTimeoutMs ?? this.defaultReadTimeoutMs,
40
+ drainTimeoutMs: drainTimeoutMs ?? this.defaultDrainTimeoutMs,
41
+ isStream: config.isStream ?? false,
42
+ abortSignal: config.abortSignal,
43
+ runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
44
+ });
45
+ return connection;
46
+ }
47
+ /**
48
+ * Check if libp2p has an active connection to the target peer
49
+ * @param address - The address to check
50
+ * @returns true if an active connection exists
51
+ */
52
+ isCached(address) {
53
+ try {
54
+ const nodeAddress = address;
55
+ if (!nodeAddress.libp2pTransports ||
56
+ nodeAddress.libp2pTransports.length === 0) {
57
+ return false;
23
58
  }
24
- else {
25
- // cached item is not valid, remove it
26
- this.cache.delete(nextHopAddress.toString());
59
+ // Extract peer ID from the first transport
60
+ const peerIdString = nodeAddress.libp2pTransports[0].toPeerId();
61
+ if (!peerIdString) {
62
+ return false;
27
63
  }
64
+ this.logger.debug('Peer ID string:', peerIdString);
65
+ // 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
66
+ const connections = this.p2pNode.getConnections(peerIdString); // ignore since converting to a proper peer id breaks the browser implementation
67
+ // Check if we have at least one open connection
68
+ return connections.some((conn) => conn.status === 'open');
28
69
  }
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
- }
70
+ catch (error) {
71
+ this.logger.debug('Error checking cached connection:', error);
72
+ return false;
64
73
  }
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
- }
69
- isCached(address) {
70
- return this.cache.has(address.toString());
71
74
  }
72
- getCachedConnection(address) {
73
- const key = address.toString();
75
+ /**
76
+ * Get an existing libp2p connection to the target peer
77
+ * @param address - The address to get a connection for
78
+ * @returns The libp2p Connection object or null if not found
79
+ */
80
+ getCachedLibp2pConnection(address) {
74
81
  try {
75
- const connection = this.cache.get(key);
76
- if (!connection) {
77
- throw new Error('Connection not found in cache');
82
+ const nodeAddress = address;
83
+ if (!nodeAddress.libp2pTransports ||
84
+ nodeAddress.libp2pTransports.length === 0) {
85
+ return null;
86
+ }
87
+ // Extract peer ID from the first transport
88
+ const peerIdString = nodeAddress.libp2pTransports[0].toPeerId();
89
+ if (!peerIdString) {
90
+ return null;
78
91
  }
79
- connection.validate();
80
- return connection;
92
+ const connections = this.p2pNode.getConnections(peerIdString); // ignore since converting to a proper peer id breaks the browser implementation
93
+ // Return the first open connection, or null if none exist
94
+ const openConnection = connections.find((conn) => conn.status === 'open');
95
+ return openConnection || null;
81
96
  }
82
97
  catch (error) {
83
- this.cache.delete(key);
84
- this.logger.error('Error getting cached connection:', error);
98
+ this.logger.debug('Error getting cached connection:', error);
99
+ return null;
85
100
  }
86
- return null;
87
101
  }
88
102
  }
@@ -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
+ }
@@ -1,5 +1,5 @@
1
1
  export * from './o-node.js';
2
- export * from './utils/network.utils.js';
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,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"}
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/network.utils.js';
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 {};
@@ -0,0 +1,46 @@
1
+ import { oAddress, NodeState, oNotificationManager } from '@olane/o-core';
2
+ import { oNodeAddress } from '../router/o-node.address.js';
3
+ import { oNodeConfig } from './o-node.config.js';
4
+ /**
5
+ * Interface for nodes that support reconnection management.
6
+ * This interface defines the contract that oReconnectionManager needs
7
+ * to perform reconnection operations without creating a circular dependency.
8
+ */
9
+ export interface IReconnectableNode {
10
+ /**
11
+ * The node's configuration
12
+ */
13
+ config: oNodeConfig;
14
+ /**
15
+ * The node's current address
16
+ */
17
+ address: oNodeAddress;
18
+ /**
19
+ * The node's current state
20
+ */
21
+ state: NodeState;
22
+ /**
23
+ * The notification manager for subscribing to events
24
+ */
25
+ notificationManager: oNotificationManager;
26
+ /**
27
+ * Register with the parent node
28
+ */
29
+ registerParent(): Promise<void>;
30
+ /**
31
+ * Register with the leader's global registry
32
+ */
33
+ register(): Promise<void>;
34
+ useSelf(request?: any): Promise<any>;
35
+ /**
36
+ * Execute a method on another node
37
+ */
38
+ use(address: oAddress, data?: {
39
+ method?: string;
40
+ params?: {
41
+ [key: string]: any;
42
+ };
43
+ id?: string;
44
+ }): Promise<any>;
45
+ }
46
+ //# sourceMappingURL=i-reconnectable-node.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -3,5 +3,48 @@ import { oNodeAddress } from '../router/o-node.address.js';
3
3
  export interface oNodeConfig extends oCoreConfig {
4
4
  leader: oNodeAddress | null;
5
5
  parent: oNodeAddress | null;
6
+ /**
7
+ * Connection heartbeat configuration (libp2p-native pings)
8
+ * Detects dead connections via periodic pings using libp2p's ping service
9
+ */
10
+ connectionHeartbeat?: {
11
+ enabled?: boolean;
12
+ intervalMs?: number;
13
+ timeoutMs?: number;
14
+ failureThreshold?: number;
15
+ checkChildren?: boolean;
16
+ checkParent?: boolean;
17
+ checkLeader?: boolean;
18
+ };
19
+ /**
20
+ * Automatic reconnection configuration
21
+ * Handles parent connection failures and attempts to reconnect
22
+ */
23
+ reconnection?: {
24
+ enabled?: boolean;
25
+ maxAttempts?: number;
26
+ baseDelayMs?: number;
27
+ maxDelayMs?: number;
28
+ useLeaderFallback?: boolean;
29
+ parentDiscoveryIntervalMs?: number;
30
+ parentDiscoveryMaxDelayMs?: number;
31
+ };
32
+ /**
33
+ * Connection timeout configuration
34
+ * Controls timeouts for stream read and drain operations in connections
35
+ */
36
+ connectionTimeouts?: {
37
+ /**
38
+ * Timeout in milliseconds for reading response data from a stream
39
+ * Default: 120000 (2 minutes)
40
+ */
41
+ readTimeoutMs?: number;
42
+ /**
43
+ * Timeout in milliseconds for waiting for stream buffer to drain when backpressure occurs
44
+ * Default: 30000 (30 seconds)
45
+ */
46
+ drainTimeoutMs?: number;
47
+ };
48
+ runOnLimitedConnection?: boolean;
6
49
  }
7
50
  //# 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;CAC7B"}
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,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,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,YAAY,CAAC,EAAE;QACb,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,yBAAyB,CAAC,EAAE,MAAM,CAAC;QACnC,yBAAyB,CAAC,EAAE,MAAM,CAAC;KACpC,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"}