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

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 +3 -2
  6. package/dist/src/connection/o-node-connection.d.ts.map +1 -1
  7. package/dist/src/connection/o-node-connection.js +58 -18
  8. package/dist/src/connection/o-node-connection.manager.d.ts +17 -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 +75 -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 +27 -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"}
@@ -1,13 +1,14 @@
1
- import { Connection, Stream } from '@olane/o-config';
1
+ import { Connection } 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
4
  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>;
8
+ setupConnectionListeners(): void;
9
9
  validate(): void;
10
10
  transmit(request: oRequest): Promise<oResponse>;
11
+ abort(error: Error): Promise<void>;
11
12
  close(): Promise<void>;
12
13
  }
13
14
  //# 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,iBAAiB,CAAC;AAC7C,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;IAOF,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IAyF/C,KAAK,CAAC,KAAK,EAAE,KAAK;IAMlB,KAAK;CAKZ"}
@@ -1,19 +1,17 @@
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
16
  validate() {
19
17
  if (this.config.p2pConnection.status !== 'open') {
@@ -23,9 +21,15 @@ export class oNodeConnection extends oConnection {
23
21
  }
24
22
  async transmit(request) {
25
23
  try {
24
+ if (this.config.runOnLimitedConnection) {
25
+ this.logger.debug('Running on limited connection...');
26
+ }
26
27
  const stream = await this.p2pConnection.newStream(this.nextHopAddress.protocol, {
27
- maxOutboundStreams: Infinity,
28
- runOnLimitedConnection: true, // TODO: should this be configurable?
28
+ signal: this.abortSignal,
29
+ maxOutboundStreams: process.env.MAX_OUTBOUND_STREAMS
30
+ ? parseInt(process.env.MAX_OUTBOUND_STREAMS)
31
+ : 1000,
32
+ runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
29
33
  });
30
34
  if (!stream || (stream.status !== 'open' && stream.status !== 'reset')) {
31
35
  throw new oError(oErrorCodes.FAILED_TO_DIAL_TARGET, 'Failed to dial target');
@@ -36,17 +40,48 @@ export class oNodeConnection extends oConnection {
36
40
  // Send the data with backpressure handling (libp2p v3 best practice)
37
41
  const data = new TextEncoder().encode(request.toString());
38
42
  const sent = stream.send(data);
43
+ let lastResponse;
44
+ await new Promise((resolve, reject) => {
45
+ const abortHandler = async () => {
46
+ try {
47
+ await stream.abort(new Error('Request aborted'));
48
+ }
49
+ catch (e) {
50
+ // Stream may already be closed
51
+ }
52
+ reject(new Error('Request aborted'));
53
+ };
54
+ // Listen for abort signal
55
+ if (this.abortSignal) {
56
+ this.abortSignal.addEventListener('abort', abortHandler);
57
+ }
58
+ stream.addEventListener('message', async (event) => {
59
+ const response = await CoreUtils.processStreamResponse(event);
60
+ this.emitter.emit('chunk', response);
61
+ // marked as the last chunk let's close
62
+ if (response.result._last || !response.result._isStreaming) {
63
+ // this.logger.debug('Last chunk received...');
64
+ lastResponse = response;
65
+ // Clean up abort listener before closing
66
+ if (this.abortSignal) {
67
+ this.abortSignal.removeEventListener('abort', abortHandler);
68
+ }
69
+ await stream.close();
70
+ resolve(true);
71
+ }
72
+ });
73
+ });
39
74
  // If send() returns false, wait for the stream to drain before continuing
40
75
  if (!sent) {
41
76
  this.logger.debug('Stream buffer full, waiting for drain...');
42
- await stream.onDrain({ signal: AbortSignal.timeout(30000) }); // 30 second timeout
77
+ await stream.onDrain({
78
+ signal: AbortSignal.timeout(this.config.drainTimeoutMs ?? 30000),
79
+ }); // Default: 30 second timeout
43
80
  }
44
- const res = await this.read(stream);
45
- await stream.close();
46
- // process the response
47
- const response = new oResponse({
48
- ...res.result,
49
- });
81
+ if (stream.status === 'open') {
82
+ await stream.abort(new Error('Connection closed'));
83
+ }
84
+ const response = oResponse.fromJSON(lastResponse);
50
85
  return response;
51
86
  }
52
87
  catch (error) {
@@ -56,6 +91,11 @@ export class oNodeConnection extends oConnection {
56
91
  throw error;
57
92
  }
58
93
  }
94
+ async abort(error) {
95
+ this.logger.debug('Aborting connection');
96
+ await this.p2pConnection.abort(error);
97
+ this.logger.debug('Connection aborted');
98
+ }
59
99
  async close() {
60
100
  this.logger.debug('Closing connection');
61
101
  await this.p2pConnection.close();
@@ -1,17 +1,30 @@
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);
8
11
  /**
9
- * Connect to a given address with exponential backoff retry
10
- * @param address - The address to connect to
12
+ * Connect to a given address, reusing libp2p connections when possible
13
+ * @param config - Connection configuration
11
14
  * @returns The connection object
12
15
  */
13
16
  connect(config: oConnectionConfig): Promise<oNodeConnection>;
17
+ /**
18
+ * Check if libp2p has an active connection to the target peer
19
+ * @param address - The address to check
20
+ * @returns true if an active connection exists
21
+ */
14
22
  isCached(address: oAddress): boolean;
15
- getCachedConnection(address: oAddress): oConnection | null;
23
+ /**
24
+ * Get an existing libp2p connection to the target peer
25
+ * @param address - The address to get a connection for
26
+ * @returns The libp2p Connection object or null if not found
27
+ */
28
+ getCachedLibp2pConnection(address: oAddress): Connection | null;
16
29
  }
17
30
  //# 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;IAOzD;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IAgDlE;;;;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,96 @@ 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;
7
10
  }
8
11
  /**
9
- * Connect to a given address with exponential backoff retry
10
- * @param address - The address to connect to
12
+ * Connect to a given address, reusing libp2p connections when possible
13
+ * @param config - Connection configuration
11
14
  * @returns The connection object
12
15
  */
13
16
  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;
23
- }
24
- else {
25
- // cached item is not valid, remove it
26
- this.cache.delete(nextHopAddress.toString());
27
- }
17
+ const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, } = config;
18
+ // Check if libp2p already has an active connection to this peer
19
+ const existingConnection = this.getCachedLibp2pConnection(nextHopAddress);
20
+ let p2pConnection;
21
+ if (existingConnection && existingConnection.status === 'open') {
22
+ this.logger.debug('Reusing existing libp2p connection for address: ' + address.toString());
23
+ p2pConnection = existingConnection;
28
24
  }
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
- }
25
+ else {
26
+ // No existing connection or connection is closed, dial a new one
27
+ this.logger.debug('Dialing new connection for address: ' + address.toString());
28
+ p2pConnection = await this.p2pNode.dial(nextHopAddress.libp2pTransports.map((ma) => ma.toMultiaddr()));
64
29
  }
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;
30
+ const connection = new oNodeConnection({
31
+ nextHopAddress: nextHopAddress,
32
+ address: address,
33
+ p2pConnection: p2pConnection,
34
+ callerAddress: callerAddress,
35
+ readTimeoutMs: readTimeoutMs ?? this.defaultReadTimeoutMs,
36
+ drainTimeoutMs: drainTimeoutMs ?? this.defaultDrainTimeoutMs,
37
+ isStream: config.isStream ?? false,
38
+ abortSignal: config.abortSignal,
39
+ runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
40
+ });
41
+ return connection;
68
42
  }
43
+ /**
44
+ * Check if libp2p has an active connection to the target peer
45
+ * @param address - The address to check
46
+ * @returns true if an active connection exists
47
+ */
69
48
  isCached(address) {
70
- return this.cache.has(address.toString());
49
+ try {
50
+ const nodeAddress = address;
51
+ if (!nodeAddress.libp2pTransports ||
52
+ nodeAddress.libp2pTransports.length === 0) {
53
+ return false;
54
+ }
55
+ // Extract peer ID from the first transport
56
+ const peerIdString = nodeAddress.libp2pTransports[0].toPeerId();
57
+ if (!peerIdString) {
58
+ return false;
59
+ }
60
+ this.logger.debug('Peer ID string:', peerIdString);
61
+ // 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
62
+ const connections = this.p2pNode.getConnections(peerIdString); // ignore since converting to a proper peer id breaks the browser implementation
63
+ // Check if we have at least one open connection
64
+ return connections.some((conn) => conn.status === 'open');
65
+ }
66
+ catch (error) {
67
+ this.logger.debug('Error checking cached connection:', error);
68
+ return false;
69
+ }
71
70
  }
72
- getCachedConnection(address) {
73
- const key = address.toString();
71
+ /**
72
+ * Get an existing libp2p connection to the target peer
73
+ * @param address - The address to get a connection for
74
+ * @returns The libp2p Connection object or null if not found
75
+ */
76
+ getCachedLibp2pConnection(address) {
74
77
  try {
75
- const connection = this.cache.get(key);
76
- if (!connection) {
77
- throw new Error('Connection not found in cache');
78
+ const nodeAddress = address;
79
+ if (!nodeAddress.libp2pTransports ||
80
+ nodeAddress.libp2pTransports.length === 0) {
81
+ return null;
82
+ }
83
+ // Extract peer ID from the first transport
84
+ const peerIdString = nodeAddress.libp2pTransports[0].toPeerId();
85
+ if (!peerIdString) {
86
+ return null;
78
87
  }
79
- connection.validate();
80
- return connection;
88
+ const connections = this.p2pNode.getConnections(peerIdString); // ignore since converting to a proper peer id breaks the browser implementation
89
+ // Return the first open connection, or null if none exist
90
+ const openConnection = connections.find((conn) => conn.status === 'open');
91
+ return openConnection || null;
81
92
  }
82
93
  catch (error) {
83
- this.cache.delete(key);
84
- this.logger.error('Error getting cached connection:', error);
94
+ this.logger.debug('Error getting cached connection:', error);
95
+ return null;
85
96
  }
86
- return null;
87
97
  }
88
98
  }
@@ -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"}