@olane/o-node 0.7.46 → 0.7.48

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.
@@ -5,58 +5,55 @@ import { oNodeConnection } from './o-node-connection.js';
5
5
  export declare class oNodeConnectionManager extends oConnectionManager {
6
6
  readonly config: oNodeConnectionManagerConfig;
7
7
  protected p2pNode: Libp2p;
8
- private defaultReadTimeoutMs?;
9
- private defaultDrainTimeoutMs?;
10
- private connectionsByAddress;
11
- private pendingDialsByAddress;
12
- /** Cache of oNodeConnection instances keyed by p2p connection ID */
13
- private nodeConnectionByConnectionId;
8
+ protected defaultReadTimeoutMs?: number;
9
+ protected defaultDrainTimeoutMs?: number;
10
+ /** Single cache of oNodeConnection instances keyed by address */
11
+ protected cachedConnections: Map<string, oNodeConnection[]>;
12
+ protected pendingDialsByAddress: Map<string, Promise<Connection>>;
14
13
  constructor(config: oNodeConnectionManagerConfig);
15
14
  /**
16
15
  * Set up listeners to maintain connection cache state
17
16
  */
18
- private setupConnectionListeners;
17
+ protected setupConnectionListeners(): void;
19
18
  /**
20
19
  * Build a stable cache key from an address.
21
20
  *
22
21
  * We key the cache by address value (e.g., "o://my-tool") to maintain
23
22
  * a simple one-to-one mapping between addresses and connections.
24
23
  */
25
- private getAddressKey;
24
+ protected getAddressKey(address: oAddress): string | null;
26
25
  /**
27
26
  * Extract peer ID string from an address
28
27
  * @param address - The address to extract peer ID from
29
28
  * @returns The peer ID string or null if not found
30
29
  */
31
- private getPeerIdFromAddress;
30
+ protected getPeerIdFromAddress(address: oAddress): string | null;
32
31
  /**
33
- * Select the best connection from an array of connections.
34
- * Prioritizes connections with active streams, then by direction based on context.
32
+ * Get the first valid (open) connection for the given address key.
33
+ * Cleans up stale connections from the cache automatically.
35
34
  *
36
- * @param connections - Array of connections to choose from
37
- * @param reuseContext - If true, prefer inbound connections; otherwise prefer outbound
38
- * @returns The best connection or null if none are suitable
39
- */
40
- private selectBestConnection;
41
- /**
42
- * Get a cached oNodeConnection for the given p2p connection if it exists and is valid.
43
- * @param p2pConnection - The p2p connection to look up
35
+ * @param addressKey - The address key to look up
44
36
  * @returns A valid oNodeConnection or null if none found
45
37
  */
46
- private getCachedNodeConnection;
38
+ protected getValidConnection(addressKey: string): oNodeConnection | null;
47
39
  /**
48
- * Cache an oNodeConnection by its p2p connection ID for potential reuse.
40
+ * Cache an oNodeConnection by its address key.
49
41
  * @param conn - The oNodeConnection to cache
42
+ * @param addressKey - The address key to cache under
43
+ */
44
+ protected cacheConnection(conn: oNodeConnection, addressKey: string): void;
45
+ /**
46
+ * Get or create a raw p2p connection to the given address.
47
+ * Subclasses can override connect() and use this method to get the underlying p2p connection.
50
48
  */
51
- private cacheNodeConnection;
52
- getOrCreateConnection(nextHopAddress: oAddress, address: oAddress): Promise<Connection>;
53
- private performDial;
49
+ protected getOrCreateP2pConnection(nextHopAddress: oAddress, addressKey: string): Promise<Connection>;
50
+ protected performDial(nextHopAddress: oAddress, addressKey: string): Promise<Connection>;
54
51
  answer(config: oConnectionConfig & {
55
52
  p2pConnection: Connection;
56
53
  reuse?: boolean;
57
54
  }): Promise<oNodeConnection>;
58
55
  /**
59
- * Connect to a given address, reusing oNodeConnection and libp2p connections when possible
56
+ * Connect to a given address, reusing oNodeConnection when possible
60
57
  * @param config - Connection configuration
61
58
  * @returns The connection object
62
59
  */
@@ -68,11 +65,11 @@ export declare class oNodeConnectionManager extends oConnectionManager {
68
65
  */
69
66
  isCached(address: oAddress): boolean;
70
67
  /**
71
- * Get an existing connection to the target peer (from cache or libp2p)
68
+ * Get an existing cached oNodeConnection for the target address
72
69
  * @param address - The address to get a connection for
73
- * @returns The Connection object or null if not found
70
+ * @returns The oNodeConnection or null if not found
74
71
  */
75
- getCachedLibp2pConnection(address: oAddress): Connection | null;
72
+ getCachedConnection(address: oAddress): oNodeConnection | null;
76
73
  /**
77
74
  * Get cache statistics for monitoring and debugging
78
75
  * @returns Object containing cache statistics
@@ -81,7 +78,6 @@ export declare class oNodeConnectionManager extends oConnectionManager {
81
78
  cachedAddresses: number;
82
79
  totalCachedConnections: number;
83
80
  pendingDials: number;
84
- cachedNodeConnections: number;
85
81
  connectionsByPeer: Array<{
86
82
  peerId: string;
87
83
  status: string;
@@ -93,10 +89,5 @@ export declare class oNodeConnectionManager extends oConnectionManager {
93
89
  * @returns Number of connections removed
94
90
  */
95
91
  cleanupStaleConnections(): number;
96
- /**
97
- * Clean up all stale oNodeConnections from cache
98
- * @returns Number of oNodeConnections removed
99
- */
100
- cleanupStaleNodeConnections(): number;
101
92
  }
102
93
  //# 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,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;IAUhD,QAAQ,CAAC,MAAM,EAAE,4BAA4B;IATzD,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,OAAO,CAAC,qBAAqB,CAAC,CAAS;IACvC,OAAO,CAAC,oBAAoB,CAAwC;IACpE,OAAO,CAAC,qBAAqB,CAA+C;IAC5E,oEAAoE;IACpE,OAAO,CAAC,4BAA4B,CACxB;gBAES,MAAM,EAAE,4BAA4B;IAWzD;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAyChC;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IASrB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;;;;;;OAOG;IACH,OAAO,CAAC,oBAAoB;IA8D5B;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAc/B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAIrB,qBAAqB,CACzB,cAAc,EAAE,QAAQ,EACxB,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,UAAU,CAAC;YAgFR,WAAW;IAwBnB,MAAM,CACV,MAAM,EAAE,iBAAiB,GAAG;QAAE,aAAa,EAAE,UAAU,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GACzE,OAAO,CAAC,eAAe,CAAC;IA2C3B;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IA6ClE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IAiDpC;;;;OAIG;IACH,yBAAyB,CAAC,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,IAAI;IA6D/D;;;OAGG;IACH,aAAa,IAAI;QACf,eAAe,EAAE,MAAM,CAAC;QACxB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,YAAY,EAAE,MAAM,CAAC;QACrB,qBAAqB,EAAE,MAAM,CAAC;QAC9B,iBAAiB,EAAE,KAAK,CAAC;YACvB,MAAM,EAAE,MAAM,CAAC;YACf,MAAM,EAAE,MAAM,CAAC;YACf,UAAU,EAAE,MAAM,CAAC;SACpB,CAAC,CAAC;KACJ;IA6BD;;;OAGG;IACH,uBAAuB,IAAI,MAAM;IA6BjC;;;OAGG;IACH,2BAA2B,IAAI,MAAM;CAgBtC"}
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,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,4BAA4B,EAAE,MAAM,kDAAkD,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,qBAAa,sBAAuB,SAAQ,kBAAkB;IAQhD,QAAQ,CAAC,MAAM,EAAE,4BAA4B;IAPzD,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACxC,SAAS,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IACzC,iEAAiE;IACjE,SAAS,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAa;IACxE,SAAS,CAAC,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAa;gBAEzD,MAAM,EAAE,4BAA4B;IAWzD;;OAEG;IACH,SAAS,CAAC,wBAAwB,IAAI,IAAI;IA+B1C;;;;;OAKG;IACH,SAAS,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI;IASzD;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI;IAahE;;;;;;OAMG;IACH,SAAS,CAAC,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAkBxE;;;;OAIG;IACH,SAAS,CAAC,eAAe,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAY1E;;;OAGG;cACa,wBAAwB,CACtC,cAAc,EAAE,QAAQ,EACxB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,CAAC;cAoCN,WAAW,CACzB,cAAc,EAAE,QAAQ,EACxB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,CAAC;IAqBhB,MAAM,CACV,MAAM,EAAE,iBAAiB,GAAG;QAAE,aAAa,EAAE,UAAU,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GACzE,OAAO,CAAC,eAAe,CAAC;IAqD3B;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IAyDlE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IAcpC;;;;OAIG;IACH,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,eAAe,GAAG,IAAI;IAc9D;;;OAGG;IACH,aAAa,IAAI;QACf,eAAe,EAAE,MAAM,CAAC;QACxB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,YAAY,EAAE,MAAM,CAAC;QACrB,iBAAiB,EAAE,KAAK,CAAC;YACvB,MAAM,EAAE,MAAM,CAAC;YACf,MAAM,EAAE,MAAM,CAAC;YACf,UAAU,EAAE,MAAM,CAAC;SACpB,CAAC,CAAC;KACJ;IAyBD;;;OAGG;IACH,uBAAuB,IAAI,MAAM;CAuBlC"}
@@ -4,10 +4,9 @@ export class oNodeConnectionManager extends oConnectionManager {
4
4
  constructor(config) {
5
5
  super(config);
6
6
  this.config = config;
7
- this.connectionsByAddress = new Map();
7
+ /** Single cache of oNodeConnection instances keyed by address */
8
+ this.cachedConnections = new Map();
8
9
  this.pendingDialsByAddress = new Map();
9
- /** Cache of oNodeConnection instances keyed by p2p connection ID */
10
- this.nodeConnectionByConnectionId = new Map();
11
10
  this.p2pNode = config.p2pNode;
12
11
  this.defaultReadTimeoutMs = config.defaultReadTimeoutMs;
13
12
  this.defaultDrainTimeoutMs = config.defaultDrainTimeoutMs;
@@ -24,31 +23,18 @@ export class oNodeConnectionManager extends oConnectionManager {
24
23
  if (!connection) {
25
24
  return;
26
25
  }
27
- // Clean up libp2p connection cache
28
- // for (const [
29
- // addressKey,
30
- // cachedConnections,
31
- // ] of this.connectionsByAddress.entries()) {
32
- // const index = cachedConnections.indexOf(connection);
33
- // if (index !== -1) {
34
- // this.logger.debug(
35
- // 'Connection closed, removing from cache for address:',
36
- // addressKey,
37
- // );
38
- // cachedConnections.splice(index, 1);
39
- // // Remove the address key entirely if no connections remain
40
- // if (cachedConnections.length === 0) {
41
- // this.connectionsByAddress.delete(addressKey);
42
- // } else {
43
- // this.connectionsByAddress.set(addressKey, cachedConnections);
44
- // }
45
- // }
46
- // }
47
- // Clean up oNodeConnection cache by connection ID
48
26
  const connectionId = connection.id;
49
- if (this.nodeConnectionByConnectionId.has(connectionId)) {
50
- this.logger.debug('Connection closed, removing oNodeConnection for connection ID:', connectionId);
51
- this.nodeConnectionByConnectionId.delete(connectionId);
27
+ // Clean up cached connections by filtering out the closed connection
28
+ for (const [key, conns] of this.cachedConnections.entries()) {
29
+ const filtered = conns.filter((c) => c.p2pConnection.id !== connectionId);
30
+ if (filtered.length === 0) {
31
+ this.logger.debug('Connection closed, removing all cached connections for address:', key);
32
+ this.cachedConnections.delete(key);
33
+ }
34
+ else if (filtered.length !== conns.length) {
35
+ this.logger.debug('Connection closed, updating cached connections for address:', key);
36
+ this.cachedConnections.set(key, filtered);
37
+ }
52
38
  }
53
39
  });
54
40
  }
@@ -86,117 +72,53 @@ export class oNodeConnectionManager extends oConnectionManager {
86
72
  }
87
73
  }
88
74
  /**
89
- * Select the best connection from an array of connections.
90
- * Prioritizes connections with active streams, then by direction based on context.
75
+ * Get the first valid (open) connection for the given address key.
76
+ * Cleans up stale connections from the cache automatically.
91
77
  *
92
- * @param connections - Array of connections to choose from
93
- * @param reuseContext - If true, prefer inbound connections; otherwise prefer outbound
94
- * @returns The best connection or null if none are suitable
78
+ * @param addressKey - The address key to look up
79
+ * @returns A valid oNodeConnection or null if none found
95
80
  */
96
- selectBestConnection(connections, reuseContext = false) {
97
- // Filter to only open connections
98
- const openConnections = connections.filter((c) => c.status === 'open');
99
- if (openConnections.length === 0) {
100
- return null;
101
- }
102
- // Priority 1: Connections with active o-protocol streams
103
- const connectionsWithStreams = openConnections
104
- .map((conn) => ({
105
- conn,
106
- activeStreamCount: conn.streams.filter((s) => s.protocol.includes('/o/') &&
107
- s.status === 'open' &&
108
- s.writeStatus === 'writable' &&
109
- s.remoteReadStatus === 'readable').length,
110
- }))
111
- .filter((item) => item.activeStreamCount > 0)
112
- .sort((a, b) => b.activeStreamCount - a.activeStreamCount);
113
- if (connectionsWithStreams.length > 0) {
114
- this.logger.debug('Selected connection with active streams', {
115
- streamCount: connectionsWithStreams[0].activeStreamCount,
116
- });
117
- return connectionsWithStreams[0].conn;
118
- }
119
- // Priority 2: Based on reuse context
120
- if (reuseContext) {
121
- // Prefer inbound connections
122
- const inbound = openConnections.find((c) => c.direction === 'inbound' || !c.direction);
123
- if (inbound) {
124
- this.logger.debug('Selected inbound connection (reuse context)');
125
- return inbound;
81
+ getValidConnection(addressKey) {
82
+ const connections = this.cachedConnections.get(addressKey) || [];
83
+ // Filter to open connections
84
+ const valid = connections.filter((c) => c.p2pConnection?.status === 'open');
85
+ // Update cache if we cleaned any stale connections
86
+ if (valid.length !== connections.length) {
87
+ if (valid.length === 0) {
88
+ this.cachedConnections.delete(addressKey);
126
89
  }
127
- }
128
- else {
129
- // Prefer outbound connections
130
- const outbound = openConnections.find((c) => c.direction === 'outbound');
131
- if (outbound) {
132
- this.logger.debug('Selected outbound connection (non-reuse context)');
133
- return outbound;
90
+ else {
91
+ this.cachedConnections.set(addressKey, valid);
134
92
  }
135
93
  }
136
- // Priority 3: Return first open connection
137
- this.logger.debug('Selected first available open connection', connectionsWithStreams);
138
- return openConnections[0];
94
+ return valid[0] ?? null;
139
95
  }
140
96
  /**
141
- * Get a cached oNodeConnection for the given p2p connection if it exists and is valid.
142
- * @param p2pConnection - The p2p connection to look up
143
- * @returns A valid oNodeConnection or null if none found
97
+ * Cache an oNodeConnection by its address key.
98
+ * @param conn - The oNodeConnection to cache
99
+ * @param addressKey - The address key to cache under
144
100
  */
145
- getCachedNodeConnection(p2pConnection) {
146
- const cached = this.nodeConnectionByConnectionId.get(p2pConnection.id);
147
- if (cached && cached.p2pConnection?.status === 'open') {
148
- return cached;
149
- }
150
- // Clean up stale entry if connection is no longer open
151
- if (cached) {
152
- this.nodeConnectionByConnectionId.delete(p2pConnection.id);
153
- }
154
- return null;
101
+ cacheConnection(conn, addressKey) {
102
+ this.logger.debug('Caching connection for address:', addressKey, conn.p2pConnection.id, conn.p2pConnection.direction);
103
+ const existing = this.cachedConnections.get(addressKey) || [];
104
+ existing.push(conn);
105
+ this.cachedConnections.set(addressKey, existing);
155
106
  }
156
107
  /**
157
- * Cache an oNodeConnection by its p2p connection ID for potential reuse.
158
- * @param conn - The oNodeConnection to cache
108
+ * Get or create a raw p2p connection to the given address.
109
+ * Subclasses can override connect() and use this method to get the underlying p2p connection.
159
110
  */
160
- cacheNodeConnection(conn) {
161
- this.nodeConnectionByConnectionId.set(conn.p2pConnection.id, conn);
162
- }
163
- async getOrCreateConnection(nextHopAddress, address) {
164
- if (!nextHopAddress) {
165
- throw new Error('Invalid address passed');
166
- }
167
- // Build an address-based cache key from the next hop address
168
- const addressKey = this.getAddressKey(nextHopAddress);
169
- if (!addressKey) {
170
- throw new Error(`Unable to extract address key from address: ${nextHopAddress.toString()}`);
171
- }
172
- // Check if we have cached connections by address key
173
- const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
174
- const bestConnection = this.selectBestConnection(cachedConnections, false);
175
- if (bestConnection) {
176
- this.logger.debug('Reusing cached connection for address:', nextHopAddress?.value, bestConnection.id);
177
- return bestConnection;
178
- }
179
- // Clean up stale connections if they exist but are not open
180
- if (cachedConnections.length > 0) {
181
- const openConnections = cachedConnections.filter((c) => c.status === 'open');
182
- if (openConnections.length === 0) {
183
- this.logger.debug('Removing all stale connections for address:', addressKey);
184
- this.connectionsByAddress.delete(addressKey);
185
- }
186
- else if (openConnections.length < cachedConnections.length) {
187
- this.logger.debug('Cleaning up some stale connections for address:', addressKey);
188
- this.connectionsByAddress.set(addressKey, openConnections);
111
+ async getOrCreateP2pConnection(nextHopAddress, addressKey) {
112
+ // Check if libp2p already has an active connection for this peer
113
+ const peerId = this.getPeerIdFromAddress(nextHopAddress);
114
+ if (peerId) {
115
+ const connections = this.p2pNode.getConnections();
116
+ const existingConnection = connections.find((conn) => conn.remotePeer?.toString() === peerId && conn.status === 'open');
117
+ if (existingConnection) {
118
+ this.logger.debug('Found existing libp2p connection for address:', addressKey);
119
+ return existingConnection;
189
120
  }
190
121
  }
191
- // Check if libp2p has an active connection for this address
192
- const libp2pConnection = this.getCachedLibp2pConnection(nextHopAddress);
193
- if (libp2pConnection && libp2pConnection.status === 'open') {
194
- this.logger.debug('Caching existing libp2p connection for address:', addressKey);
195
- const connections = this.connectionsByAddress.get(addressKey) || [];
196
- connections.push(libp2pConnection);
197
- this.connectionsByAddress.set(addressKey, connections);
198
- return libp2pConnection;
199
- }
200
122
  // Check if dial is already in progress for this address key
201
123
  const pendingDial = this.pendingDialsByAddress.get(addressKey);
202
124
  if (pendingDial) {
@@ -207,12 +129,7 @@ export class oNodeConnectionManager extends oConnectionManager {
207
129
  const dialPromise = this.performDial(nextHopAddress, addressKey);
208
130
  this.pendingDialsByAddress.set(addressKey, dialPromise);
209
131
  try {
210
- const connection = await dialPromise;
211
- // Add the established connection to the cache array
212
- const connections = this.connectionsByAddress.get(addressKey) || [];
213
- connections.push(connection);
214
- this.connectionsByAddress.set(addressKey, connections);
215
- return connection;
132
+ return await dialPromise;
216
133
  }
217
134
  finally {
218
135
  this.pendingDialsByAddress.delete(addressKey);
@@ -233,6 +150,17 @@ export class oNodeConnectionManager extends oConnectionManager {
233
150
  }
234
151
  async answer(config) {
235
152
  const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, p2pConnection, reuse, } = config;
153
+ const addressKey = this.getAddressKey(nextHopAddress);
154
+ if (!addressKey) {
155
+ this.logger.error('Failed to generate an address key for address:', nextHopAddress);
156
+ throw new Error(`Unable to extract address key from address: ${nextHopAddress.toString()}`);
157
+ }
158
+ // Check if we already have a cached connection for this address
159
+ const existingConnection = this.getValidConnection(addressKey);
160
+ if (existingConnection) {
161
+ this.logger.debug('Reusing cached connection for answer:', addressKey, existingConnection.p2pConnection.id);
162
+ return existingConnection;
163
+ }
236
164
  const connection = new oNodeConnection({
237
165
  nextHopAddress: nextHopAddress,
238
166
  address: address,
@@ -246,38 +174,33 @@ export class oNodeConnectionManager extends oConnectionManager {
246
174
  requestHandler: config.requestHandler ?? undefined,
247
175
  reusePolicy: reuse ? 'reuse' : 'none',
248
176
  });
249
- // Cache the new connection by its p2p connection ID
250
- this.cacheNodeConnection(connection);
251
- const addressKey = this.getAddressKey(nextHopAddress);
252
- if (addressKey) {
253
- const connections = this.connectionsByAddress.get(addressKey) || [];
254
- // Only add if not already in the cache
255
- if (!connections.includes(p2pConnection)) {
256
- connections.push(p2pConnection);
257
- this.connectionsByAddress.set(addressKey, connections);
258
- }
259
- }
260
- else {
261
- this.logger.error('Should not happen! Failed to generate an address key for address:', nextHopAddress);
262
- }
177
+ // Cache the new connection
178
+ this.cacheConnection(connection, addressKey);
263
179
  return connection;
264
180
  }
265
181
  /**
266
- * Connect to a given address, reusing oNodeConnection and libp2p connections when possible
182
+ * Connect to a given address, reusing oNodeConnection when possible
267
183
  * @param config - Connection configuration
268
184
  * @returns The connection object
269
185
  */
270
186
  async connect(config) {
271
187
  const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, } = config;
272
- // First get or create the underlying p2p connection
273
- const p2pConnection = await this.getOrCreateConnection(nextHopAddress, address);
274
- // Check for existing valid oNodeConnection for this p2p connection
275
- const existingConnection = this.getCachedNodeConnection(p2pConnection);
188
+ if (!nextHopAddress) {
189
+ throw new Error('Invalid address passed');
190
+ }
191
+ const addressKey = this.getAddressKey(nextHopAddress);
192
+ if (!addressKey) {
193
+ throw new Error(`Unable to extract address key from address: ${nextHopAddress.toString()}`);
194
+ }
195
+ // Check for existing valid cached connection
196
+ const existingConnection = this.getValidConnection(addressKey);
276
197
  if (existingConnection) {
277
- this.logger.debug('Reusing cached oNodeConnection for connection ID:', p2pConnection.id);
198
+ this.logger.debug('Reusing cached connection for address:', addressKey, existingConnection.p2pConnection.id);
278
199
  return existingConnection;
279
200
  }
280
- // No valid cached connection, create new one
201
+ // Get or create the underlying p2p connection
202
+ const p2pConnection = await this.getOrCreateP2pConnection(nextHopAddress, addressKey);
203
+ // Create new oNodeConnection
281
204
  const connection = new oNodeConnection({
282
205
  nextHopAddress: nextHopAddress,
283
206
  address: address,
@@ -290,8 +213,8 @@ export class oNodeConnectionManager extends oConnectionManager {
290
213
  runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
291
214
  requestHandler: config.requestHandler ?? undefined,
292
215
  });
293
- // Cache the new connection by its p2p connection ID
294
- this.cacheNodeConnection(connection);
216
+ // Cache the new connection
217
+ this.cacheConnection(connection, addressKey);
295
218
  return connection;
296
219
  }
297
220
  /**
@@ -305,30 +228,7 @@ export class oNodeConnectionManager extends oConnectionManager {
305
228
  if (!addressKey) {
306
229
  return false;
307
230
  }
308
- // Check our address-based cache first
309
- const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
310
- const bestConnection = this.selectBestConnection(cachedConnections, false);
311
- if (bestConnection) {
312
- return true;
313
- }
314
- // Fall back to checking libp2p's connections
315
- const peerId = this.getPeerIdFromAddress(address);
316
- // 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
317
- const connections = this.p2pNode.getConnections(peerId); // ignore since converting to a proper peer id breaks the browser implementation
318
- // Check if we have at least one open connection
319
- const hasOpenConnection = connections.some((conn) => conn.status === 'open');
320
- // If libp2p has an open connection, update our cache
321
- if (hasOpenConnection) {
322
- const openConnection = connections.find((conn) => conn.status === 'open');
323
- if (openConnection) {
324
- const existingConnections = this.connectionsByAddress.get(addressKey) || [];
325
- if (!existingConnections.includes(openConnection)) {
326
- existingConnections.push(openConnection);
327
- this.connectionsByAddress.set(addressKey, existingConnections);
328
- }
329
- }
330
- }
331
- return hasOpenConnection;
231
+ return this.getValidConnection(addressKey) !== null;
332
232
  }
333
233
  catch (error) {
334
234
  this.logger.debug('Error checking cached connection:', error);
@@ -336,48 +236,17 @@ export class oNodeConnectionManager extends oConnectionManager {
336
236
  }
337
237
  }
338
238
  /**
339
- * Get an existing connection to the target peer (from cache or libp2p)
239
+ * Get an existing cached oNodeConnection for the target address
340
240
  * @param address - The address to get a connection for
341
- * @returns The Connection object or null if not found
241
+ * @returns The oNodeConnection or null if not found
342
242
  */
343
- getCachedLibp2pConnection(address) {
243
+ getCachedConnection(address) {
344
244
  try {
345
245
  const addressKey = this.getAddressKey(address);
346
246
  if (!addressKey) {
347
247
  return null;
348
248
  }
349
- // Check address-based cache first
350
- const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
351
- const bestConnection = this.selectBestConnection(cachedConnections, false);
352
- if (bestConnection) {
353
- return bestConnection;
354
- }
355
- const peerId = this.getPeerIdFromAddress(address);
356
- if (!peerId) {
357
- return null;
358
- }
359
- // Query libp2p for connections to this peer
360
- const connections = this.p2pNode.getConnections();
361
- const filteredConnections = connections.filter((conn) => conn.remotePeer?.toString() === peerId);
362
- // Find open connections
363
- const openConnections = filteredConnections.filter((conn) => conn.status === 'open');
364
- // If we found open connections in libp2p, add them to cache and select best
365
- if (openConnections.length > 0) {
366
- const existingConnections = this.connectionsByAddress.get(addressKey) || [];
367
- // Add any new connections that aren't already cached
368
- for (const conn of openConnections) {
369
- if (!existingConnections.includes(conn)) {
370
- existingConnections.push(conn);
371
- }
372
- }
373
- this.connectionsByAddress.set(addressKey, existingConnections);
374
- return this.selectBestConnection(existingConnections, false);
375
- }
376
- // Clean up stale cache entries if connections are no longer open
377
- if (cachedConnections.length > 0) {
378
- this.connectionsByAddress.delete(addressKey);
379
- }
380
- return null;
249
+ return this.getValidConnection(addressKey);
381
250
  }
382
251
  catch (error) {
383
252
  this.logger.debug('Error getting cached connection:', error);
@@ -390,20 +259,19 @@ export class oNodeConnectionManager extends oConnectionManager {
390
259
  */
391
260
  getCacheStats() {
392
261
  const allConnections = [];
393
- for (const [addressKey, connections,] of this.connectionsByAddress.entries()) {
262
+ for (const [addressKey, connections] of this.cachedConnections.entries()) {
394
263
  for (const conn of connections) {
395
264
  allConnections.push({
396
- peerId: conn.remotePeer?.toString() ?? 'unknown',
397
- status: conn.status,
265
+ peerId: conn.p2pConnection?.remotePeer?.toString() ?? 'unknown',
266
+ status: conn.p2pConnection?.status ?? 'unknown',
398
267
  addressKey,
399
268
  });
400
269
  }
401
270
  }
402
271
  return {
403
- cachedAddresses: this.connectionsByAddress.size,
272
+ cachedAddresses: this.cachedConnections.size,
404
273
  totalCachedConnections: allConnections.length,
405
274
  pendingDials: this.pendingDialsByAddress.size,
406
- cachedNodeConnections: this.nodeConnectionByConnectionId.size,
407
275
  connectionsByPeer: allConnections,
408
276
  };
409
277
  }
@@ -413,18 +281,16 @@ export class oNodeConnectionManager extends oConnectionManager {
413
281
  */
414
282
  cleanupStaleConnections() {
415
283
  let removed = 0;
416
- for (const [addressKey, connections,] of this.connectionsByAddress.entries()) {
417
- const openConnections = connections.filter((conn) => conn.status === 'open');
284
+ for (const [addressKey, connections] of this.cachedConnections.entries()) {
285
+ const openConnections = connections.filter((conn) => conn.p2pConnection?.status === 'open');
418
286
  const staleCount = connections.length - openConnections.length;
419
287
  if (staleCount > 0) {
420
288
  removed += staleCount;
421
289
  if (openConnections.length === 0) {
422
- // Remove the entire entry if no connections remain
423
- this.connectionsByAddress.delete(addressKey);
290
+ this.cachedConnections.delete(addressKey);
424
291
  }
425
292
  else {
426
- // Keep only the open connections
427
- this.connectionsByAddress.set(addressKey, openConnections);
293
+ this.cachedConnections.set(addressKey, openConnections);
428
294
  }
429
295
  }
430
296
  }
@@ -433,21 +299,4 @@ export class oNodeConnectionManager extends oConnectionManager {
433
299
  }
434
300
  return removed;
435
301
  }
436
- /**
437
- * Clean up all stale oNodeConnections from cache
438
- * @returns Number of oNodeConnections removed
439
- */
440
- cleanupStaleNodeConnections() {
441
- let removed = 0;
442
- for (const [connectionId, conn,] of this.nodeConnectionByConnectionId.entries()) {
443
- if (conn.p2pConnection?.status !== 'open') {
444
- this.nodeConnectionByConnectionId.delete(connectionId);
445
- removed++;
446
- }
447
- }
448
- if (removed > 0) {
449
- this.logger.debug(`Cleaned up ${removed} stale oNodeConnections`);
450
- }
451
- return removed;
452
- }
453
302
  }
@@ -1,6 +1,5 @@
1
1
  export * from './o-node.js';
2
2
  export * from './utils/index.js';
3
- export * from './decorators/index.js';
4
3
  export * from './o-node.hierarchy-manager.js';
5
4
  export * from './interfaces/o-node.config.js';
6
5
  export * from './connection/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,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"}
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,6 +1,5 @@
1
1
  export * from './o-node.js';
2
2
  export * from './utils/index.js';
3
- export * from './decorators/index.js';
4
3
  export * from './o-node.hierarchy-manager.js';
5
4
  export * from './interfaces/o-node.config.js';
6
5
  export * from './connection/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"o-node.d.ts","sourceRoot":"","sources":["../../src/o-node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"o-node.d.ts","sourceRoot":"","sources":["../../src/o-node.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,3 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- };
7
- var __metadata = (this && this.__metadata) || function (k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
- };
10
1
  import { createNode, defaultLibp2pConfig, KEEP_ALIVE, } from '@olane/o-config';
11
2
  import { v4 as uuidv4 } from 'uuid';
12
3
  import { oNodeRouter } from './router/o-node.router.js';
@@ -21,7 +12,6 @@ import { oLeaderResolverFallback } from './router/index.js';
21
12
  import { oNodeNotificationManager } from './o-node.notification-manager.js';
22
13
  import { oReconnectionManager } from './managers/o-reconnection.manager.js';
23
14
  import { LockManager } from './utils/lock-manager.js';
24
- import { Synchronized } from './decorators/synchronized.js';
25
15
  export class oNode extends oToolBase {
26
16
  constructor(config) {
27
17
  super(config);
@@ -652,15 +642,3 @@ export class oNode extends oToolBase {
652
642
  }
653
643
  }
654
644
  }
655
- __decorate([
656
- Synchronized('register:parent'),
657
- __metadata("design:type", Function),
658
- __metadata("design:paramtypes", []),
659
- __metadata("design:returntype", Promise)
660
- ], oNode.prototype, "registerParent", null);
661
- __decorate([
662
- Synchronized('register:leader'),
663
- __metadata("design:type", Function),
664
- __metadata("design:paramtypes", []),
665
- __metadata("design:returntype", Promise)
666
- ], oNode.prototype, "registerLeader", null);
@@ -11,6 +11,7 @@ export declare class oNodeTool extends oNodeTool_base {
11
11
  private streamHandler;
12
12
  handleProtocolReuse(address: oAddress): Promise<void>;
13
13
  handleProtocol(address: oAddress): Promise<void>;
14
+ initializeProtocols(): Promise<void>;
14
15
  initialize(): Promise<void>;
15
16
  handleStreamReuse(stream: Stream, connection: Connection): Promise<void>;
16
17
  handleStream(stream: Stream, connection: Connection, reuse?: boolean): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"o-node.tool.d.ts","sourceRoot":"","sources":["../../src/o-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAoB,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;;AAMrD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAkB;IAC/C,OAAO,CAAC,aAAa,CAAiB;IAEhC,mBAAmB,CAAC,OAAO,EAAE,QAAQ;IAsBrC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAoBhC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,IAAI,CAAC;IAIV,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC;IAyCV,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAQ9B,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CAiC5D"}
1
+ {"version":3,"file":"o-node.tool.d.ts","sourceRoot":"","sources":["../../src/o-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAoB,MAAM,eAAe,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;;AAMrD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAkB;IAC/C,OAAO,CAAC,aAAa,CAAiB;IAEhC,mBAAmB,CAAC,OAAO,EAAE,QAAQ;IAsBrC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAoBhC,mBAAmB;IAWnB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,IAAI,CAAC;IAIV,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC;IAyCV,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAQ9B,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CAiC5D"}
@@ -47,15 +47,19 @@ export class oNodeTool extends oTool(oServerNode) {
47
47
  });
48
48
  await this.handleProtocolReuse(address);
49
49
  }
50
- async initialize() {
51
- await super.initialize();
52
- this.streamHandler = new StreamHandler(this.logger);
50
+ async initializeProtocols() {
51
+ this.logger.info('Initializing custom protocols for node...');
53
52
  await this.handleProtocol(this.address);
54
53
  if (this.staticAddress &&
55
54
  this.staticAddress?.toString() !== this.address.toString()) {
56
55
  await this.handleProtocol(this.staticAddress);
57
56
  }
58
57
  }
58
+ async initialize() {
59
+ await super.initialize();
60
+ this.streamHandler = new StreamHandler(this.logger);
61
+ await this.initializeProtocols();
62
+ }
59
63
  async handleStreamReuse(stream, connection) {
60
64
  return this.handleStream(stream, connection, true);
61
65
  }
@@ -1,6 +1,11 @@
1
1
  import { Connection } from '@olane/o-config';
2
2
  import { oObject } from '@olane/o-core';
3
3
  export declare class ConnectionUtils extends oObject {
4
+ /**
5
+ * Waits for a peer to appear in the peer store with sufficient protocol information.
6
+ * Implements retry logic to handle race conditions where peer store is not immediately populated.
7
+ */
8
+ private static waitForPeerInStore;
4
9
  static addressFromConnection(options: {
5
10
  currentNode: any;
6
11
  connection: Connection;
@@ -1 +1 @@
1
- {"version":3,"file":"connection.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/connection.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2B,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAuB,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7D,qBAAa,eAAgB,SAAQ,OAAO;WACtB,qBAAqB,CAAC,OAAO,EAAE;QACjD,WAAW,EAAE,GAAG,CAAC;QACjB,UAAU,EAAE,UAAU,CAAC;KACxB;CA+DF"}
1
+ {"version":3,"file":"connection.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/connection.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2B,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAuB,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7D,qBAAa,eAAgB,SAAQ,OAAO;IAC1C;;;OAGG;mBACkB,kBAAkB;WAmCnB,qBAAqB,CAAC,OAAO,EAAE;QACjD,WAAW,EAAE,GAAG,CAAC;QACjB,UAAU,EAAE,UAAU,CAAC;KACxB;CA8DF"}
@@ -2,25 +2,48 @@ import { oError, oErrorCodes, oObject } from '@olane/o-core';
2
2
  import { oNodeAddress } from '../router/o-node.address.js';
3
3
  import { oNodeTransport } from '../router/o-node.transport.js';
4
4
  export class ConnectionUtils extends oObject {
5
- static async addressFromConnection(options) {
6
- try {
7
- const { currentNode, connection } = options;
8
- const p2pNode = currentNode.p2pNode;
9
- // Extract the actual olane address from the peer store
5
+ /**
6
+ * Waits for a peer to appear in the peer store with sufficient protocol information.
7
+ * Implements retry logic to handle race conditions where peer store is not immediately populated.
8
+ */
9
+ static async waitForPeerInStore(p2pNode, remotePeerId) {
10
+ const MAX_RETRIES = 100; // 5 seconds total (100 * 50ms)
11
+ const RETRY_DELAY_MS = 50;
12
+ const MIN_PROTOCOLS = 2;
13
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
10
14
  const peers = await p2pNode.peerStore.all();
11
15
  const remotePeer = peers.find((peer) => {
12
- return peer.id.toString() === connection.remotePeer.toString();
16
+ return peer.id.toString() === remotePeerId.toString();
13
17
  });
14
- if (!remotePeer) {
15
- console.log('Failed to find peer:', remotePeer);
16
- throw new Error(`Failed to extract remote address, peer ${connection.remotePeer.toString()} not found in peer store.`);
18
+ // Check if peer exists and has sufficient protocol information
19
+ if (remotePeer && remotePeer.protocols.length > MIN_PROTOCOLS) {
20
+ console.log(`Peer found in store after ${attempt} attempts (${attempt * RETRY_DELAY_MS}ms)`);
21
+ return remotePeer;
22
+ }
23
+ // Wait before next retry
24
+ if (attempt < MAX_RETRIES - 1) {
25
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
17
26
  }
27
+ }
28
+ // Timeout exceeded
29
+ throw new Error(`Timeout waiting for peer ${remotePeerId.toString()} to appear in peer store with sufficient protocols (waited ${MAX_RETRIES * RETRY_DELAY_MS}ms)`);
30
+ }
31
+ // TODO: improve this logic (poor implementation for now)
32
+ static async addressFromConnection(options) {
33
+ try {
34
+ const { currentNode, connection } = options;
35
+ const p2pNode = currentNode.p2pNode;
36
+ // Wait for peer to appear in peer store with sufficient protocol information
37
+ // This handles race conditions where peer store is not immediately populated
38
+ const remotePeer = await this.waitForPeerInStore(p2pNode, connection.remotePeer);
18
39
  // Get origin address for comparison
19
40
  const originAddress = currentNode.address?.value;
20
41
  if (!originAddress) {
21
42
  throw new Error('Origin address is not configured');
22
43
  }
23
- const oProtocol = remotePeer.protocols.find((p) => p.startsWith('/o/'));
44
+ console.log('Deriving address from connection protocols:', remotePeer.protocols);
45
+ const oProtocol = remotePeer.protocols.find((p) => p.startsWith('/o/') &&
46
+ p.includes(currentNode?.address?.protocol) === false);
24
47
  if (!oProtocol) {
25
48
  throw new Error('Failed to extract remote address, could not find o-protocol in peer protocols.');
26
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olane/o-node",
3
- "version": "0.7.46",
3
+ "version": "0.7.48",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "devDependencies": {
41
41
  "@eslint/eslintrc": "^3.3.1",
42
42
  "@eslint/js": "^9.29.0",
43
- "@olane/o-test": "0.7.46",
43
+ "@olane/o-test": "0.7.48",
44
44
  "@tsconfig/node20": "^20.1.6",
45
45
  "@types/jest": "^30.0.0",
46
46
  "@types/json5": "^2.2.0",
@@ -60,13 +60,13 @@
60
60
  "typescript": "5.4.5"
61
61
  },
62
62
  "dependencies": {
63
- "@olane/o-config": "0.7.46",
64
- "@olane/o-core": "0.7.46",
65
- "@olane/o-protocol": "0.7.46",
66
- "@olane/o-tool": "0.7.46",
63
+ "@olane/o-config": "0.7.48",
64
+ "@olane/o-core": "0.7.48",
65
+ "@olane/o-protocol": "0.7.48",
66
+ "@olane/o-tool": "0.7.48",
67
67
  "debug": "^4.4.1",
68
68
  "dotenv": "^16.5.0",
69
69
  "json5": "^2.2.3"
70
70
  },
71
- "gitHead": "48c45f37c6e2e1ee626ce1fb0f0de175ca2db3b6"
71
+ "gitHead": "1810818bb9682b586e9db2febcc2a89da5057449"
72
72
  }
@@ -1,2 +0,0 @@
1
- export * from './synchronized.js';
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
@@ -1 +0,0 @@
1
- export * from './synchronized.js';
@@ -1,21 +0,0 @@
1
- /**
2
- * Method decorator that automatically acquires and releases a lock
3
- * when the decorated method is called. Ensures only one invocation
4
- * of the method executes at a time.
5
- *
6
- * @param lockName - Optional custom lock name. If not provided,
7
- * defaults to "ClassName:methodName"
8
- * @param timeoutMs - Optional timeout for lock acquisition in milliseconds
9
- *
10
- * @example
11
- * class MyNode extends oNode {
12
- * protected lockManager = new LockManager();
13
- *
14
- * @Synchronized('register:leader')
15
- * async registerLeader(): Promise<void> {
16
- * // This method is automatically synchronized
17
- * }
18
- * }
19
- */
20
- export declare function Synchronized(lockName?: string, timeoutMs?: number): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
21
- //# sourceMappingURL=synchronized.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"synchronized.d.ts","sourceRoot":"","sources":["../../../src/decorators/synchronized.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,YAEtD,GAAG,eACE,MAAM,cACP,kBAAkB,wBA8BjC"}
@@ -1,39 +0,0 @@
1
- /**
2
- * Method decorator that automatically acquires and releases a lock
3
- * when the decorated method is called. Ensures only one invocation
4
- * of the method executes at a time.
5
- *
6
- * @param lockName - Optional custom lock name. If not provided,
7
- * defaults to "ClassName:methodName"
8
- * @param timeoutMs - Optional timeout for lock acquisition in milliseconds
9
- *
10
- * @example
11
- * class MyNode extends oNode {
12
- * protected lockManager = new LockManager();
13
- *
14
- * @Synchronized('register:leader')
15
- * async registerLeader(): Promise<void> {
16
- * // This method is automatically synchronized
17
- * }
18
- * }
19
- */
20
- export function Synchronized(lockName, timeoutMs) {
21
- return function (target, propertyKey, descriptor) {
22
- const originalMethod = descriptor.value;
23
- // Generate lock name if not provided
24
- const finalLockName = lockName || `${target.constructor.name}:${propertyKey}`;
25
- descriptor.value = async function (...args) {
26
- // Ensure lockManager exists on the instance
27
- const lockManager = this.lockManager;
28
- if (!lockManager) {
29
- throw new Error(`LockManager not initialized on instance. ` +
30
- `Ensure 'protected lockManager = new LockManager()' is declared in ${target.constructor.name}`);
31
- }
32
- // Execute the method with automatic lock management
33
- return lockManager.withLock(finalLockName, async () => {
34
- return originalMethod.apply(this, args);
35
- }, timeoutMs);
36
- };
37
- return descriptor;
38
- };
39
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=astream-reuse.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"astream-reuse.spec.d.ts","sourceRoot":"","sources":["../../test/astream-reuse.spec.ts"],"names":[],"mappings":""}
@@ -1,107 +0,0 @@
1
- import { expect } from 'chai';
2
- import { TestEnvironment } from './helpers/index.js';
3
- import { NetworkTopologies, } from './helpers/network-builder.js';
4
- import { oNodeAddress } from '../src/router/o-node.address.js';
5
- describe('Stream Reuse', () => {
6
- const env = new TestEnvironment();
7
- let builder;
8
- afterEach(async () => {
9
- if (builder) {
10
- await builder.cleanup();
11
- }
12
- await env.cleanup();
13
- });
14
- describe('Stream Reuse with reusePolicy="reuse"', () => {
15
- it('should reuse the same stream across multiple requests', async () => {
16
- builder = await NetworkTopologies.twoNode();
17
- const leader = builder.getNode('o://leader');
18
- const child = builder.getNode('o://child');
19
- // Track stream IDs
20
- const streamIds = [];
21
- // Make first request
22
- const response1 = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
23
- method: 'echo',
24
- params: { message: 'first' },
25
- });
26
- expect(response1.result.success).to.be.true;
27
- // Get active connection to inspect streams
28
- const connectionManager = leader.connectionManager;
29
- const connection = connectionManager.getCachedLibp2pConnection(child.address);
30
- connection.reusePolicy;
31
- if (connection) {
32
- // Store initial stream count and first stream ID
33
- const initialStreamCount = connection.streams.length;
34
- if (connection.streams.length > 0) {
35
- streamIds.push(connection.streams[0].id);
36
- }
37
- // Make second request
38
- const response2 = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
39
- method: 'echo',
40
- params: { message: 'second' },
41
- });
42
- expect(response2.result.success).to.be.true;
43
- // Get stream count after second request
44
- const finalStreamCount = connection.streams.length;
45
- if (connection.streams.length > 0) {
46
- streamIds.push(connection.streams[0].id);
47
- }
48
- // With default behavior (reusePolicy='none'), new streams are created
49
- // This test verifies current behavior
50
- expect(streamIds.length).to.be.greaterThan(0);
51
- expect(finalStreamCount).to.be.greaterThan(0);
52
- }
53
- });
54
- });
55
- describe('Stream Creation with reusePolicy="none"', () => {
56
- it('should create new streams for each request', async () => {
57
- builder = await NetworkTopologies.twoNode();
58
- const leader = builder.getNode('o://leader');
59
- const child = builder.getNode('o://child');
60
- // Make multiple requests
61
- for (let i = 0; i < 3; i++) {
62
- const response = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
63
- method: 'echo',
64
- params: { message: `request-${i}` },
65
- });
66
- expect(response.result.success).to.be.true;
67
- }
68
- // Get connection to verify stream behavior
69
- const connectionManager = leader.connectionManager;
70
- const connection = connectionManager.getCachedLibp2pConnection(child.address);
71
- // With default reusePolicy='none', streams are closed after each request
72
- // So we expect minimal open streams
73
- expect(connection).to.exist;
74
- if (connection) {
75
- // The number of open streams should be limited since old ones are closed
76
- expect(connection.streams.length).to.be.lessThan(10);
77
- }
78
- });
79
- });
80
- describe('Stream Persistence After Transmission', () => {
81
- it('should close streams after transmission with reusePolicy="none"', async () => {
82
- builder = await NetworkTopologies.twoNode();
83
- const leader = builder.getNode('o://leader');
84
- const child = builder.getNode('o://child');
85
- // Make a request
86
- const response = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
87
- method: 'echo',
88
- params: { message: 'test' },
89
- });
90
- expect(response.result.success).to.be.true;
91
- // Get connection
92
- const connectionManager = leader.connectionManager;
93
- const connection = connectionManager.getCachedLibp2pConnection(child.address);
94
- expect(connection).to.exist;
95
- if (connection) {
96
- // Make another request to verify new stream can be created
97
- const response2 = await leader.use(new oNodeAddress(child.address.toString(), child.address.libp2pTransports), {
98
- method: 'echo',
99
- params: { message: 'test2' },
100
- });
101
- expect(response2.result.success).to.be.true;
102
- // Verify connection is still functional
103
- expect(connection.status).to.equal('open');
104
- }
105
- });
106
- });
107
- });