@olane/o-node 0.7.44 → 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.
@@ -27,6 +27,15 @@ export declare class oNodeConnectionManager extends oConnectionManager {
27
27
  * @returns The peer ID string or null if not found
28
28
  */
29
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;
30
39
  getOrCreateConnection(nextHopAddress: oAddress, address: oAddress): Promise<Connection>;
31
40
  private performDial;
32
41
  answer(config: oConnectionConfig & {
@@ -56,11 +65,13 @@ export declare class oNodeConnectionManager extends oConnectionManager {
56
65
  * @returns Object containing cache statistics
57
66
  */
58
67
  getCacheStats(): {
59
- cachedConnections: number;
68
+ cachedAddresses: number;
69
+ totalCachedConnections: number;
60
70
  pendingDials: number;
61
71
  connectionsByPeer: Array<{
62
72
  peerId: string;
63
73
  status: string;
74
+ addressKey: string;
64
75
  }>;
65
76
  };
66
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,oBAAoB,CAAsC;IAClE,OAAO,CAAC,qBAAqB,CACjB;gBAES,MAAM,EAAE,4BAA4B;IAWzD;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAsBhC;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IASrB;;;;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"}
@@ -22,10 +22,18 @@ export class oNodeConnectionManager extends oConnectionManager {
22
22
  if (!connection) {
23
23
  return;
24
24
  }
25
- for (const [addressKey, cachedConnection,] of this.connectionsByAddress.entries()) {
26
- if (cachedConnection === connection) {
25
+ for (const [addressKey, cachedConnections,] of this.connectionsByAddress.entries()) {
26
+ const index = cachedConnections.indexOf(connection);
27
+ if (index !== -1) {
27
28
  this.logger.debug('Connection closed, removing from cache for address:', addressKey);
28
- this.connectionsByAddress.delete(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
  });
@@ -63,6 +71,59 @@ export class oNodeConnectionManager extends oConnectionManager {
63
71
  return null;
64
72
  }
65
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
+ }
66
127
  async getOrCreateConnection(nextHopAddress, address) {
67
128
  if (!nextHopAddress) {
68
129
  throw new Error('Invalid address passed');
@@ -72,22 +133,32 @@ export class oNodeConnectionManager extends oConnectionManager {
72
133
  if (!addressKey) {
73
134
  throw new Error(`Unable to extract address key from address: ${nextHopAddress.toString()}`);
74
135
  }
75
- // Check if we have a cached connection by address key
76
- const cachedConnection = this.connectionsByAddress.get(addressKey);
77
- if (cachedConnection && cachedConnection.status === 'open') {
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) {
78
140
  this.logger.debug('Reusing cached connection for address:', nextHopAddress?.value);
79
- return cachedConnection;
141
+ return bestConnection;
80
142
  }
81
- // Clean up stale connection if it exists but is not open
82
- if (cachedConnection && cachedConnection.status !== 'open') {
83
- this.logger.debug('Removing stale connection for address:', addressKey);
84
- this.connectionsByAddress.delete(addressKey);
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
+ }
85
154
  }
86
155
  // Check if libp2p has an active connection for this address
87
156
  const libp2pConnection = this.getCachedLibp2pConnection(nextHopAddress);
88
157
  if (libp2pConnection && libp2pConnection.status === 'open') {
89
158
  this.logger.debug('Caching existing libp2p connection for address:', addressKey);
90
- this.connectionsByAddress.set(addressKey, libp2pConnection);
159
+ const connections = this.connectionsByAddress.get(addressKey) || [];
160
+ connections.push(libp2pConnection);
161
+ this.connectionsByAddress.set(addressKey, connections);
91
162
  return libp2pConnection;
92
163
  }
93
164
  // Check if dial is already in progress for this address key
@@ -101,8 +172,10 @@ export class oNodeConnectionManager extends oConnectionManager {
101
172
  this.pendingDialsByAddress.set(addressKey, dialPromise);
102
173
  try {
103
174
  const connection = await dialPromise;
104
- // Cache the established connection by address key
105
- this.connectionsByAddress.set(addressKey, 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);
106
179
  return connection;
107
180
  }
108
181
  finally {
@@ -139,7 +212,12 @@ export class oNodeConnectionManager extends oConnectionManager {
139
212
  });
140
213
  const addressKey = this.getAddressKey(nextHopAddress);
141
214
  if (addressKey) {
142
- this.connectionsByAddress.set(addressKey, p2pConnection);
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
+ }
143
221
  }
144
222
  else {
145
223
  this.logger.error('Should not happen! Failed to generate an address key for address:', nextHopAddress);
@@ -180,8 +258,9 @@ export class oNodeConnectionManager extends oConnectionManager {
180
258
  return false;
181
259
  }
182
260
  // Check our address-based cache first
183
- const cachedConnection = this.connectionsByAddress.get(addressKey);
184
- if (cachedConnection?.status === 'open') {
261
+ const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
262
+ const bestConnection = this.selectBestConnection(cachedConnections, false);
263
+ if (bestConnection) {
185
264
  return true;
186
265
  }
187
266
  // Fall back to checking libp2p's connections
@@ -194,7 +273,11 @@ export class oNodeConnectionManager extends oConnectionManager {
194
273
  if (hasOpenConnection) {
195
274
  const openConnection = connections.find((conn) => conn.status === 'open');
196
275
  if (openConnection) {
197
- this.connectionsByAddress.set(addressKey, openConnection);
276
+ const existingConnections = this.connectionsByAddress.get(addressKey) || [];
277
+ if (!existingConnections.includes(openConnection)) {
278
+ existingConnections.push(openConnection);
279
+ this.connectionsByAddress.set(addressKey, existingConnections);
280
+ }
198
281
  }
199
282
  }
200
283
  return hasOpenConnection;
@@ -216,9 +299,10 @@ export class oNodeConnectionManager extends oConnectionManager {
216
299
  return null;
217
300
  }
218
301
  // Check address-based cache first
219
- const cachedConnection = this.connectionsByAddress.get(addressKey);
220
- if (cachedConnection?.status === 'open') {
221
- return cachedConnection;
302
+ const cachedConnections = this.connectionsByAddress.get(addressKey) || [];
303
+ const bestConnection = this.selectBestConnection(cachedConnections, false);
304
+ if (bestConnection) {
305
+ return bestConnection;
222
306
  }
223
307
  const peerId = this.getPeerIdFromAddress(address);
224
308
  if (!peerId) {
@@ -227,15 +311,22 @@ export class oNodeConnectionManager extends oConnectionManager {
227
311
  // Query libp2p for connections to this peer
228
312
  const connections = this.p2pNode.getConnections();
229
313
  const filteredConnections = connections.filter((conn) => conn.remotePeer?.toString() === peerId);
230
- // Find the first open connection
231
- const openConnection = filteredConnections.find((conn) => conn.status === 'open');
232
- // If we found an open connection in libp2p, cache it by address key
233
- if (openConnection) {
234
- this.connectionsByAddress.set(addressKey, openConnection);
235
- 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);
236
327
  }
237
- // Clean up stale cache entry if connection is no longer open
238
- if (cachedConnection) {
328
+ // Clean up stale cache entries if connections are no longer open
329
+ if (cachedConnections.length > 0) {
239
330
  this.connectionsByAddress.delete(addressKey);
240
331
  }
241
332
  return null;
@@ -250,13 +341,21 @@ export class oNodeConnectionManager extends oConnectionManager {
250
341
  * @returns Object containing cache statistics
251
342
  */
252
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
+ }
253
354
  return {
254
- cachedConnections: this.connectionsByAddress.size,
355
+ cachedAddresses: this.connectionsByAddress.size,
356
+ totalCachedConnections: allConnections.length,
255
357
  pendingDials: this.pendingDialsByAddress.size,
256
- connectionsByPeer: Array.from(this.connectionsByAddress.values()).map((conn) => ({
257
- peerId: conn.remotePeer?.toString() ?? 'unknown',
258
- status: conn.status,
259
- })),
358
+ connectionsByPeer: allConnections,
260
359
  };
261
360
  }
262
361
  /**
@@ -265,10 +364,19 @@ export class oNodeConnectionManager extends oConnectionManager {
265
364
  */
266
365
  cleanupStaleConnections() {
267
366
  let removed = 0;
268
- for (const [addressKey, connection,] of this.connectionsByAddress.entries()) {
269
- if (connection.status !== 'open') {
270
- this.connectionsByAddress.delete(addressKey);
271
- 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
+ }
272
380
  }
273
381
  }
274
382
  if (removed > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olane/o-node",
3
- "version": "0.7.44",
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.44",
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.44",
64
- "@olane/o-core": "0.7.44",
65
- "@olane/o-protocol": "0.7.44",
66
- "@olane/o-tool": "0.7.44",
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": "de7bc5247c97fb219d522cc5874f7459634e03ec"
71
+ "gitHead": "d336869dfa3105b453d2249ae388870088b17343"
72
72
  }