@olane/o-node 0.7.40 → 0.7.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/src/connection/index.d.ts +1 -1
  2. package/dist/src/connection/index.d.ts.map +1 -1
  3. package/dist/src/connection/index.js +1 -1
  4. package/dist/src/connection/interfaces/o-node-connection-stream.config.d.ts +8 -0
  5. package/dist/src/connection/interfaces/o-node-connection-stream.config.d.ts.map +1 -0
  6. package/dist/src/connection/interfaces/o-node-connection-stream.config.js +1 -0
  7. package/dist/src/connection/o-node-connection-stream.d.ts +34 -0
  8. package/dist/src/connection/o-node-connection-stream.d.ts.map +1 -0
  9. package/dist/src/connection/o-node-connection-stream.js +68 -0
  10. package/dist/src/connection/o-node-connection.d.ts +10 -4
  11. package/dist/src/connection/o-node-connection.d.ts.map +1 -1
  12. package/dist/src/connection/o-node-connection.js +51 -34
  13. package/dist/src/connection/o-node-connection.manager.d.ts +4 -0
  14. package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
  15. package/dist/src/connection/o-node-connection.manager.js +24 -0
  16. package/dist/src/connection/stream-handler.d.ts +1 -59
  17. package/dist/src/connection/stream-handler.d.ts.map +1 -1
  18. package/dist/src/connection/stream-handler.js +104 -179
  19. package/dist/src/o-node.tool.d.ts +3 -1
  20. package/dist/src/o-node.tool.d.ts.map +1 -1
  21. package/dist/src/o-node.tool.js +44 -11
  22. package/dist/src/router/o-node.address.d.ts +1 -0
  23. package/dist/src/router/o-node.address.d.ts.map +1 -1
  24. package/dist/src/router/o-node.address.js +4 -0
  25. package/dist/src/utils/connection.utils.d.ts +9 -0
  26. package/dist/src/utils/connection.utils.d.ts.map +1 -0
  27. package/dist/src/utils/connection.utils.js +26 -0
  28. package/dist/src/utils/index.d.ts +1 -0
  29. package/dist/src/utils/index.d.ts.map +1 -1
  30. package/dist/src/utils/index.js +1 -0
  31. package/dist/test/astream-reuse.spec.d.ts +2 -0
  32. package/dist/test/astream-reuse.spec.d.ts.map +1 -0
  33. package/dist/test/astream-reuse.spec.js +107 -0
  34. package/dist/test/connection-management.spec.js +1 -0
  35. package/dist/test/helpers/network-builder.d.ts.map +1 -1
  36. package/package.json +7 -7
  37. package/dist/src/connection/o-managed-stream.d.ts +0 -57
  38. package/dist/src/connection/o-managed-stream.d.ts.map +0 -1
  39. package/dist/src/connection/o-managed-stream.js +0 -76
  40. package/dist/test/o-managed-stream.spec.d.ts +0 -2
  41. package/dist/test/o-managed-stream.spec.d.ts.map +0 -1
  42. package/dist/test/o-managed-stream.spec.js +0 -122
  43. package/dist/test/stream-handler-caching.spec.d.ts +0 -2
  44. package/dist/test/stream-handler-caching.spec.d.ts.map +0 -1
  45. package/dist/test/stream-handler-caching.spec.js +0 -261
@@ -1,7 +1,6 @@
1
1
  import { oRequest, oResponse, CoreUtils, oError, oErrorCodes, Logger, ResponseBuilder, } from '@olane/o-core';
2
2
  import { lpStream } from '@olane/o-config';
3
3
  import JSON5 from 'json5';
4
- import { oManagedStream } from './o-managed-stream.js';
5
4
  /**
6
5
  * StreamHandler centralizes all stream-related functionality including:
7
6
  * - Message type detection (request vs response)
@@ -12,7 +11,6 @@ import { oManagedStream } from './o-managed-stream.js';
12
11
  */
13
12
  export class StreamHandler {
14
13
  constructor(logger) {
15
- this.streamCache = new Map();
16
14
  this.logger = logger ?? new Logger('StreamHandler');
17
15
  }
18
16
  /**
@@ -85,161 +83,110 @@ export class StreamHandler {
85
83
  const addresses = [callerAddress.value, receiverAddress.value].sort();
86
84
  return `${addresses[0]}↔${addresses[1]}`;
87
85
  }
88
- /**
89
- * Caches a managed stream for reuse
90
- *
91
- * @param managedStream - The managed stream to cache
92
- */
93
- cacheStream(managedStream) {
94
- this.streamCache.set(managedStream.cacheKey, managedStream);
95
- this.logger.debug('Cached stream', {
96
- cacheKey: managedStream.cacheKey,
97
- direction: managedStream.direction,
98
- streamId: managedStream.stream.id,
99
- });
100
- }
101
- /**
102
- * Removes a stream from the cache
103
- *
104
- * @param stream - The stream to remove
105
- */
106
- removeStreamFromCache(stream) {
107
- // Find and remove the stream from cache
108
- for (const [key, managedStream] of this.streamCache.entries()) {
109
- if (managedStream.stream.id === stream.id) {
110
- this.streamCache.delete(key);
111
- this.logger.debug('Removed stream from cache', {
112
- cacheKey: key,
113
- streamId: stream.id,
114
- });
115
- break;
116
- }
117
- }
118
- }
119
- /**
120
- * Sets up cleanup listener for stream close events
121
- *
122
- * @param stream - The stream to monitor
123
- */
124
- setupStreamCleanup(stream) {
125
- // Listen for stream close event
126
- stream.addEventListener('close', () => {
127
- this.removeStreamFromCache(stream);
128
- });
129
- }
130
- /**
131
- * Extracts the remote peer's address from a connection
132
- * Falls back to creating an address from the peer ID if no address metadata is available
133
- *
134
- * @param connection - The libp2p connection
135
- * @returns The remote peer's oAddress
136
- */
137
- extractRemotePeerAddress(connection) {
138
- // Try to get address from connection metadata if available
139
- // For now, create address from peer ID as fallback
140
- const peerId = connection.remotePeer.toString();
141
- // Import oAddress at runtime to avoid circular dependency
142
- const { oAddress: oAddressClass } = require('@olane/o-core');
143
- return new oAddressClass(`o://peer/${peerId}`);
144
- }
145
- /**
146
- * Caches an inbound stream for bidirectional reuse
147
- * This allows the same stream to be reused for responses
148
- *
149
- * @param stream - The inbound stream to cache
150
- * @param callerAddress - The address of the caller
151
- * @param receiverAddress - The address of the receiver (local node)
152
- * @param reusePolicy - Whether to enable caching
153
- */
154
- cacheInboundStream(stream, callerAddress, receiverAddress, reusePolicy = 'none') {
155
- if (reusePolicy !== 'reuse') {
156
- return;
157
- }
158
- const cacheKey = this.buildCacheKey(callerAddress, receiverAddress);
159
- // Check if already cached
160
- if (this.streamCache.has(cacheKey)) {
161
- this.logger.debug('Stream already cached', { cacheKey });
162
- return;
163
- }
164
- const managedStream = new oManagedStream(stream, callerAddress, receiverAddress, 'inbound');
165
- this.cacheStream(managedStream);
166
- this.setupStreamCleanup(stream);
167
- }
168
- /**
169
- * Gets an existing open stream or creates a new one based on reuse policy
170
- *
171
- * @param connection - The libp2p connection
172
- * @param protocol - The protocol to use for the stream
173
- * @param config - Stream handler configuration
174
- * @param streamAddresses - Optional addresses for address-based stream reuse
175
- */
176
- async getOrCreateStream(connection, protocol, config = {}, streamAddresses) {
177
- this.logger.debug(`Getting or creating stream for protocol: ${protocol}, connection`, {
178
- status: connection.status,
179
- remoteAddr: connection.remoteAddr.toString(),
180
- streamCount: connection.streams.length,
181
- reusePolicy: config.reusePolicy ?? 'none',
182
- hasAddresses: !!streamAddresses,
183
- });
184
- if (connection.status !== 'open') {
185
- throw new oError(oErrorCodes.INVALID_STATE, 'Connection not open');
186
- }
187
- const reusePolicy = config.reusePolicy ?? 'none';
188
- // Check address-based cache if reuse is enabled and addresses provided
189
- if (reusePolicy === 'reuse' && streamAddresses) {
190
- const cacheKey = this.buildCacheKey(streamAddresses.callerAddress, streamAddresses.receiverAddress);
191
- const cachedStream = this.streamCache.get(cacheKey);
192
- if (cachedStream?.isReusable) {
193
- this.logger.debug('Reusing cached stream by address', {
194
- cacheKey,
195
- streamId: cachedStream.stream.id,
196
- direction: cachedStream.direction,
197
- });
198
- cachedStream.updateLastUsed();
199
- return cachedStream.stream;
200
- }
201
- else if (cachedStream) {
202
- // Stream exists but not reusable, remove from cache
203
- this.logger.debug('Removing non-reusable stream from cache', {
204
- cacheKey,
205
- status: cachedStream.stream.status,
206
- });
207
- this.streamCache.delete(cacheKey);
208
- }
209
- }
210
- this.logger.debug('No reusable cached stream found, checking connection streams', connection.streams.map((s) => ({
211
- status: s.status,
212
- protocol: s.protocol,
213
- writeStatus: s.writeStatus,
214
- remoteReadStatus: s.remoteReadStatus,
215
- id: s.id,
216
- })));
217
- // Fallback to protocol-based check (legacy behavior)
218
- if (reusePolicy === 'reuse' && !streamAddresses) {
219
- const existingStream = connection.streams.find((stream) => stream.status === 'open' &&
220
- stream.protocol === protocol &&
221
- stream.writeStatus === 'writable' &&
222
- stream.remoteReadStatus === 'readable');
223
- if (existingStream) {
224
- this.logger.debug('Reusing existing stream by protocol (legacy)', existingStream.id, existingStream.direction);
225
- return existingStream;
226
- }
227
- }
228
- // Create new stream
229
- this.logger.debug('Creating new stream', { protocol });
230
- const stream = await connection.newStream(protocol, {
231
- signal: config.signal,
232
- maxOutboundStreams: config.maxOutboundStreams ?? 1000,
233
- runOnLimitedConnection: config.runOnLimitedConnection ?? false,
234
- });
235
- // Cache the stream if reuse is enabled and addresses are provided
236
- if (reusePolicy === 'reuse' && streamAddresses) {
237
- const managedStream = new oManagedStream(stream, streamAddresses.callerAddress, streamAddresses.receiverAddress, streamAddresses.direction);
238
- this.cacheStream(managedStream);
239
- this.setupStreamCleanup(stream);
240
- }
241
- return stream;
242
- }
86
+ // /**
87
+ // * Gets an existing open stream or creates a new one based on reuse policy
88
+ // *
89
+ // * @param connection - The libp2p connection
90
+ // * @param protocol - The protocol to use for the stream
91
+ // * @param config - Stream handler configuration
92
+ // * @param streamAddresses - Optional addresses for address-based stream reuse
93
+ // */
94
+ // async getOrCreateStream(
95
+ // connection: Connection,
96
+ // protocol: string,
97
+ // config: StreamHandlerConfig = {},
98
+ // streamAddresses?: {
99
+ // callerAddress: oAddress;
100
+ // receiverAddress: oAddress;
101
+ // direction: 'inbound' | 'outbound';
102
+ // },
103
+ // ): Promise<Stream> {
104
+ // this.logger.debug(
105
+ // `Getting or creating stream for protocol: ${protocol}, connection`,
106
+ // {
107
+ // status: connection.status,
108
+ // remoteAddr: connection.remoteAddr.toString(),
109
+ // streamCount: connection.streams.length,
110
+ // reusePolicy: config.reusePolicy ?? 'none',
111
+ // hasAddresses: !!streamAddresses,
112
+ // },
113
+ // );
114
+ // if (connection.status !== 'open') {
115
+ // throw new oError(oErrorCodes.INVALID_STATE, 'Connection not open');
116
+ // }
117
+ // const reusePolicy = config.reusePolicy ?? 'none';
118
+ // // Check address-based cache if reuse is enabled and addresses provided
119
+ // if (reusePolicy === 'reuse' && streamAddresses) {
120
+ // const cacheKey = this.buildCacheKey(
121
+ // streamAddresses.callerAddress,
122
+ // streamAddresses.receiverAddress,
123
+ // );
124
+ // const cachedStream = this.streamCache.get(cacheKey);
125
+ // if (cachedStream?.isReusable) {
126
+ // this.logger.debug('Reusing cached stream by address', {
127
+ // cacheKey,
128
+ // streamId: cachedStream.stream.id,
129
+ // direction: cachedStream.direction,
130
+ // });
131
+ // cachedStream.updateLastUsed();
132
+ // return cachedStream.stream;
133
+ // } else if (cachedStream) {
134
+ // // Stream exists but not reusable, remove from cache
135
+ // this.logger.debug('Removing non-reusable stream from cache', {
136
+ // cacheKey,
137
+ // status: cachedStream.stream.status,
138
+ // });
139
+ // this.streamCache.delete(cacheKey);
140
+ // }
141
+ // }
142
+ // this.logger.debug(
143
+ // 'No reusable cached stream found, checking connection streams',
144
+ // connection.streams.map((s) => ({
145
+ // status: s.status,
146
+ // protocol: s.protocol,
147
+ // writeStatus: s.writeStatus,
148
+ // remoteReadStatus: s.remoteReadStatus,
149
+ // id: s.id,
150
+ // })),
151
+ // );
152
+ // // Fallback to protocol-based check (legacy behavior)
153
+ // if (reusePolicy === 'reuse' && !streamAddresses) {
154
+ // const existingStream = connection.streams.find(
155
+ // (stream) =>
156
+ // stream.status === 'open' &&
157
+ // stream.protocol === protocol &&
158
+ // stream.writeStatus === 'writable' &&
159
+ // stream.remoteReadStatus === 'readable',
160
+ // );
161
+ // if (existingStream) {
162
+ // this.logger.debug(
163
+ // 'Reusing existing stream by protocol (legacy)',
164
+ // existingStream.id,
165
+ // existingStream.direction,
166
+ // );
167
+ // return existingStream;
168
+ // }
169
+ // }
170
+ // // Create new stream
171
+ // this.logger.debug('Creating new stream', { protocol });
172
+ // const stream = await connection.newStream(protocol, {
173
+ // signal: config.signal,
174
+ // maxOutboundStreams: config.maxOutboundStreams ?? 1000,
175
+ // runOnLimitedConnection: config.runOnLimitedConnection ?? false,
176
+ // });
177
+ // // Cache the stream if reuse is enabled and addresses are provided
178
+ // if (reusePolicy === 'reuse' && streamAddresses) {
179
+ // const managedStream = new oNodeConnectionStream(
180
+ // stream,
181
+ // streamAddresses.callerAddress,
182
+ // streamAddresses.receiverAddress,
183
+ // streamAddresses.direction,
184
+ // );
185
+ // this.cacheStream(managedStream);
186
+ // this.setupStreamCleanup(stream);
187
+ // }
188
+ // return stream;
189
+ // }
243
190
  /**
244
191
  * Sends data through a stream using length-prefixed encoding (libp2p v3 best practice)
245
192
  * Each message is automatically prefixed with a varint indicating the message length
@@ -253,28 +200,6 @@ export class StreamHandler {
253
200
  const lp = lpStream(stream);
254
201
  await lp.write(data, { signal: config.signal });
255
202
  }
256
- /**
257
- * Closes a stream safely with error handling
258
- *
259
- * @param stream - The stream to close
260
- * @param config - Configuration including reuse policy
261
- */
262
- async close(stream, config = {}) {
263
- // Don't close if reuse policy is enabled
264
- if (config.reusePolicy === 'reuse') {
265
- this.logger.debug('Stream reuse enabled, not closing stream');
266
- return;
267
- }
268
- if (stream.status === 'open') {
269
- try {
270
- // force the close for now until we can implement a proper close
271
- await stream.abort(new Error('Stream closed'));
272
- }
273
- catch (error) {
274
- this.logger.debug('Error closing stream:', error.message);
275
- }
276
- }
277
- }
278
203
  /**
279
204
  * Handles an incoming stream on the server side using length-prefixed protocol
280
205
  * Uses async read loops instead of event listeners (libp2p v3 best practice)
@@ -9,9 +9,11 @@ declare const oNodeTool_base: typeof oServerNode;
9
9
  */
10
10
  export declare class oNodeTool extends oNodeTool_base {
11
11
  private streamHandler;
12
+ handleProtocolReuse(address: oAddress): Promise<void>;
12
13
  handleProtocol(address: oAddress): Promise<void>;
13
14
  initialize(): Promise<void>;
14
- handleStream(stream: Stream, connection: Connection): Promise<void>;
15
+ handleStreamReuse(stream: Stream, connection: Connection): Promise<void>;
16
+ handleStream(stream: Stream, connection: Connection, reuse?: boolean): Promise<void>;
15
17
  _tool_identify(): Promise<any>;
16
18
  _tool_child_register(request: oRequest): Promise<any>;
17
19
  }
@@ -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;;AAKrD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAkB;IAC/C,OAAO,CAAC,aAAa,CAAiB;IAEhC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAehC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAoCnE,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;IAgBrC,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;IAwCV,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAQ9B,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CAgC5D"}
@@ -4,25 +4,46 @@ import { oServerNode } from './nodes/server.node.js';
4
4
  import { oNodeTransport } from './router/o-node.transport.js';
5
5
  import { oNodeAddress } from './router/o-node.address.js';
6
6
  import { StreamHandler } from './connection/stream-handler.js';
7
+ import { ConnectionUtils } from './utils/connection.utils.js';
7
8
  /**
8
9
  * oTool is a mixin that extends the base class and implements the oTool interface
9
10
  * @param Base - The base class to extend
10
11
  * @returns A new class that extends the base class and implements the oTool interface
11
12
  */
12
13
  export class oNodeTool extends oTool(oServerNode) {
14
+ async handleProtocolReuse(address) {
15
+ const reuseProtocol = address.protocol + '/reuse';
16
+ const protocols = this.p2pNode.getProtocols();
17
+ if (protocols.find((p) => p === reuseProtocol)) {
18
+ // already handling
19
+ return;
20
+ }
21
+ const maxOutboundsStreams = process.env.MAX_OUTBOUND_STREAMS
22
+ ? parseInt(process.env.MAX_OUTBOUND_STREAMS)
23
+ : 1000;
24
+ await this.p2pNode.handle(reuseProtocol, this.handleStream.bind(this), {
25
+ maxInboundStreams: 10000,
26
+ maxOutboundStreams: maxOutboundsStreams,
27
+ });
28
+ }
13
29
  async handleProtocol(address) {
14
30
  const protocols = this.p2pNode.getProtocols();
15
31
  if (protocols.find((p) => p === address.protocol)) {
16
32
  // already handling
17
33
  return;
18
34
  }
19
- this.logger.debug('Handling protocol: ' + address.protocol);
35
+ const maxOutboundsStreams = process.env.MAX_OUTBOUND_STREAMS
36
+ ? parseInt(process.env.MAX_OUTBOUND_STREAMS)
37
+ : 1000;
38
+ this.logger.debug('Handling protocol: ' + address.protocol, {
39
+ maxInboundStreams: 10000,
40
+ maxOutboundStreams: maxOutboundsStreams,
41
+ });
20
42
  await this.p2pNode.handle(address.protocol, this.handleStream.bind(this), {
21
43
  maxInboundStreams: 10000,
22
- maxOutboundStreams: process.env.MAX_OUTBOUND_STREAMS
23
- ? parseInt(process.env.MAX_OUTBOUND_STREAMS)
24
- : 1000,
44
+ maxOutboundStreams: maxOutboundsStreams,
25
45
  });
46
+ await this.handleProtocolReuse(address);
26
47
  }
27
48
  async initialize() {
28
49
  await super.initialize();
@@ -33,12 +54,25 @@ export class oNodeTool extends oTool(oServerNode) {
33
54
  await this.handleProtocol(this.staticAddress);
34
55
  }
35
56
  }
36
- async handleStream(stream, connection) {
37
- // Extract caller address from connection
38
- const callerAddress = this.streamHandler.extractRemotePeerAddress(connection);
39
- // Cache inbound stream for bidirectional reuse (if reuse policy is enabled)
40
- // The cacheInboundStream method will check the reuse policy
41
- this.streamHandler.cacheInboundStream(stream, callerAddress, this.address, 'reuse');
57
+ async handleStreamReuse(stream, connection) {
58
+ return this.handleStream(stream, connection, true);
59
+ }
60
+ async handleStream(stream, connection, reuse) {
61
+ if (reuse) {
62
+ this.logger.debug('Handle stream with reuse = true');
63
+ }
64
+ // record inbound connection to manager
65
+ const remoteAddress = await ConnectionUtils.addressFromConnection({
66
+ currentNode: this,
67
+ connection: connection,
68
+ });
69
+ this.connectionManager.answer({
70
+ nextHopAddress: remoteAddress,
71
+ address: remoteAddress,
72
+ callerAddress: this.address,
73
+ p2pConnection: connection,
74
+ reuse,
75
+ });
42
76
  // Use StreamHandler for consistent stream handling
43
77
  // This follows libp2p v3 best practices for length-prefixed streaming
44
78
  await this.streamHandler.handleIncomingStream(stream, connection, async (request, stream) => {
@@ -61,7 +95,6 @@ export class oNodeTool extends oTool(oServerNode) {
61
95
  };
62
96
  }
63
97
  async _tool_child_register(request) {
64
- this.logger.debug('Child register: ', request.params);
65
98
  const { address, transports } = request.params;
66
99
  const childAddress = new oNodeAddress(address, transports.map((t) => new oNodeTransport(t)));
67
100
  // Add child to hierarchy
@@ -14,5 +14,6 @@ export declare class oNodeAddress extends oAddress {
14
14
  toString(): string;
15
15
  toMultiaddr(): Multiaddr;
16
16
  static fromMultiaddr(ma: Multiaddr): oAddress;
17
+ static fromProtocol(protocol: string): oAddress;
17
18
  }
18
19
  //# sourceMappingURL=o-node.address.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"o-node.address.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.address.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAa,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAqB,QAAQ,EAAiB,MAAM,eAAe,CAAC;AAE3E,qBAAa,YAAa,SAAQ,QAAQ;aAItB,KAAK,EAAE,MAAM;IAHxB,UAAU,EAAE,cAAc,EAAE,CAAM;gBAGvB,KAAK,EAAE,MAAM,EAC7B,UAAU,GAAE,cAAc,EAAO;IAMnC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;KACpC,GAAG,YAAY;IAShB,IAAI,gBAAgB,IAAI,cAAc,EAAE,CAEvC;IAED,IAAI,gBAAgB,IAAI,cAAc,EAAE,CAEvC;IAED,QAAQ,IAAI,MAAM;IAIlB,WAAW,IAAI,SAAS;IAIxB,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,SAAS,GAAG,QAAQ;CAG9C"}
1
+ {"version":3,"file":"o-node.address.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.address.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAa,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAiB,MAAM,eAAe,CAAC;AAExD,qBAAa,YAAa,SAAQ,QAAQ;aAItB,KAAK,EAAE,MAAM;IAHxB,UAAU,EAAE,cAAc,EAAE,CAAM;gBAGvB,KAAK,EAAE,MAAM,EAC7B,UAAU,GAAE,cAAc,EAAO;IAMnC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;KACpC,GAAG,YAAY;IAShB,IAAI,gBAAgB,IAAI,cAAc,EAAE,CAEvC;IAED,IAAI,gBAAgB,IAAI,cAAc,EAAE,CAEvC;IAED,QAAQ,IAAI,MAAM;IAIlB,WAAW,IAAI,SAAS;IAIxB,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,SAAS,GAAG,QAAQ;IAI7C,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ;CAIhD"}
@@ -26,4 +26,8 @@ export class oNodeAddress extends oAddress {
26
26
  static fromMultiaddr(ma) {
27
27
  return new oAddress(ma.toString().replace('/o/', 'o://'));
28
28
  }
29
+ static fromProtocol(protocol) {
30
+ const addressInput = protocol.replace('/o/', 'o://');
31
+ return new oAddress(addressInput);
32
+ }
29
33
  }
@@ -0,0 +1,9 @@
1
+ import { Connection } from '@olane/o-config';
2
+ import { oObject } from '@olane/o-core';
3
+ export declare class ConnectionUtils extends oObject {
4
+ static addressFromConnection(options: {
5
+ currentNode: any;
6
+ connection: Connection;
7
+ }): Promise<import("@olane/o-core").oAddress>;
8
+ }
9
+ //# sourceMappingURL=connection.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/connection.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAGxC,qBAAa,eAAgB,SAAQ,OAAO;WACtB,qBAAqB,CAAC,OAAO,EAAE;QACjD,WAAW,EAAE,GAAG,CAAC;QACjB,UAAU,EAAE,UAAU,CAAC;KACxB;CAsCF"}
@@ -0,0 +1,26 @@
1
+ import { oObject } from '@olane/o-core';
2
+ import { oNodeAddress } from '../router/o-node.address.js';
3
+ export class ConnectionUtils extends oObject {
4
+ static async addressFromConnection(options) {
5
+ const { currentNode, connection } = options;
6
+ const p2pNode = currentNode.p2pNode;
7
+ // Extract the actual olane address from the peer store
8
+ const peers = await p2pNode.peerStore.all();
9
+ const remotePeer = peers.find((peer) => peer.id.toString() === connection.remotePeer.toString());
10
+ if (!remotePeer) {
11
+ console.log('Failed to find peer:', remotePeer);
12
+ throw new Error(`Failed to extract remote address, peer ${connection.remotePeer.toString()} not found in peer store.`);
13
+ }
14
+ // Get origin address for comparison
15
+ const originAddress = currentNode.address?.value;
16
+ if (!originAddress) {
17
+ throw new Error('Origin address is not configured');
18
+ }
19
+ const originProtocol = originAddress.toString();
20
+ const oProtocol = remotePeer.protocols.find((p) => p.startsWith('/o/') && p.startsWith(originProtocol) === false);
21
+ if (!oProtocol) {
22
+ throw new Error('Failed to extract remote address, could not find o-protocol in peer protocols.');
23
+ }
24
+ return oNodeAddress.fromProtocol(oProtocol);
25
+ }
26
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './stream.utils.js';
2
2
  export * from './network.utils.js';
3
+ export * from './connection.utils.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC"}
@@ -1,2 +1,3 @@
1
1
  export * from './stream.utils.js';
2
2
  export * from './network.utils.js';
3
+ export * from './connection.utils.js';
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=astream-reuse.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"astream-reuse.spec.d.ts","sourceRoot":"","sources":["../../test/astream-reuse.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,107 @@
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
+ });
@@ -16,6 +16,7 @@ describe('Connection Management', () => {
16
16
  describe('Connection Pooling', () => {
17
17
  it('should cache and reuse connections', async () => {
18
18
  builder = await NetworkTopologies.twoNode();
19
+ console.log('Built the 2 node network, starting test');
19
20
  const leader = builder.getNode('o://leader');
20
21
  const child = builder.getNode('o://child');
21
22
  const spy = createConnectionSpy(leader);