@olane/o-node 0.7.43 → 0.7.45

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.
@@ -7,27 +7,35 @@ export declare class oNodeConnectionManager extends oConnectionManager {
7
7
  protected p2pNode: Libp2p;
8
8
  private defaultReadTimeoutMs?;
9
9
  private defaultDrainTimeoutMs?;
10
- private connectionsByTransportKey;
11
- private pendingDialsByTransportKey;
10
+ private connectionsByAddress;
11
+ private pendingDialsByAddress;
12
12
  constructor(config: oNodeConnectionManagerConfig);
13
13
  /**
14
14
  * Set up listeners to maintain connection cache state
15
15
  */
16
16
  private setupConnectionListeners;
17
17
  /**
18
- * Build a stable cache key from the libp2p transports on an address.
18
+ * Build a stable cache key from an address.
19
19
  *
20
- * We intentionally key the cache by transports (multiaddrs) instead of peer IDs
21
- * to avoid ambiguity when multiple peers may share a peer ID or when addresses
22
- * change but the libp2p transports are the true dial targets.
20
+ * We key the cache by address value (e.g., "o://my-tool") to maintain
21
+ * a simple one-to-one mapping between addresses and connections.
23
22
  */
24
- private getTransportKeyFromAddress;
23
+ private getAddressKey;
25
24
  /**
26
25
  * Extract peer ID string from an address
27
26
  * @param address - The address to extract peer ID from
28
27
  * @returns The peer ID string or null if not found
29
28
  */
30
29
  private getPeerIdFromAddress;
30
+ /**
31
+ * Select the best connection from an array of connections.
32
+ * Prioritizes connections with active streams, then by direction based on context.
33
+ *
34
+ * @param connections - Array of connections to choose from
35
+ * @param reuseContext - If true, prefer inbound connections; otherwise prefer outbound
36
+ * @returns The best connection or null if none are suitable
37
+ */
38
+ private selectBestConnection;
31
39
  getOrCreateConnection(nextHopAddress: oAddress, address: oAddress): Promise<Connection>;
32
40
  private performDial;
33
41
  answer(config: oConnectionConfig & {
@@ -57,11 +65,13 @@ export declare class oNodeConnectionManager extends oConnectionManager {
57
65
  * @returns Object containing cache statistics
58
66
  */
59
67
  getCacheStats(): {
60
- cachedConnections: number;
68
+ cachedAddresses: number;
69
+ totalCachedConnections: number;
61
70
  pendingDials: number;
62
71
  connectionsByPeer: Array<{
63
72
  peerId: string;
64
73
  status: string;
74
+ addressKey: string;
65
75
  }>;
66
76
  };
67
77
  /**
@@ -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;IAQhD,QAAQ,CAAC,MAAM,EAAE,4BAA4B;IAPzD,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,OAAO,CAAC,qBAAqB,CAAC,CAAS;IACvC,OAAO,CAAC,yBAAyB,CAAsC;IACvE,OAAO,CAAC,0BAA0B,CACtB;gBAES,MAAM,EAAE,4BAA4B;IAWzD;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAsBhC;;;;;;OAMG;IACH,OAAO,CAAC,0BAA0B;IAiBlC;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAatB,qBAAqB,CACzB,cAAc,EAAE,QAAQ,EACxB,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,UAAU,CAAC;YA+DR,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;IAmC3B;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IA8BlE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IAwCpC;;;;OAIG;IACH,yBAAyB,CAAC,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,IAAI;IA+C/D;;;OAGG;IACH,aAAa,IAAI;QACf,iBAAiB,EAAE,MAAM,CAAC;QAC1B,YAAY,EAAE,MAAM,CAAC;QACrB,iBAAiB,EAAE,KAAK,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAC9D;IAaD;;;OAGG;IACH,uBAAuB,IAAI,MAAM;CAgBlC"}
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;IAQhD,QAAQ,CAAC,MAAM,EAAE,4BAA4B;IAPzD,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,CACjB;gBAES,MAAM,EAAE,4BAA4B;IAWzD;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA8BhC;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IASrB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;;;;;;OAOG;IACH,OAAO,CAAC,oBAAoB;IA6DtB,qBAAqB,CACzB,cAAc,EAAE,QAAQ,EACxB,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,UAAU,CAAC;YA+ER,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;IAwC3B;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IA8BlE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IA8CpC;;;;OAIG;IACH,yBAAyB,CAAC,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,IAAI;IA0D/D;;;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,8 +4,8 @@ export class oNodeConnectionManager extends oConnectionManager {
4
4
  constructor(config) {
5
5
  super(config);
6
6
  this.config = config;
7
- this.connectionsByTransportKey = new Map();
8
- this.pendingDialsByTransportKey = new Map();
7
+ this.connectionsByAddress = new Map();
8
+ this.pendingDialsByAddress = new Map();
9
9
  this.p2pNode = config.p2pNode;
10
10
  this.defaultReadTimeoutMs = config.defaultReadTimeoutMs;
11
11
  this.defaultDrainTimeoutMs = config.defaultDrainTimeoutMs;
@@ -22,34 +22,34 @@ export class oNodeConnectionManager extends oConnectionManager {
22
22
  if (!connection) {
23
23
  return;
24
24
  }
25
- for (const [transportKey, cachedConnection,] of this.connectionsByTransportKey.entries()) {
26
- if (cachedConnection === connection) {
27
- this.logger.debug('Connection closed, removing from cache for transport key:', transportKey);
28
- this.connectionsByTransportKey.delete(transportKey);
25
+ for (const [addressKey, cachedConnections,] of this.connectionsByAddress.entries()) {
26
+ const index = cachedConnections.indexOf(connection);
27
+ if (index !== -1) {
28
+ this.logger.debug('Connection closed, removing from cache for address:', addressKey);
29
+ cachedConnections.splice(index, 1);
30
+ // Remove the address key entirely if no connections remain
31
+ if (cachedConnections.length === 0) {
32
+ this.connectionsByAddress.delete(addressKey);
33
+ }
34
+ else {
35
+ this.connectionsByAddress.set(addressKey, cachedConnections);
36
+ }
29
37
  }
30
38
  }
31
39
  });
32
40
  }
33
41
  /**
34
- * Build a stable cache key from the libp2p transports on an address.
42
+ * Build a stable cache key from an address.
35
43
  *
36
- * We intentionally key the cache by transports (multiaddrs) instead of peer IDs
37
- * to avoid ambiguity when multiple peers may share a peer ID or when addresses
38
- * change but the libp2p transports are the true dial targets.
44
+ * We key the cache by address value (e.g., "o://my-tool") to maintain
45
+ * a simple one-to-one mapping between addresses and connections.
39
46
  */
40
- getTransportKeyFromAddress(address) {
47
+ getAddressKey(address) {
41
48
  try {
42
- const nodeAddress = address;
43
- const transports = nodeAddress.libp2pTransports;
44
- if (!transports?.length) {
45
- return null;
46
- }
47
- // Sort to ensure deterministic keys regardless of transport ordering.
48
- const parts = transports.map((t) => t.toString()).sort();
49
- return parts.join('|');
49
+ return address.value || null;
50
50
  }
51
51
  catch (error) {
52
- this.logger.debug('Error extracting transport key from address:', error);
52
+ this.logger.debug('Error extracting address key from address:', error);
53
53
  return null;
54
54
  }
55
55
  }
@@ -71,60 +71,125 @@ export class oNodeConnectionManager extends oConnectionManager {
71
71
  return null;
72
72
  }
73
73
  }
74
+ /**
75
+ * Select the best connection from an array of connections.
76
+ * Prioritizes connections with active streams, then by direction based on context.
77
+ *
78
+ * @param connections - Array of connections to choose from
79
+ * @param reuseContext - If true, prefer inbound connections; otherwise prefer outbound
80
+ * @returns The best connection or null if none are suitable
81
+ */
82
+ selectBestConnection(connections, reuseContext = false) {
83
+ // Filter to only open connections
84
+ const openConnections = connections.filter((c) => c.status === 'open');
85
+ if (openConnections.length === 0) {
86
+ return null;
87
+ }
88
+ // Priority 1: Connections with active o-protocol streams
89
+ const connectionsWithStreams = openConnections
90
+ .map((conn) => ({
91
+ conn,
92
+ activeStreamCount: conn.streams.filter((s) => s.protocol.includes('/o/') &&
93
+ s.status === 'open' &&
94
+ s.writeStatus === 'writable' &&
95
+ s.remoteReadStatus === 'readable').length,
96
+ }))
97
+ .filter((item) => item.activeStreamCount > 0)
98
+ .sort((a, b) => b.activeStreamCount - a.activeStreamCount);
99
+ if (connectionsWithStreams.length > 0) {
100
+ this.logger.debug('Selected connection with active streams', {
101
+ streamCount: connectionsWithStreams[0].activeStreamCount,
102
+ });
103
+ return connectionsWithStreams[0].conn;
104
+ }
105
+ // Priority 2: Based on reuse context
106
+ if (reuseContext) {
107
+ // Prefer inbound connections
108
+ const inbound = openConnections.find((c) => c.direction === 'inbound' ||
109
+ !c.direction);
110
+ if (inbound) {
111
+ this.logger.debug('Selected inbound connection (reuse context)');
112
+ return inbound;
113
+ }
114
+ }
115
+ else {
116
+ // Prefer outbound connections
117
+ const outbound = openConnections.find((c) => c.direction === 'outbound');
118
+ if (outbound) {
119
+ this.logger.debug('Selected outbound connection (non-reuse context)');
120
+ return outbound;
121
+ }
122
+ }
123
+ // Priority 3: Return first open connection
124
+ this.logger.debug('Selected first available open connection');
125
+ return openConnections[0];
126
+ }
74
127
  async getOrCreateConnection(nextHopAddress, address) {
75
128
  if (!nextHopAddress) {
76
129
  throw new Error('Invalid address passed');
77
130
  }
78
- // Build a transport-based cache key from the next hop address
79
- const transportKey = this.getTransportKeyFromAddress(nextHopAddress);
80
- if (!transportKey) {
81
- throw new Error(`Unable to extract libp2p transports from address: ${nextHopAddress.toString()}`);
131
+ // Build an address-based cache key from the next hop address
132
+ const addressKey = this.getAddressKey(nextHopAddress);
133
+ if (!addressKey) {
134
+ throw new Error(`Unable to extract address key from address: ${nextHopAddress.toString()}`);
82
135
  }
83
- // Check if we have a cached connection by transport key
84
- const cachedConnection = this.connectionsByTransportKey.get(transportKey);
85
- if (cachedConnection && cachedConnection.status === 'open') {
86
- this.logger.debug('Reusing cached connection for transports:', nextHopAddress?.value);
87
- return cachedConnection;
136
+ // Check if we have cached connections by address key
137
+ const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
138
+ const bestConnection = this.selectBestConnection(cachedConnections, false);
139
+ if (bestConnection) {
140
+ this.logger.debug('Reusing cached connection for address:', nextHopAddress?.value);
141
+ return bestConnection;
88
142
  }
89
- // Clean up stale connection if it exists but is not open
90
- if (cachedConnection && cachedConnection.status !== 'open') {
91
- this.logger.debug('Removing stale connection for transports:', transportKey);
92
- this.connectionsByTransportKey.delete(transportKey);
143
+ // Clean up stale connections if they exist but are not open
144
+ if (cachedConnections.length > 0) {
145
+ const openConnections = cachedConnections.filter((c) => c.status === 'open');
146
+ if (openConnections.length === 0) {
147
+ this.logger.debug('Removing all stale connections for address:', addressKey);
148
+ this.connectionsByAddress.delete(addressKey);
149
+ }
150
+ else if (openConnections.length < cachedConnections.length) {
151
+ this.logger.debug('Cleaning up some stale connections for address:', addressKey);
152
+ this.connectionsByAddress.set(addressKey, openConnections);
153
+ }
93
154
  }
94
155
  // Check if libp2p has an active connection for this address
95
156
  const libp2pConnection = this.getCachedLibp2pConnection(nextHopAddress);
96
157
  if (libp2pConnection && libp2pConnection.status === 'open') {
97
- this.logger.debug('Caching existing libp2p connection for transports:', transportKey);
98
- this.connectionsByTransportKey.set(transportKey, libp2pConnection);
158
+ this.logger.debug('Caching existing libp2p connection for address:', addressKey);
159
+ const connections = this.connectionsByAddress.get(addressKey) || [];
160
+ connections.push(libp2pConnection);
161
+ this.connectionsByAddress.set(addressKey, connections);
99
162
  return libp2pConnection;
100
163
  }
101
- // Check if dial is already in progress for this transport key
102
- const pendingDial = this.pendingDialsByTransportKey.get(transportKey);
164
+ // Check if dial is already in progress for this address key
165
+ const pendingDial = this.pendingDialsByAddress.get(addressKey);
103
166
  if (pendingDial) {
104
- this.logger.debug('Awaiting existing dial for transports:', transportKey);
167
+ this.logger.debug('Awaiting existing dial for address:', addressKey);
105
168
  return pendingDial;
106
169
  }
107
- // Start new dial and cache the promise by transport key
108
- const dialPromise = this.performDial(nextHopAddress, transportKey);
109
- this.pendingDialsByTransportKey.set(transportKey, dialPromise);
170
+ // Start new dial and cache the promise by address key
171
+ const dialPromise = this.performDial(nextHopAddress, addressKey);
172
+ this.pendingDialsByAddress.set(addressKey, dialPromise);
110
173
  try {
111
174
  const connection = await dialPromise;
112
- // Cache the established connection by transport key
113
- this.connectionsByTransportKey.set(transportKey, connection);
175
+ // Add the established connection to the cache array
176
+ const connections = this.connectionsByAddress.get(addressKey) || [];
177
+ connections.push(connection);
178
+ this.connectionsByAddress.set(addressKey, connections);
114
179
  return connection;
115
180
  }
116
181
  finally {
117
- this.pendingDialsByTransportKey.delete(transportKey);
182
+ this.pendingDialsByAddress.delete(addressKey);
118
183
  }
119
184
  }
120
- async performDial(nextHopAddress, transportKey) {
185
+ async performDial(nextHopAddress, addressKey) {
121
186
  this.logger.debug('Dialing new connection', {
122
187
  address: nextHopAddress.value,
123
- transportKey,
188
+ addressKey,
124
189
  });
125
190
  const connection = await this.p2pNode.dial(nextHopAddress.libp2pTransports.map((ma) => ma.toMultiaddr()));
126
191
  this.logger.debug('Successfully dialed connection', {
127
- transportKey,
192
+ addressKey,
128
193
  status: connection.status,
129
194
  remotePeer: connection.remotePeer?.toString(),
130
195
  });
@@ -145,12 +210,17 @@ export class oNodeConnectionManager extends oConnectionManager {
145
210
  requestHandler: config.requestHandler ?? undefined,
146
211
  reusePolicy: reuse ? 'reuse' : 'none',
147
212
  });
148
- const transportKey = this.getTransportKeyFromAddress(nextHopAddress);
149
- if (transportKey) {
150
- this.connectionsByTransportKey.set(transportKey, p2pConnection);
213
+ const addressKey = this.getAddressKey(nextHopAddress);
214
+ if (addressKey) {
215
+ const connections = this.connectionsByAddress.get(addressKey) || [];
216
+ // Only add if not already in the cache
217
+ if (!connections.includes(p2pConnection)) {
218
+ connections.push(p2pConnection);
219
+ this.connectionsByAddress.set(addressKey, connections);
220
+ }
151
221
  }
152
222
  else {
153
- this.logger.error('Should not happen! Failed to generate a transport key for address:', nextHopAddress);
223
+ this.logger.error('Should not happen! Failed to generate an address key for address:', nextHopAddress);
154
224
  }
155
225
  return connection;
156
226
  }
@@ -183,13 +253,14 @@ export class oNodeConnectionManager extends oConnectionManager {
183
253
  */
184
254
  isCached(address) {
185
255
  try {
186
- const transportKey = this.getTransportKeyFromAddress(address);
187
- if (!transportKey) {
256
+ const addressKey = this.getAddressKey(address);
257
+ if (!addressKey) {
188
258
  return false;
189
259
  }
190
- // Check our transport-based cache first
191
- const cachedConnection = this.connectionsByTransportKey.get(transportKey);
192
- if (cachedConnection?.status === 'open') {
260
+ // Check our address-based cache first
261
+ const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
262
+ const bestConnection = this.selectBestConnection(cachedConnections, false);
263
+ if (bestConnection) {
193
264
  return true;
194
265
  }
195
266
  // Fall back to checking libp2p's connections
@@ -202,7 +273,11 @@ export class oNodeConnectionManager extends oConnectionManager {
202
273
  if (hasOpenConnection) {
203
274
  const openConnection = connections.find((conn) => conn.status === 'open');
204
275
  if (openConnection) {
205
- this.connectionsByTransportKey.set(transportKey, openConnection);
276
+ const existingConnections = this.connectionsByAddress.get(addressKey) || [];
277
+ if (!existingConnections.includes(openConnection)) {
278
+ existingConnections.push(openConnection);
279
+ this.connectionsByAddress.set(addressKey, existingConnections);
280
+ }
206
281
  }
207
282
  }
208
283
  return hasOpenConnection;
@@ -219,14 +294,15 @@ export class oNodeConnectionManager extends oConnectionManager {
219
294
  */
220
295
  getCachedLibp2pConnection(address) {
221
296
  try {
222
- const transportKey = this.getTransportKeyFromAddress(address);
223
- if (!transportKey) {
297
+ const addressKey = this.getAddressKey(address);
298
+ if (!addressKey) {
224
299
  return null;
225
300
  }
226
- // Check transport-based cache first
227
- const cachedConnection = this.connectionsByTransportKey.get(transportKey);
228
- if (cachedConnection?.status === 'open') {
229
- return cachedConnection;
301
+ // Check address-based cache first
302
+ const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
303
+ const bestConnection = this.selectBestConnection(cachedConnections, false);
304
+ if (bestConnection) {
305
+ return bestConnection;
230
306
  }
231
307
  const peerId = this.getPeerIdFromAddress(address);
232
308
  if (!peerId) {
@@ -235,16 +311,23 @@ export class oNodeConnectionManager extends oConnectionManager {
235
311
  // Query libp2p for connections to this peer
236
312
  const connections = this.p2pNode.getConnections();
237
313
  const filteredConnections = connections.filter((conn) => conn.remotePeer?.toString() === peerId);
238
- // Find the first open connection
239
- const openConnection = filteredConnections.find((conn) => conn.status === 'open');
240
- // If we found an open connection in libp2p, cache it by transport key
241
- if (openConnection) {
242
- this.connectionsByTransportKey.set(transportKey, openConnection);
243
- return openConnection;
314
+ // Find open connections
315
+ const openConnections = filteredConnections.filter((conn) => conn.status === 'open');
316
+ // If we found open connections in libp2p, add them to cache and select best
317
+ if (openConnections.length > 0) {
318
+ const existingConnections = this.connectionsByAddress.get(addressKey) || [];
319
+ // Add any new connections that aren't already cached
320
+ for (const conn of openConnections) {
321
+ if (!existingConnections.includes(conn)) {
322
+ existingConnections.push(conn);
323
+ }
324
+ }
325
+ this.connectionsByAddress.set(addressKey, existingConnections);
326
+ return this.selectBestConnection(existingConnections, false);
244
327
  }
245
- // Clean up stale cache entry if connection is no longer open
246
- if (cachedConnection) {
247
- this.connectionsByTransportKey.delete(transportKey);
328
+ // Clean up stale cache entries if connections are no longer open
329
+ if (cachedConnections.length > 0) {
330
+ this.connectionsByAddress.delete(addressKey);
248
331
  }
249
332
  return null;
250
333
  }
@@ -258,13 +341,21 @@ export class oNodeConnectionManager extends oConnectionManager {
258
341
  * @returns Object containing cache statistics
259
342
  */
260
343
  getCacheStats() {
344
+ const allConnections = [];
345
+ for (const [addressKey, connections] of this.connectionsByAddress.entries()) {
346
+ for (const conn of connections) {
347
+ allConnections.push({
348
+ peerId: conn.remotePeer?.toString() ?? 'unknown',
349
+ status: conn.status,
350
+ addressKey,
351
+ });
352
+ }
353
+ }
261
354
  return {
262
- cachedConnections: this.connectionsByTransportKey.size,
263
- pendingDials: this.pendingDialsByTransportKey.size,
264
- connectionsByPeer: Array.from(this.connectionsByTransportKey.values()).map((conn) => ({
265
- peerId: conn.remotePeer?.toString() ?? 'unknown',
266
- status: conn.status,
267
- })),
355
+ cachedAddresses: this.connectionsByAddress.size,
356
+ totalCachedConnections: allConnections.length,
357
+ pendingDials: this.pendingDialsByAddress.size,
358
+ connectionsByPeer: allConnections,
268
359
  };
269
360
  }
270
361
  /**
@@ -273,10 +364,19 @@ export class oNodeConnectionManager extends oConnectionManager {
273
364
  */
274
365
  cleanupStaleConnections() {
275
366
  let removed = 0;
276
- for (const [transportKey, connection,] of this.connectionsByTransportKey.entries()) {
277
- if (connection.status !== 'open') {
278
- this.connectionsByTransportKey.delete(transportKey);
279
- removed++;
367
+ for (const [addressKey, connections] of this.connectionsByAddress.entries()) {
368
+ const openConnections = connections.filter((conn) => conn.status === 'open');
369
+ const staleCount = connections.length - openConnections.length;
370
+ if (staleCount > 0) {
371
+ removed += staleCount;
372
+ if (openConnections.length === 0) {
373
+ // Remove the entire entry if no connections remain
374
+ this.connectionsByAddress.delete(addressKey);
375
+ }
376
+ else {
377
+ // Keep only the open connections
378
+ this.connectionsByAddress.set(addressKey, openConnections);
379
+ }
280
380
  }
281
381
  }
282
382
  if (removed > 0) {
@@ -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;CAmEF"}
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"}
@@ -4,12 +4,13 @@ import { oNodeTransport } from '../router/o-node.transport.js';
4
4
  export class ConnectionUtils extends oObject {
5
5
  static async addressFromConnection(options) {
6
6
  try {
7
- console.log('[ConnectionUtils] addressFromConnection');
8
7
  const { currentNode, connection } = options;
9
8
  const p2pNode = currentNode.p2pNode;
10
9
  // Extract the actual olane address from the peer store
11
10
  const peers = await p2pNode.peerStore.all();
12
- const remotePeer = peers.find((peer) => peer.id.toString() === connection.remotePeer.toString());
11
+ const remotePeer = peers.find((peer) => {
12
+ return peer.id.toString() === connection.remotePeer.toString();
13
+ });
13
14
  if (!remotePeer) {
14
15
  console.log('Failed to find peer:', remotePeer);
15
16
  throw new Error(`Failed to extract remote address, peer ${connection.remotePeer.toString()} not found in peer store.`);
@@ -19,8 +20,7 @@ export class ConnectionUtils extends oObject {
19
20
  if (!originAddress) {
20
21
  throw new Error('Origin address is not configured');
21
22
  }
22
- const originProtocol = originAddress.toString();
23
- const oProtocol = remotePeer.protocols.find((p) => p.startsWith('/o/') && p.startsWith(originProtocol) === false);
23
+ const oProtocol = remotePeer.protocols.find((p) => p.startsWith('/o/'));
24
24
  if (!oProtocol) {
25
25
  throw new Error('Failed to extract remote address, could not find o-protocol in peer protocols.');
26
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olane/o-node",
3
- "version": "0.7.43",
3
+ "version": "0.7.45",
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.43",
43
+ "@olane/o-test": "0.7.45",
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.43",
64
- "@olane/o-core": "0.7.43",
65
- "@olane/o-protocol": "0.7.43",
66
- "@olane/o-tool": "0.7.43",
63
+ "@olane/o-config": "0.7.45",
64
+ "@olane/o-core": "0.7.45",
65
+ "@olane/o-protocol": "0.7.45",
66
+ "@olane/o-tool": "0.7.45",
67
67
  "debug": "^4.4.1",
68
68
  "dotenv": "^16.5.0",
69
69
  "json5": "^2.2.3"
70
70
  },
71
- "gitHead": "12e63fad2b09173fbcf455d881276b7ca7b3344d"
71
+ "gitHead": "d336869dfa3105b453d2249ae388870088b17343"
72
72
  }