@olane/o-node 0.7.55 → 0.7.57

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 (54) hide show
  1. package/dist/src/connection/enums/o-node-message-event.d.ts +14 -0
  2. package/dist/src/connection/enums/o-node-message-event.d.ts.map +1 -0
  3. package/dist/src/connection/enums/o-node-message-event.js +5 -0
  4. package/dist/src/connection/index.d.ts +0 -3
  5. package/dist/src/connection/index.d.ts.map +1 -1
  6. package/dist/src/connection/index.js +0 -3
  7. package/dist/src/connection/interfaces/abort-signal.config.d.ts +5 -0
  8. package/dist/src/connection/interfaces/abort-signal.config.d.ts.map +1 -0
  9. package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts +13 -1
  10. package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts.map +1 -1
  11. package/dist/src/connection/interfaces/o-node-connection.config.d.ts +18 -5
  12. package/dist/src/connection/interfaces/o-node-connection.config.d.ts.map +1 -1
  13. package/dist/src/connection/interfaces/o-node-stream.config.d.ts +3 -11
  14. package/dist/src/connection/interfaces/o-node-stream.config.d.ts.map +1 -1
  15. package/dist/src/connection/o-node-connection.d.ts +29 -53
  16. package/dist/src/connection/o-node-connection.d.ts.map +1 -1
  17. package/dist/src/connection/o-node-connection.js +102 -149
  18. package/dist/src/connection/o-node-connection.manager.d.ts +12 -8
  19. package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
  20. package/dist/src/connection/o-node-connection.manager.js +49 -35
  21. package/dist/src/connection/o-node-stream.d.ts +48 -15
  22. package/dist/src/connection/o-node-stream.d.ts.map +1 -1
  23. package/dist/src/connection/o-node-stream.js +143 -31
  24. package/dist/src/connection/stream-handler.config.d.ts +0 -11
  25. package/dist/src/connection/stream-handler.config.d.ts.map +1 -1
  26. package/dist/src/lib/interfaces/o-node-request-manager.config.d.ts +9 -0
  27. package/dist/src/lib/interfaces/o-node-request-manager.config.d.ts.map +1 -0
  28. package/dist/src/lib/interfaces/o-node-request-manager.config.js +1 -0
  29. package/dist/src/lib/o-node-request-manager.d.ts +46 -0
  30. package/dist/src/lib/o-node-request-manager.d.ts.map +1 -0
  31. package/dist/src/lib/o-node-request-manager.js +173 -0
  32. package/dist/src/managers/o-reconnection.manager.d.ts.map +1 -1
  33. package/dist/src/managers/o-reconnection.manager.js +4 -0
  34. package/dist/src/managers/o-registration.manager.d.ts.map +1 -1
  35. package/dist/src/managers/o-registration.manager.js +9 -4
  36. package/dist/src/o-node.d.ts +6 -7
  37. package/dist/src/o-node.d.ts.map +1 -1
  38. package/dist/src/o-node.js +22 -37
  39. package/dist/src/o-node.tool.d.ts +3 -3
  40. package/dist/src/o-node.tool.d.ts.map +1 -1
  41. package/dist/src/o-node.tool.js +28 -56
  42. package/dist/src/router/o-node.router.d.ts.map +1 -1
  43. package/dist/src/router/o-node.router.js +4 -2
  44. package/dist/test/connection-management.spec.js +3 -0
  45. package/package.json +7 -7
  46. package/dist/src/connection/interfaces/stream-init-message.d.ts +0 -64
  47. package/dist/src/connection/interfaces/stream-init-message.d.ts.map +0 -1
  48. package/dist/src/connection/interfaces/stream-init-message.js +0 -18
  49. package/dist/src/connection/interfaces/stream-manager.config.d.ts +0 -8
  50. package/dist/src/connection/interfaces/stream-manager.config.d.ts.map +0 -1
  51. package/dist/src/connection/o-node-stream.manager.d.ts +0 -210
  52. package/dist/src/connection/o-node-stream.manager.d.ts.map +0 -1
  53. package/dist/src/connection/o-node-stream.manager.js +0 -696
  54. /package/dist/src/connection/interfaces/{stream-manager.config.js → abort-signal.config.js} +0 -0
@@ -1,5 +1,7 @@
1
1
  import { oConnectionManager } from '@olane/o-core';
2
2
  import { oNodeConnection } from './o-node-connection.js';
3
+ import { EventEmitter } from 'events';
4
+ import { oNodeStream } from './o-node-stream.js';
3
5
  /**
4
6
  * Manages oNodeConnection instances, reusing connections when possible.
5
7
  */
@@ -10,10 +12,11 @@ export class oNodeConnectionManager extends oConnectionManager {
10
12
  /** Single cache of oNodeConnection instances keyed by address */
11
13
  this.cachedConnections = new Map();
12
14
  this.pendingDialsByAddress = new Map();
15
+ this.eventEmitter = new EventEmitter();
13
16
  this.p2pNode = config.p2pNode;
14
17
  this.defaultReadTimeoutMs = config.defaultReadTimeoutMs;
15
18
  this.defaultDrainTimeoutMs = config.defaultDrainTimeoutMs;
16
- this.logger.setNamespace(`oNodeConnectionManager[${config.originAddress}]`);
19
+ this.logger.setNamespace(`oNodeConnectionManager[${config.callerAddress}]`);
17
20
  // Set up connection lifecycle listeners for cache management
18
21
  this.setupConnectionListeners();
19
22
  }
@@ -54,7 +57,7 @@ export class oNodeConnectionManager extends oConnectionManager {
54
57
  * @param addressKey - The address key to cache under
55
58
  */
56
59
  cacheConnection(conn) {
57
- this.logger.debug('Caching connection for address:', conn.p2pConnection.id, conn.p2pConnection.direction, conn.nextHopAddress.value, conn.p2pConnection.streams.map((s) => s.protocol).join(', '));
60
+ this.logger.debug('Caching connection for address:', conn.p2pConnection.id, conn.p2pConnection.direction, conn.nextHopAddress.value, conn.p2pConnection.streams.map((s) => s.protocol).join(', '), conn.remotePeerId);
58
61
  this.cachedConnections.set(conn.p2pConnection.id, conn);
59
62
  }
60
63
  /**
@@ -92,66 +95,63 @@ export class oNodeConnectionManager extends oConnectionManager {
92
95
  });
93
96
  return connection;
94
97
  }
95
- async answer(config) {
96
- const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, p2pConnection, reuse, } = config;
98
+ async createConnection(config) {
99
+ return new oNodeConnection(config);
100
+ }
101
+ async answer(config, stream) {
102
+ const { targetAddress, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, p2pConnection, } = config;
103
+ if (!p2pConnection) {
104
+ throw new Error('Failed to answer connection, p2pConnection is undefined');
105
+ }
97
106
  this.logger.debug('Answering connection for address:', {
98
107
  address: nextHopAddress?.value,
99
108
  connectionId: p2pConnection.id,
100
109
  direction: p2pConnection.direction,
101
- reuse,
102
110
  });
103
111
  // Check if we already have a cached connection for this address with the same connection id
104
112
  const existingConnection = this.cachedConnections.get(p2pConnection.id);
105
113
  if (existingConnection) {
106
- this.logger.debug('Reusing cached connection for answer:', existingConnection.p2pConnection.id);
114
+ this.logger.debug('Reusing cached connection for answer:', existingConnection.id);
115
+ existingConnection.trackStream(new oNodeStream(stream, {
116
+ remoteAddress: nextHopAddress,
117
+ limited: this.config.runOnLimitedConnection,
118
+ }), config);
107
119
  return existingConnection;
108
120
  }
109
- const connection = new oNodeConnection({
121
+ const connection = await this.createConnection({
110
122
  nextHopAddress: nextHopAddress,
111
- address: address,
112
- p2pConnection: p2pConnection,
113
- p2pNode: this.p2pNode,
123
+ targetAddress: targetAddress,
114
124
  callerAddress: callerAddress,
115
125
  readTimeoutMs: readTimeoutMs ?? this.defaultReadTimeoutMs,
116
126
  drainTimeoutMs: drainTimeoutMs ?? this.defaultDrainTimeoutMs,
117
127
  isStream: config.isStream ?? false,
118
128
  abortSignal: config.abortSignal,
119
129
  runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
120
- reusePolicy: reuse ? 'reuse' : 'none',
130
+ p2pConnection: p2pConnection,
121
131
  });
132
+ connection.trackStream(new oNodeStream(stream, {
133
+ remoteAddress: nextHopAddress,
134
+ limited: this.config.runOnLimitedConnection,
135
+ }), config);
122
136
  // Cache the new connection
123
137
  this.cacheConnection(connection);
124
138
  return connection;
125
139
  }
126
- getConnectionFromAddress(address) {
127
- const protocol = address.protocol;
128
- this.logger.debug('Searching cached connections for protocol:', protocol);
129
- for (const conn of this.cachedConnections.values()) {
130
- // if nextHopAddress protocol matches, return conn
131
- if (conn.nextHopAddress.protocol === protocol) {
132
- this.logger.debug('local reuse cache found:', protocol);
133
- return conn;
134
- }
135
- // if remote protocols include protocol, return conn
136
- if (conn.remoteProtocols.includes(protocol)) {
137
- this.logger.debug('remote reuse cache found', protocol);
138
- return conn;
139
- }
140
- }
141
- return null;
142
- }
143
140
  /**
144
141
  * Connect to a given address, reusing oNodeConnection when possible
145
142
  * @param config - Connection configuration
146
143
  * @returns The connection object
147
144
  */
148
145
  async connect(config) {
149
- const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, } = config;
146
+ const { targetAddress, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, } = config;
150
147
  if (!nextHopAddress) {
151
148
  throw new Error('Invalid address passed');
152
149
  }
150
+ if (nextHopAddress.libp2pTransports?.length === 0) {
151
+ throw new Error('No transports provided for the address, cannot connect');
152
+ }
153
153
  // Check for existing valid cached connection
154
- const existingConnection = this.getConnectionFromAddress(nextHopAddress);
154
+ const existingConnection = this.getCachedConnectionFromAddress(nextHopAddress);
155
155
  if (existingConnection) {
156
156
  this.logger.debug('Reusing cached connection for address:', existingConnection.p2pConnection.id);
157
157
  return existingConnection;
@@ -159,15 +159,14 @@ export class oNodeConnectionManager extends oConnectionManager {
159
159
  else {
160
160
  this.logger.debug('No cached connection found for address:', {
161
161
  address: nextHopAddress.value,
162
+ peerId: nextHopAddress.libp2pTransports?.[0].toPeerId(),
162
163
  });
163
164
  }
164
165
  // Get or create the underlying p2p connection
165
166
  const p2pConnection = await this.getOrCreateP2pConnection(nextHopAddress, nextHopAddress.value);
166
- // Create new oNodeConnection
167
- const connection = new oNodeConnection({
167
+ const connection = await this.createConnection({
168
168
  nextHopAddress: nextHopAddress,
169
- address: address,
170
- p2pNode: this.p2pNode,
169
+ targetAddress: targetAddress,
171
170
  p2pConnection: p2pConnection,
172
171
  callerAddress: callerAddress,
173
172
  readTimeoutMs: readTimeoutMs ?? this.defaultReadTimeoutMs,
@@ -175,10 +174,25 @@ export class oNodeConnectionManager extends oConnectionManager {
175
174
  isStream: config.isStream ?? false,
176
175
  abortSignal: config.abortSignal,
177
176
  runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
178
- requestHandler: config.requestHandler,
179
177
  });
180
178
  // Cache the new connection
181
179
  this.cacheConnection(connection);
182
180
  return connection;
183
181
  }
182
+ getCachedConnectionFromAddress(address) {
183
+ const vals = Array.from(this.cachedConnections.values());
184
+ for (let i = 0; i < vals.length; ++i) {
185
+ const c = vals[i];
186
+ const connection = c;
187
+ const peerId = address.libp2pTransports?.[0].toPeerId();
188
+ if (connection.p2pConnection?.remotePeer.toString() === peerId &&
189
+ connection.isOpen) {
190
+ return connection;
191
+ }
192
+ }
193
+ return null;
194
+ }
195
+ get connectionCount() {
196
+ return this.cachedConnections.size;
197
+ }
184
198
  }
@@ -1,21 +1,53 @@
1
+ /// <reference types="node" />
1
2
  import type { Stream } from '@libp2p/interface';
2
- import { oObject } from '@olane/o-core';
3
+ import { oObject, oRequest, oResponse } from '@olane/o-core';
3
4
  import { oNodeStreamConfig } from './interfaces/o-node-stream.config.js';
5
+ import { LengthPrefixedStream } from '@olane/o-config';
6
+ import { AbortSignalConfig } from './interfaces/abort-signal.config.js';
7
+ import { EventEmitter } from 'events';
8
+ import { oNodeMessageEvent, oNodeMessageEventData } from './enums/o-node-message-event.js';
4
9
  /**
5
- * oNodeStream wraps a libp2p Stream with caller/receiver address metadata
6
- * to enable proper stream reuse based on address pairs rather than protocol only.
7
- *
8
- * Key features:
9
- * - Bidirectional cache keys: A↔B === B↔A
10
- * - Automatic reusability checking
11
- * - Idle time tracking for cleanup
10
+ * oNodeStream wraps a libp2p Stream and transmits or receives messages across the libp2p streams
12
11
  */
13
12
  export declare class oNodeStream extends oObject {
14
13
  readonly p2pStream: Stream;
15
14
  readonly config: oNodeStreamConfig;
16
15
  readonly createdAt: number;
16
+ protected readonly lp: LengthPrefixedStream;
17
+ protected readonly eventEmitter: EventEmitter;
17
18
  constructor(p2pStream: Stream, config: oNodeStreamConfig);
19
+ listenForLibp2pEvents(): void;
18
20
  validate(): void;
21
+ /**
22
+ * Extracts and parses JSON from various formats including:
23
+ * - Already parsed objects
24
+ * - Plain JSON
25
+ * - Markdown code blocks (```json ... ``` or ``` ... ```)
26
+ * - Mixed content with explanatory text
27
+ * - JSON5 format (trailing commas, comments, unquoted keys, etc.)
28
+ *
29
+ * @param decoded - The decoded string that may contain JSON, or an already parsed object
30
+ * @returns Parsed JSON object
31
+ * @throws Error if JSON parsing fails even with JSON5 fallback
32
+ */
33
+ protected extractAndParseJSON(decoded: string | any): any;
34
+ send(request: oRequest, options: AbortSignalConfig): Promise<void>;
35
+ /**
36
+ * listen - process every message inbound on the stream and emit it for the connection to bubble up
37
+ * @param options
38
+ */
39
+ listen(options: AbortSignalConfig): Promise<void>;
40
+ listenOnce(options: AbortSignalConfig): Promise<void>;
41
+ waitForResponse(requestId: string): Promise<oResponse>;
42
+ /**
43
+ * Detects if a decoded message is a request
44
+ * Requests have a 'method' field and no 'result' field
45
+ */
46
+ isRequest(message: any): boolean;
47
+ /**
48
+ * Detects if a decoded message is a response
49
+ */
50
+ isResponse(message: any): boolean;
19
51
  /**
20
52
  * Checks if the stream is in a valid state:
21
53
  * - Stream status is 'open'
@@ -29,18 +61,19 @@ export declare class oNodeStream extends oObject {
29
61
  * Gets the age of the stream in milliseconds
30
62
  */
31
63
  get age(): number;
64
+ close(): Promise<void>;
65
+ get id(): string;
32
66
  /**
33
- * Returns the stream type (defaults to 'general' for backward compatibility)
67
+ * Add event listener
34
68
  */
35
- get streamType(): string;
69
+ on<K extends oNodeMessageEvent>(event: K, listener: (data: oNodeMessageEventData[K]) => void): void;
36
70
  /**
37
- * Checks if this stream is designated as a dedicated reader
71
+ * Remove event listener
38
72
  */
39
- get isDedicatedReader(): boolean;
73
+ off<K extends oNodeMessageEvent>(event: K, listener: (data: oNodeMessageEventData[K]) => void): void;
40
74
  /**
41
- * Checks if this stream is designated for request-response cycles
75
+ * Emit event
42
76
  */
43
- get isRequestResponse(): boolean;
44
- close(): Promise<void>;
77
+ private emit;
45
78
  }
46
79
  //# sourceMappingURL=o-node-stream.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"o-node-stream.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAuB,OAAO,EAAiB,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAEzE;;;;;;;;GAQG;AACH,qBAAa,WAAY,SAAQ,OAAO;aAIpB,SAAS,EAAE,MAAM;aACjB,MAAM,EAAE,iBAAiB;IAJ3C,SAAgB,SAAS,EAAE,MAAM,CAAC;gBAGhB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,iBAAiB;IAO3C,QAAQ;IA6BR;;;;;;;OAOG;IACH,IAAI,OAAO,IAAI,OAAO,CAMrB;IAED;;OAEG;IACH,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED;;OAEG;IACH,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAED;;OAEG;IACH,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAiB7B"}
1
+ {"version":3,"file":"o-node-stream.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-stream.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAGL,OAAO,EACP,QAAQ,EACR,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAY,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAExE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AAGzC;;GAEG;AACH,qBAAa,WAAY,SAAQ,OAAO;aAMpB,SAAS,EAAE,MAAM;aACjB,MAAM,EAAE,iBAAiB;IAN3C,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,oBAAoB,CAAC;IAC5C,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAsB;gBAGjD,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,iBAAiB;IAQ3C,qBAAqB;IAOrB,QAAQ;IA6BR;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,GAAG,GAAG;IAqCnD,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB;IAUxD;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjD,UAAU,CAAC,OAAO,EAAE,iBAAiB;IAqBrC,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAe5D;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAQhC;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAIjC;;;;;;;OAOG;IACH,IAAI,OAAO,IAAI,OAAO,CAMrB;IAED;;OAEG;IACH,IAAI,GAAG,IAAI,MAAM,CAEhB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,IAAI,EAAE,IAAI,MAAM,CAEf;IAED;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,iBAAiB,EAC5B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,GACjD,IAAI;IAIP;;OAEG;IACH,GAAG,CAAC,CAAC,SAAS,iBAAiB,EAC7B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,GACjD,IAAI;IAIP;;OAEG;IACH,OAAO,CAAC,IAAI;CAMb"}
@@ -1,19 +1,26 @@
1
- import { oError, oErrorCodes, oObject } from '@olane/o-core';
1
+ import { oError, oErrorCodes, oObject, oResponse, } from '@olane/o-core';
2
+ import { lpStream } from '@olane/o-config';
3
+ import JSON5 from 'json5';
4
+ import { EventEmitter } from 'events';
5
+ import { oNodeMessageEvent, } from './enums/o-node-message-event.js';
6
+ import { oStreamRequest } from './o-stream.request.js';
2
7
  /**
3
- * oNodeStream wraps a libp2p Stream with caller/receiver address metadata
4
- * to enable proper stream reuse based on address pairs rather than protocol only.
5
- *
6
- * Key features:
7
- * - Bidirectional cache keys: A↔B === B↔A
8
- * - Automatic reusability checking
9
- * - Idle time tracking for cleanup
8
+ * oNodeStream wraps a libp2p Stream and transmits or receives messages across the libp2p streams
10
9
  */
11
10
  export class oNodeStream extends oObject {
12
11
  constructor(p2pStream, config) {
13
12
  super();
14
13
  this.p2pStream = p2pStream;
15
14
  this.config = config;
15
+ this.eventEmitter = new EventEmitter();
16
16
  this.createdAt = Date.now();
17
+ this.lp = lpStream(p2pStream);
18
+ this.listenForLibp2pEvents();
19
+ }
20
+ listenForLibp2pEvents() {
21
+ this.p2pStream.addEventListener('close', () => {
22
+ this.close();
23
+ });
17
24
  }
18
25
  // callable pattern to disrupt flow if not in valid state
19
26
  validate() {
@@ -30,6 +37,113 @@ export class oNodeStream extends oObject {
30
37
  throw new oError(oErrorCodes.INVALID_STATE, 'Session is not in a valid state');
31
38
  }
32
39
  }
40
+ /**
41
+ * Extracts and parses JSON from various formats including:
42
+ * - Already parsed objects
43
+ * - Plain JSON
44
+ * - Markdown code blocks (```json ... ``` or ``` ... ```)
45
+ * - Mixed content with explanatory text
46
+ * - JSON5 format (trailing commas, comments, unquoted keys, etc.)
47
+ *
48
+ * @param decoded - The decoded string that may contain JSON, or an already parsed object
49
+ * @returns Parsed JSON object
50
+ * @throws Error if JSON parsing fails even with JSON5 fallback
51
+ */
52
+ extractAndParseJSON(decoded) {
53
+ // If already an object (not a string), return it directly
54
+ if (typeof decoded !== 'string') {
55
+ return decoded;
56
+ }
57
+ let jsonString = decoded.trim();
58
+ // Attempt standard JSON.parse first
59
+ try {
60
+ return JSON.parse(jsonString);
61
+ }
62
+ catch (jsonError) {
63
+ this.logger.debug('Standard JSON parse failed, trying JSON5', {
64
+ error: jsonError.message,
65
+ position: jsonError.message.match(/position (\d+)/)?.[1],
66
+ preview: jsonString,
67
+ });
68
+ // Fallback to JSON5 for more relaxed parsing
69
+ try {
70
+ return JSON5.parse(jsonString);
71
+ }
72
+ catch (json5Error) {
73
+ // Enhanced error with context
74
+ this.logger.error('JSON5 parse also failed', {
75
+ originalError: jsonError.message,
76
+ json5Error: json5Error.message,
77
+ preview: jsonString.substring(0, 200),
78
+ length: jsonString.length,
79
+ });
80
+ throw new Error(`Failed to parse JSON: ${jsonError.message}\nJSON5 also failed: ${json5Error.message}\nPreview: ${jsonString.substring(0, 200)}${jsonString.length > 200 ? '...' : ''}`);
81
+ }
82
+ }
83
+ }
84
+ async send(request, options) {
85
+ // Ensure stream is valid
86
+ this.validate();
87
+ // Send the request with backpressure handling
88
+ const data = new TextEncoder().encode(request.toString());
89
+ await this.lp.write(data, { signal: options?.abortSignal });
90
+ }
91
+ /**
92
+ * listen - process every message inbound on the stream and emit it for the connection to bubble up
93
+ * @param options
94
+ */
95
+ async listen(options) {
96
+ while (this.isValid && !options?.abortSignal?.aborted) {
97
+ await this.listenOnce(options);
98
+ }
99
+ }
100
+ async listenOnce(options) {
101
+ const messageBytes = await this.lp.read({ signal: options?.abortSignal });
102
+ const decoded = new TextDecoder().decode(messageBytes.subarray());
103
+ const message = this.extractAndParseJSON(decoded);
104
+ if (this.isRequest(message)) {
105
+ // package up the request + stream and emit
106
+ const request = new oStreamRequest({
107
+ ...message,
108
+ stream: this.p2pStream,
109
+ });
110
+ this.emit(oNodeMessageEvent.request, request);
111
+ }
112
+ else if (this.isResponse(message)) {
113
+ const response = new oResponse({
114
+ ...message.result,
115
+ id: message.id,
116
+ });
117
+ this.emit(oNodeMessageEvent.response, response);
118
+ }
119
+ }
120
+ async waitForResponse(requestId) {
121
+ return new Promise((resolve, reject) => {
122
+ const handler = (data) => {
123
+ if (data.id === requestId) {
124
+ this.off(oNodeMessageEvent.response, handler);
125
+ this.logger.debug('Stream stopped listening for responses due to "waitForResponse", technically should continue if listen was called elsewhere');
126
+ resolve(data);
127
+ }
128
+ };
129
+ this.on(oNodeMessageEvent.response, handler);
130
+ });
131
+ }
132
+ /**
133
+ * Detects if a decoded message is a request
134
+ * Requests have a 'method' field and no 'result' field
135
+ */
136
+ isRequest(message) {
137
+ return (typeof message?.method === 'string' &&
138
+ message.result === undefined &&
139
+ message.error === undefined);
140
+ }
141
+ /**
142
+ * Detects if a decoded message is a response
143
+ */
144
+ isResponse(message) {
145
+ return message?.result !== undefined || message?.error !== undefined;
146
+ }
33
147
  /**
34
148
  * Checks if the stream is in a valid state:
35
149
  * - Stream status is 'open'
@@ -49,30 +163,7 @@ export class oNodeStream extends oObject {
49
163
  get age() {
50
164
  return Date.now() - this.createdAt;
51
165
  }
52
- /**
53
- * Returns the stream type (defaults to 'general' for backward compatibility)
54
- */
55
- get streamType() {
56
- return this.config.streamType || 'general';
57
- }
58
- /**
59
- * Checks if this stream is designated as a dedicated reader
60
- */
61
- get isDedicatedReader() {
62
- return this.streamType === 'dedicated-reader';
63
- }
64
- /**
65
- * Checks if this stream is designated for request-response cycles
66
- */
67
- get isRequestResponse() {
68
- return this.streamType === 'request-response';
69
- }
70
166
  async close() {
71
- // Don't close if reuse policy is enabled
72
- if (this.config.reusePolicy === 'reuse') {
73
- this.logger.debug('Stream reuse enabled, not closing stream');
74
- return;
75
- }
76
167
  if (this.p2pStream.status === 'open') {
77
168
  try {
78
169
  // force the close for now until we can implement a proper close
@@ -84,4 +175,25 @@ export class oNodeStream extends oObject {
84
175
  }
85
176
  }
86
177
  }
178
+ get id() {
179
+ return this.p2pStream?.id;
180
+ }
181
+ /**
182
+ * Add event listener
183
+ */
184
+ on(event, listener) {
185
+ this.eventEmitter.on(event, listener);
186
+ }
187
+ /**
188
+ * Remove event listener
189
+ */
190
+ off(event, listener) {
191
+ this.eventEmitter.off(event, listener);
192
+ }
193
+ /**
194
+ * Emit event
195
+ */
196
+ emit(event, data) {
197
+ this.eventEmitter.emit(event, data);
198
+ }
87
199
  }
@@ -1,20 +1,9 @@
1
1
  /// <reference types="node" />
2
2
  import type { Stream } from '@libp2p/interface';
3
- /**
4
- * Stream reuse policy determines how streams are managed across multiple requests
5
- */
6
- export type StreamReusePolicy = 'none' | 'reuse' | 'pool';
7
3
  /**
8
4
  * Configuration for StreamHandler behavior
9
5
  */
10
6
  export interface StreamHandlerConfig {
11
- /**
12
- * Stream reuse policy:
13
- * - 'none': Create new stream for each request (default)
14
- * - 'reuse': Reuse existing open streams
15
- * - 'pool': (Future) Maintain a pool of streams
16
- */
17
- reusePolicy?: StreamReusePolicy;
18
7
  /**
19
8
  * Timeout in milliseconds to wait for stream drain when buffer is full
20
9
  * @default 30000 (30 seconds)
@@ -1 +1 @@
1
- {"version":3,"file":"stream-handler.config.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-handler.config.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAEhC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IAErB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,mBAAmB,CAAC;CAC7B"}
1
+ {"version":3,"file":"stream-handler.config.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-handler.config.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IAErB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,mBAAmB,CAAC;CAC7B"}
@@ -0,0 +1,9 @@
1
+ import { oNodeAddress } from '../../router/o-node.address.js';
2
+ import { oNodeConnectionManager } from '../../connection/o-node-connection.manager.js';
3
+ import { oNodeRouter } from '../../router/o-node.router.js';
4
+ export interface oNodeRequestManagerConfig {
5
+ callerAddress: oNodeAddress;
6
+ connectionManager: oNodeConnectionManager;
7
+ router: oNodeRouter;
8
+ }
9
+ //# sourceMappingURL=o-node-request-manager.config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"o-node-request-manager.config.d.ts","sourceRoot":"","sources":["../../../../src/lib/interfaces/o-node-request-manager.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAE5D,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,YAAY,CAAC;IAC5B,iBAAiB,EAAE,sBAAsB,CAAC;IAC1C,MAAM,EAAE,WAAW,CAAC;CACrB"}
@@ -0,0 +1,46 @@
1
+ /// <reference types="node" />
2
+ import { oRequestManager, oResponse, UseOptions } from '@olane/o-core';
3
+ import { oNodeRequestManagerConfig } from './interfaces/o-node-request-manager.config.js';
4
+ import { oNodeConnection, oNodeConnectionConfig, oNodeConnectionManager } from '../connection/index.js';
5
+ import { oNodeRouter } from '../router/o-node.router.js';
6
+ import { oNodeAddress } from '../router/o-node.address.js';
7
+ import { UseDataConfig } from '@olane/o-core/dist/src/core/interfaces/use-data.config.js';
8
+ import { oNode } from '../o-node.js';
9
+ import { Connection, Stream } from '@olane/o-config';
10
+ import { oNodeMessageEvent, oNodeMessageEventData } from '../connection/enums/o-node-message-event.js';
11
+ import { oStreamRequest } from '../connection/o-stream.request.js';
12
+ import { EventEmitter } from 'events';
13
+ import { AbortSignalConfig } from '../connection/interfaces/abort-signal.config.js';
14
+ import { RunResult } from '@olane/o-tool';
15
+ export declare class oNodeRequestManager extends oRequestManager {
16
+ readonly config: oNodeRequestManagerConfig;
17
+ connectionManager: oNodeConnectionManager;
18
+ router: oNodeRouter;
19
+ protected eventEmitter: EventEmitter;
20
+ constructor(config: oNodeRequestManagerConfig);
21
+ translateAddress(address: oNodeAddress, options?: UseOptions, nodeRef?: oNode): Promise<{
22
+ nextHopAddress: oNodeAddress;
23
+ targetAddress: oNodeAddress;
24
+ }>;
25
+ connectToNode(nextHopAddress: oNodeAddress, options: Omit<oNodeConnectionConfig, 'nextHopAddress'>): Promise<oNodeConnection>;
26
+ send(address: oNodeAddress, data: UseDataConfig, options?: UseOptions, nodeRef?: oNode): Promise<oResponse>;
27
+ receiveStream({ connection, stream, }: {
28
+ connection: Connection;
29
+ stream: Stream;
30
+ }): Promise<void>;
31
+ sendResponse(request: oStreamRequest, result: RunResult): Promise<void>;
32
+ protected listenForMessages(connection: oNodeConnection, options: AbortSignalConfig): Promise<void>;
33
+ /**
34
+ * Add event listener
35
+ */
36
+ on<K extends oNodeMessageEvent>(event: K | string, listener: (data: oNodeMessageEventData[K]) => void): void;
37
+ /**
38
+ * Remove event listener
39
+ */
40
+ off<K extends oNodeMessageEvent>(event: K | string, listener: (data: oNodeMessageEventData[K]) => void): void;
41
+ /**
42
+ * Emit event
43
+ */
44
+ private emitAsync;
45
+ }
46
+ //# sourceMappingURL=o-node-request-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"o-node-request-manager.d.ts","sourceRoot":"","sources":["../../../src/lib/o-node-request-manager.ts"],"names":[],"mappings":";AAAA,OAAO,EAML,eAAe,EACf,SAAS,EAET,UAAU,EACX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,yBAAyB,EAAE,MAAM,+CAA+C,CAAC;AAC1F,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EAEvB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,2DAA2D,CAAC;AAC1F,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACtB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AACpF,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAG1C,qBAAa,mBAAoB,SAAQ,eAAe;IAK1C,QAAQ,CAAC,MAAM,EAAE,yBAAyB;IAJtD,iBAAiB,EAAE,sBAAsB,CAAC;IAC1C,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,CAAC,YAAY,EAAE,YAAY,CAAsB;gBAErC,MAAM,EAAE,yBAAyB;IAMhD,gBAAgB,CACpB,OAAO,EAAE,YAAY,EACrB,OAAO,CAAC,EAAE,UAAU,EACpB,OAAO,CAAC,EAAE,KAAK,GACd,OAAO,CAAC;QAAE,cAAc,EAAE,YAAY,CAAC;QAAC,aAAa,EAAE,YAAY,CAAA;KAAE,CAAC;IAenE,aAAa,CACjB,cAAc,EAAE,YAAY,EAC5B,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,gBAAgB,CAAC,GACrD,OAAO,CAAC,eAAe,CAAC;IAyBrB,IAAI,CACR,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,aAAa,EACnB,OAAO,CAAC,EAAE,UAAU,EACpB,OAAO,CAAC,EAAE,KAAK,GACd,OAAO,CAAC,SAAS,CAAC;IAgEf,aAAa,CAAC,EAClB,UAAU,EACV,MAAM,GACP,EAAE;QACD,UAAU,EAAE,UAAU,CAAC;QACvB,MAAM,EAAE,MAAM,CAAC;KAChB;IAsBK,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS;cAsB7C,iBAAiB,CAC/B,UAAU,EAAE,eAAe,EAC3B,OAAO,EAAE,iBAAiB;IAyB5B;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,iBAAiB,EAC5B,KAAK,EAAE,CAAC,GAAG,MAAM,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,GACjD,IAAI;IAIP;;OAEG;IACH,GAAG,CAAC,CAAC,SAAS,iBAAiB,EAC7B,KAAK,EAAE,CAAC,GAAG,MAAM,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,GACjD,IAAI;IAIP;;OAEG;YAQW,SAAS;CAcxB"}