@olane/o-node 0.7.51 → 0.7.52

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 (48) hide show
  1. package/dist/src/connection/index.d.ts +6 -5
  2. package/dist/src/connection/index.d.ts.map +1 -1
  3. package/dist/src/connection/index.js +6 -5
  4. package/dist/src/connection/interfaces/{o-node-connection-stream.config.d.ts → o-node-stream.config.d.ts} +2 -2
  5. package/dist/src/connection/interfaces/o-node-stream.config.d.ts.map +1 -0
  6. package/dist/src/connection/interfaces/stream-init-message.d.ts +29 -0
  7. package/dist/src/connection/interfaces/stream-init-message.d.ts.map +1 -0
  8. package/dist/src/connection/interfaces/stream-init-message.js +8 -0
  9. package/dist/src/connection/interfaces/stream-manager.config.d.ts +8 -0
  10. package/dist/src/connection/interfaces/stream-manager.config.d.ts.map +1 -0
  11. package/dist/src/connection/o-node-connection.d.ts +5 -7
  12. package/dist/src/connection/o-node-connection.d.ts.map +1 -1
  13. package/dist/src/connection/o-node-connection.js +26 -56
  14. package/dist/src/connection/o-node-connection.manager.d.ts +7 -0
  15. package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
  16. package/dist/src/connection/o-node-connection.manager.js +23 -5
  17. package/dist/src/connection/{o-node-connection-stream.d.ts → o-node-stream.d.ts} +6 -6
  18. package/dist/src/connection/o-node-stream.d.ts.map +1 -0
  19. package/dist/src/connection/{o-node-connection-stream.js → o-node-stream.js} +2 -2
  20. package/dist/src/connection/o-node-stream.manager.d.ts +181 -0
  21. package/dist/src/connection/o-node-stream.manager.d.ts.map +1 -0
  22. package/dist/src/connection/o-node-stream.manager.js +526 -0
  23. package/dist/src/connection/stream-manager.events.d.ts +83 -0
  24. package/dist/src/connection/stream-manager.events.d.ts.map +1 -0
  25. package/dist/src/connection/stream-manager.events.js +18 -0
  26. package/dist/src/o-node.tool.d.ts +0 -1
  27. package/dist/src/o-node.tool.d.ts.map +1 -1
  28. package/dist/src/o-node.tool.js +30 -20
  29. package/dist/test/helpers/stream-pool-test-helpers.d.ts +0 -75
  30. package/dist/test/helpers/stream-pool-test-helpers.d.ts.map +1 -1
  31. package/dist/test/helpers/stream-pool-test-helpers.js +262 -229
  32. package/dist/test/parent-child-registration.spec.js +1 -0
  33. package/package.json +7 -7
  34. package/dist/src/connection/interfaces/o-node-connection-stream.config.d.ts.map +0 -1
  35. package/dist/src/connection/interfaces/stream-pool-manager.config.d.ts +0 -41
  36. package/dist/src/connection/interfaces/stream-pool-manager.config.d.ts.map +0 -1
  37. package/dist/src/connection/o-node-connection-stream.d.ts.map +0 -1
  38. package/dist/src/connection/stream-handler.d.ts +0 -102
  39. package/dist/src/connection/stream-handler.d.ts.map +0 -1
  40. package/dist/src/connection/stream-handler.js +0 -357
  41. package/dist/src/connection/stream-pool-manager.d.ts +0 -86
  42. package/dist/src/connection/stream-pool-manager.d.ts.map +0 -1
  43. package/dist/src/connection/stream-pool-manager.events.d.ts +0 -57
  44. package/dist/src/connection/stream-pool-manager.events.d.ts.map +0 -1
  45. package/dist/src/connection/stream-pool-manager.events.js +0 -14
  46. package/dist/src/connection/stream-pool-manager.js +0 -356
  47. /package/dist/src/connection/interfaces/{o-node-connection-stream.config.js → o-node-stream.config.js} +0 -0
  48. /package/dist/src/connection/interfaces/{stream-pool-manager.config.js → stream-manager.config.js} +0 -0
@@ -1,102 +0,0 @@
1
- /// <reference types="node" />
2
- import type { Connection, Stream } from '@libp2p/interface';
3
- import { EventEmitter } from 'events';
4
- import { oRequest, oResponse, Logger } from '@olane/o-core';
5
- import type { oRouterRequest } from '@olane/o-core';
6
- import type { oConnection } from '@olane/o-core';
7
- import type { RunResult } from '@olane/o-tool';
8
- import type { StreamHandlerConfig } from './stream-handler.config.js';
9
- /**
10
- * StreamHandler centralizes all stream-related functionality including:
11
- * - Message type detection (request vs response)
12
- * - Stream lifecycle management (create, reuse, close)
13
- * - Backpressure handling
14
- * - Request/response handling
15
- * - Stream routing for middleware nodes
16
- */
17
- export declare class StreamHandler {
18
- private logger;
19
- constructor(logger?: Logger);
20
- /**
21
- * Detects if a decoded message is a request
22
- * Requests have a 'method' field and no 'result' field
23
- */
24
- isRequest(message: any): boolean;
25
- /**
26
- * Detects if a decoded message is a response
27
- * Responses have a 'result' field and no 'method' field
28
- */
29
- isResponse(message: any): boolean;
30
- /**
31
- * Extracts and parses JSON from various formats including:
32
- * - Already parsed objects
33
- * - Plain JSON
34
- * - Markdown code blocks (```json ... ``` or ``` ... ```)
35
- * - Mixed content with explanatory text
36
- * - JSON5 format (trailing commas, comments, unquoted keys, etc.)
37
- *
38
- * @param decoded - The decoded string that may contain JSON, or an already parsed object
39
- * @returns Parsed JSON object
40
- * @throws Error if JSON parsing fails even with JSON5 fallback
41
- */
42
- private extractAndParseJSON;
43
- /**
44
- * Builds a bidirectional cache key from caller and receiver addresses
45
- * The key is symmetric: A↔B === B↔A
46
- *
47
- * @param callerAddress - The caller's address
48
- * @param receiverAddress - The receiver's address
49
- * @returns Cache key string
50
- */
51
- private buildCacheKey;
52
- /**
53
- * Sends data through a stream using length-prefixed encoding (libp2p v3 best practice)
54
- * Each message is automatically prefixed with a varint indicating the message length
55
- * This ensures proper message boundaries and eliminates concatenation issues
56
- *
57
- * @param stream - The stream to send data through
58
- * @param data - The data to send
59
- * @param config - Configuration for timeout and other options
60
- */
61
- sendLengthPrefixed(stream: Stream, data: Uint8Array, config?: StreamHandlerConfig): Promise<void>;
62
- /**
63
- * Handles an incoming stream on the server side using length-prefixed protocol
64
- * Uses async read loops instead of event listeners (libp2p v3 best practice)
65
- * Processes complete messages with proper boundaries
66
- *
67
- * @param stream - The incoming stream
68
- * @param connection - The connection the stream belongs to
69
- * @param toolExecutor - Function to execute tools for requests
70
- */
71
- handleIncomingStream(stream: Stream, connection: Connection, toolExecutor: (request: oRequest, stream: Stream) => Promise<RunResult>): Promise<void>;
72
- /**
73
- * Handles a request message by executing the tool and sending response
74
- *
75
- * @param message - The decoded request message
76
- * @param stream - The stream to send the response on
77
- * @param toolExecutor - Function to execute the tool
78
- */
79
- private handleRequestMessage;
80
- /**
81
- * Handles an outgoing stream on the client side using length-prefixed protocol
82
- * Uses async read loops to process responses with proper message boundaries
83
- *
84
- * @param stream - The outgoing stream
85
- * @param emitter - Event emitter for chunk events
86
- * @param config - Configuration including abort signal
87
- * @param requestHandler - Optional handler for processing router requests received on this stream
88
- * @param requestId - Optional request ID to filter responses (for stream reuse scenarios)
89
- * @returns Promise that resolves with the final response
90
- */
91
- handleOutgoingStream(stream: Stream, emitter: EventEmitter, config?: StreamHandlerConfig, requestHandler?: (request: oRequest, stream: Stream) => Promise<RunResult>, requestId?: string | number): Promise<oResponse>;
92
- /**
93
- * Forwards a request to the next hop and relays response chunks back
94
- * This implements the middleware/proxy pattern for intermediate nodes
95
- *
96
- * @param request - The router request to forward
97
- * @param incomingStream - The stream to send responses back on
98
- * @param dialFn - Function to dial the next hop connection
99
- */
100
- forwardRequest(request: oRouterRequest, incomingStream: Stream, dialFn: (address: string) => Promise<oConnection>): Promise<void>;
101
- }
102
- //# sourceMappingURL=stream-handler.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"stream-handler.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-handler.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EACL,QAAQ,EACR,SAAS,EAIT,MAAM,EAEP,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EACV,mBAAmB,EAEpB,MAAM,4BAA4B,CAAC;AAKpC;;;;;;;GAOG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,CAAC,EAAE,MAAM;IAI3B;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAIhC;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAIjC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,mBAAmB;IAqC3B;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IA0HrB;;;;;;;;OAQG;IACG,kBAAkB,CACtB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,MAAM,GAAE,mBAAwB,GAC/B,OAAO,CAAC,IAAI,CAAC;IAKhB;;;;;;;;OAQG;IACG,oBAAoB,CACxB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,GACtE,OAAO,CAAC,IAAI,CAAC;IA+BhB;;;;;;OAMG;YACW,oBAAoB;IA6BlC;;;;;;;;;;OAUG;IACG,oBAAoB,CACxB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,YAAY,EACrB,MAAM,GAAE,mBAAwB,EAChC,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,EAC1E,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAC1B,OAAO,CAAC,SAAS,CAAC;IAiErB;;;;;;;OAOG;IACG,cAAc,CAClB,OAAO,EAAE,cAAc,EACvB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,GAChD,OAAO,CAAC,IAAI,CAAC;CAyBjB"}
@@ -1,357 +0,0 @@
1
- import { oRequest, oResponse, CoreUtils, oError, oErrorCodes, Logger, ResponseBuilder, } from '@olane/o-core';
2
- import { lpStream } from '@olane/o-config';
3
- import JSON5 from 'json5';
4
- /**
5
- * StreamHandler centralizes all stream-related functionality including:
6
- * - Message type detection (request vs response)
7
- * - Stream lifecycle management (create, reuse, close)
8
- * - Backpressure handling
9
- * - Request/response handling
10
- * - Stream routing for middleware nodes
11
- */
12
- export class StreamHandler {
13
- constructor(logger) {
14
- this.logger = logger ?? new Logger('StreamHandler');
15
- }
16
- /**
17
- * Detects if a decoded message is a request
18
- * Requests have a 'method' field and no 'result' field
19
- */
20
- isRequest(message) {
21
- return typeof message?.method === 'string' && message.result === undefined;
22
- }
23
- /**
24
- * Detects if a decoded message is a response
25
- * Responses have a 'result' field and no 'method' field
26
- */
27
- isResponse(message) {
28
- return message?.result !== undefined && message.method === undefined;
29
- }
30
- /**
31
- * Extracts and parses JSON from various formats including:
32
- * - Already parsed objects
33
- * - Plain JSON
34
- * - Markdown code blocks (```json ... ``` or ``` ... ```)
35
- * - Mixed content with explanatory text
36
- * - JSON5 format (trailing commas, comments, unquoted keys, etc.)
37
- *
38
- * @param decoded - The decoded string that may contain JSON, or an already parsed object
39
- * @returns Parsed JSON object
40
- * @throws Error if JSON parsing fails even with JSON5 fallback
41
- */
42
- extractAndParseJSON(decoded) {
43
- // If already an object (not a string), return it directly
44
- if (typeof decoded !== 'string') {
45
- return decoded;
46
- }
47
- let jsonString = decoded.trim();
48
- // Attempt standard JSON.parse first
49
- try {
50
- return JSON.parse(jsonString);
51
- }
52
- catch (jsonError) {
53
- this.logger.debug('Standard JSON parse failed, trying JSON5', {
54
- error: jsonError.message,
55
- position: jsonError.message.match(/position (\d+)/)?.[1],
56
- preview: jsonString,
57
- });
58
- // Fallback to JSON5 for more relaxed parsing
59
- try {
60
- return JSON5.parse(jsonString);
61
- }
62
- catch (json5Error) {
63
- // Enhanced error with context
64
- this.logger.error('JSON5 parse also failed', {
65
- originalError: jsonError.message,
66
- json5Error: json5Error.message,
67
- preview: jsonString.substring(0, 200),
68
- length: jsonString.length,
69
- });
70
- throw new Error(`Failed to parse JSON: ${jsonError.message}\nJSON5 also failed: ${json5Error.message}\nPreview: ${jsonString.substring(0, 200)}${jsonString.length > 200 ? '...' : ''}`);
71
- }
72
- }
73
- }
74
- /**
75
- * Builds a bidirectional cache key from caller and receiver addresses
76
- * The key is symmetric: A↔B === B↔A
77
- *
78
- * @param callerAddress - The caller's address
79
- * @param receiverAddress - The receiver's address
80
- * @returns Cache key string
81
- */
82
- buildCacheKey(callerAddress, receiverAddress) {
83
- const addresses = [callerAddress.value, receiverAddress.value].sort();
84
- return `${addresses[0]}↔${addresses[1]}`;
85
- }
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
- // }
190
- /**
191
- * Sends data through a stream using length-prefixed encoding (libp2p v3 best practice)
192
- * Each message is automatically prefixed with a varint indicating the message length
193
- * This ensures proper message boundaries and eliminates concatenation issues
194
- *
195
- * @param stream - The stream to send data through
196
- * @param data - The data to send
197
- * @param config - Configuration for timeout and other options
198
- */
199
- async sendLengthPrefixed(stream, data, config = {}) {
200
- const lp = lpStream(stream);
201
- await lp.write(data, { signal: config.signal });
202
- }
203
- /**
204
- * Handles an incoming stream on the server side using length-prefixed protocol
205
- * Uses async read loops instead of event listeners (libp2p v3 best practice)
206
- * Processes complete messages with proper boundaries
207
- *
208
- * @param stream - The incoming stream
209
- * @param connection - The connection the stream belongs to
210
- * @param toolExecutor - Function to execute tools for requests
211
- */
212
- async handleIncomingStream(stream, connection, toolExecutor) {
213
- const lp = lpStream(stream);
214
- try {
215
- while (stream.status === 'open') {
216
- // Read complete length-prefixed message
217
- const messageBytes = await lp.read();
218
- const decoded = new TextDecoder().decode(messageBytes.subarray());
219
- // Parse JSON (handles markdown blocks, mixed content, and JSON5)
220
- const message = this.extractAndParseJSON(decoded);
221
- if (this.isRequest(message)) {
222
- await this.handleRequestMessage(message, stream, toolExecutor);
223
- }
224
- else if (this.isResponse(message)) {
225
- this.logger.warn('Received response message on server-side stream, ignoring', message);
226
- }
227
- else {
228
- this.logger.warn('Received unknown message type', message);
229
- }
230
- }
231
- }
232
- catch (error) {
233
- // Stream closed or error occurred
234
- if (stream.status === 'open') {
235
- this.logger.error('Error in length-prefixed stream handler:', error);
236
- }
237
- }
238
- }
239
- /**
240
- * Handles a request message by executing the tool and sending response
241
- *
242
- * @param message - The decoded request message
243
- * @param stream - The stream to send the response on
244
- * @param toolExecutor - Function to execute the tool
245
- */
246
- async handleRequestMessage(message, stream, toolExecutor) {
247
- const request = new oRequest(message);
248
- const responseBuilder = ResponseBuilder.create();
249
- try {
250
- // this.logger.debug(
251
- // `Processing request on stream: method=${request.method}, id=${request.id}`,
252
- // );
253
- const result = await toolExecutor(request, stream);
254
- const response = await responseBuilder.build(request, result, null);
255
- await CoreUtils.sendResponse(response, stream);
256
- this.logger.debug(`Successfully processed request: method=${request.method}, id=${request.id}`);
257
- }
258
- catch (error) {
259
- this.logger.error(`Error processing request: method=${request.method}, id=${request.id}`, error);
260
- const errorResponse = await responseBuilder.buildError(request, error);
261
- await CoreUtils.sendResponse(errorResponse, stream);
262
- }
263
- }
264
- /**
265
- * Handles an outgoing stream on the client side using length-prefixed protocol
266
- * Uses async read loops to process responses with proper message boundaries
267
- *
268
- * @param stream - The outgoing stream
269
- * @param emitter - Event emitter for chunk events
270
- * @param config - Configuration including abort signal
271
- * @param requestHandler - Optional handler for processing router requests received on this stream
272
- * @param requestId - Optional request ID to filter responses (for stream reuse scenarios)
273
- * @returns Promise that resolves with the final response
274
- */
275
- async handleOutgoingStream(stream, emitter, config = {}, requestHandler, requestId) {
276
- const lp = lpStream(stream);
277
- try {
278
- while (stream.status === 'open') {
279
- this.logger.debug('Waiting for response...');
280
- // Read complete length-prefixed message
281
- const messageBytes = await lp.read({ signal: config.signal });
282
- const decoded = new TextDecoder().decode(messageBytes.subarray());
283
- // Parse JSON (handles markdown blocks, mixed content, and JSON5)
284
- const message = this.extractAndParseJSON(decoded);
285
- if (this.isResponse(message)) {
286
- const response = new oResponse({
287
- ...message.result,
288
- id: message.id,
289
- });
290
- // Filter by request ID if provided
291
- if (requestId !== undefined && response.id !== requestId) {
292
- this.logger.debug(`Ignoring response for different request (expected: ${requestId}, received: ${response.id})`);
293
- continue;
294
- }
295
- // Emit chunk for streaming responses
296
- emitter.emit('chunk', response);
297
- // Check if this is the last chunk
298
- if (response.result._last || !response.result._isStreaming) {
299
- return response;
300
- }
301
- }
302
- else if (this.isRequest(message)) {
303
- // Process incoming router requests if handler is provided
304
- if (requestHandler) {
305
- this.logger.debug('Received router request on client-side stream, processing...', message);
306
- await this.handleRequestMessage(message, stream, requestHandler);
307
- }
308
- else {
309
- this.logger.warn('Received request message on client-side stream, ignoring (no handler)', message);
310
- }
311
- }
312
- else {
313
- this.logger.warn('Received unknown message type', message);
314
- }
315
- }
316
- throw new oError(oErrorCodes.TIMEOUT, 'Stream closed before response received');
317
- }
318
- catch (error) {
319
- if (config.signal?.aborted) {
320
- throw new oError(oErrorCodes.TIMEOUT, 'Request aborted');
321
- }
322
- throw error;
323
- }
324
- }
325
- /**
326
- * Forwards a request to the next hop and relays response chunks back
327
- * This implements the middleware/proxy pattern for intermediate nodes
328
- *
329
- * @param request - The router request to forward
330
- * @param incomingStream - The stream to send responses back on
331
- * @param dialFn - Function to dial the next hop connection
332
- */
333
- async forwardRequest(request, incomingStream, dialFn) {
334
- try {
335
- // Connect to next hop
336
- const nextHopConnection = await dialFn(request.params.address);
337
- // Set up chunk relay - forward responses from next hop back to incoming stream
338
- nextHopConnection.onChunk(async (response) => {
339
- try {
340
- await CoreUtils.sendResponse(response, incomingStream);
341
- }
342
- catch (error) {
343
- this.logger.error('Error forwarding chunk:', error);
344
- }
345
- });
346
- // Transmit the request to next hop
347
- await nextHopConnection.transmit(request);
348
- }
349
- catch (error) {
350
- this.logger.error('Error forwarding request:', error);
351
- // Send error response back on incoming stream using ResponseBuilder
352
- const responseBuilder = ResponseBuilder.create();
353
- const errorResponse = await responseBuilder.buildError(request, error);
354
- await CoreUtils.sendResponse(errorResponse, incomingStream);
355
- }
356
- }
357
- }
@@ -1,86 +0,0 @@
1
- import { oObject } from '@olane/o-core';
2
- import { oNodeConnectionStream } from './o-node-connection-stream.js';
3
- import { StreamPoolEvent, StreamPoolEventData } from './stream-pool-manager.events.js';
4
- import type { StreamPoolManagerConfig, StreamPoolStats } from './interfaces/stream-pool-manager.config.js';
5
- /**
6
- * StreamPoolManager manages a pool of reusable streams with automatic recovery.
7
- *
8
- * Architecture:
9
- * - Stream[0]: Dedicated reader for incoming requests (background loop)
10
- * - Streams[1-N]: Round-robin pool for outgoing request-response cycles
11
- *
12
- * Features:
13
- * - Automatic stream pool initialization
14
- * - Dedicated reader with automatic restart on failure
15
- * - Event-based stream monitoring (listens to 'close' events)
16
- * - Automatic stream replacement when failures detected
17
- * - Event emission for monitoring and observability
18
- */
19
- export declare class StreamPoolManager extends oObject {
20
- private streams;
21
- private readonly POOL_SIZE;
22
- private readonly READER_STREAM_INDEX;
23
- private dedicatedReaderStream?;
24
- private currentStreamIndex;
25
- private failureCount;
26
- private eventEmitter;
27
- private streamEventHandlers;
28
- private config;
29
- private isInitialized;
30
- private isClosing;
31
- constructor(config: StreamPoolManagerConfig);
32
- get backgroundReaderActive(): boolean;
33
- /**
34
- * Initialize the stream pool and start the dedicated reader
35
- */
36
- initialize(): Promise<void>;
37
- /**
38
- * Get a stream for outgoing request-response cycles using round-robin selection
39
- */
40
- getStream(): Promise<oNodeConnectionStream>;
41
- /**
42
- * Get statistics about the stream pool
43
- */
44
- getStats(): StreamPoolStats;
45
- /**
46
- * Close the stream pool and cleanup resources
47
- */
48
- close(): Promise<void>;
49
- /**
50
- * Add event listener
51
- */
52
- on<K extends StreamPoolEvent>(event: K | string, listener: (data: StreamPoolEventData[K]) => void): void;
53
- /**
54
- * Remove event listener
55
- */
56
- off<K extends StreamPoolEvent>(event: K | string, listener: (data: StreamPoolEventData[K]) => void): void;
57
- /**
58
- * Emit event
59
- */
60
- private emit;
61
- /**
62
- * Initialize the background reader on the dedicated stream
63
- */
64
- private initializeBackgroundReader;
65
- /**
66
- * Handle dedicated reader failure with automatic recovery
67
- */
68
- private handleReaderFailure;
69
- /**
70
- * Replace a stream at the given index
71
- */
72
- private replaceStream;
73
- /**
74
- * Attach event listeners to a stream for monitoring
75
- */
76
- private attachStreamListeners;
77
- /**
78
- * Remove event listeners from a stream
79
- */
80
- private removeStreamListeners;
81
- /**
82
- * Handle stream close event - triggers immediate replacement
83
- */
84
- private handleStreamClose;
85
- }
86
- //# sourceMappingURL=stream-pool-manager.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"stream-pool-manager.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-pool-manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAiC,MAAM,eAAe,CAAC;AAEvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EACL,eAAe,EACf,mBAAmB,EACpB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EACV,uBAAuB,EACvB,eAAe,EAChB,MAAM,4CAA4C,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,qBAAa,iBAAkB,SAAQ,OAAO;IAC5C,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAE7C,OAAO,CAAC,qBAAqB,CAAC,CAAwB;IACtD,OAAO,CAAC,kBAAkB,CAAa;IAEvC,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,YAAY,CAAoC;IACxD,OAAO,CAAC,mBAAmB,CAAgD;IAE3E,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,SAAS,CAAkB;gBAEvB,MAAM,EAAE,uBAAuB;IAa3C,IAAI,sBAAsB,YAEzB;IAED;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAoDjC;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAqCjD;;OAEG;IACH,QAAQ,IAAI,eAAe;IAuB3B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA4B5B;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,eAAe,EAC1B,KAAK,EAAE,CAAC,GAAG,MAAM,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,IAAI,GAC/C,IAAI;IAIP;;OAEG;IACH,GAAG,CAAC,CAAC,SAAS,eAAe,EAC3B,KAAK,EAAE,CAAC,GAAG,MAAM,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,IAAI,GAC/C,IAAI;IAIP;;OAEG;IACH,OAAO,CAAC,IAAI;IAOZ;;OAEG;YACW,0BAA0B;IA0ExC;;OAEG;YACW,mBAAmB;IA6BjC;;OAEG;YACW,aAAa;IA0C3B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAuB7B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAW7B;;OAEG;IACH,OAAO,CAAC,iBAAiB;CA2C1B"}
@@ -1,57 +0,0 @@
1
- /**
2
- * Events emitted by StreamPoolManager
3
- */
4
- export declare enum StreamPoolEvent {
5
- PoolInitialized = "pool-initialized",
6
- PoolClosed = "pool-closed",
7
- ReaderStarted = "reader-started",
8
- ReaderFailed = "reader-failed",
9
- ReaderRecovered = "reader-recovered",
10
- RecoveryFailed = "recovery-failed",
11
- StreamReplaced = "stream-replaced",
12
- StreamFailed = "stream-failed"
13
- }
14
- /**
15
- * Event data interfaces
16
- */
17
- export interface PoolInitializedData {
18
- poolSize: number;
19
- }
20
- export interface ReaderStartedData {
21
- streamId: string;
22
- }
23
- export interface ReaderFailedData {
24
- error?: string;
25
- failureCount: number;
26
- }
27
- export interface ReaderRecoveredData {
28
- failureCount: number;
29
- }
30
- export interface RecoveryFailedData {
31
- error: string;
32
- failureCount: number;
33
- }
34
- export interface StreamReplacedData {
35
- index: number;
36
- streamType: string;
37
- }
38
- export interface StreamFailedData {
39
- index: number;
40
- streamId: string;
41
- error?: string;
42
- failureCount: number;
43
- }
44
- /**
45
- * Mapped type for type-safe event listeners
46
- */
47
- export type StreamPoolEventData = {
48
- [StreamPoolEvent.PoolInitialized]: PoolInitializedData;
49
- [StreamPoolEvent.PoolClosed]: void;
50
- [StreamPoolEvent.ReaderStarted]: ReaderStartedData;
51
- [StreamPoolEvent.ReaderFailed]: ReaderFailedData;
52
- [StreamPoolEvent.ReaderRecovered]: ReaderRecoveredData;
53
- [StreamPoolEvent.RecoveryFailed]: RecoveryFailedData;
54
- [StreamPoolEvent.StreamReplaced]: StreamReplacedData;
55
- [StreamPoolEvent.StreamFailed]: StreamFailedData;
56
- };
57
- //# sourceMappingURL=stream-pool-manager.events.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"stream-pool-manager.events.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-pool-manager.events.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,oBAAY,eAAe;IACzB,eAAe,qBAAqB;IACpC,UAAU,gBAAgB;IAC1B,aAAa,mBAAmB;IAChC,YAAY,kBAAkB;IAC9B,eAAe,qBAAqB;IACpC,cAAc,oBAAoB;IAClC,cAAc,oBAAoB;IAClC,YAAY,kBAAkB;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACvD,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC;IACnC,CAAC,eAAe,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IACnD,CAAC,eAAe,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC;IACjD,CAAC,eAAe,CAAC,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACvD,CAAC,eAAe,CAAC,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACrD,CAAC,eAAe,CAAC,cAAc,CAAC,EAAE,kBAAkB,CAAC;IACrD,CAAC,eAAe,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC;CAClD,CAAC"}
@@ -1,14 +0,0 @@
1
- /**
2
- * Events emitted by StreamPoolManager
3
- */
4
- export var StreamPoolEvent;
5
- (function (StreamPoolEvent) {
6
- StreamPoolEvent["PoolInitialized"] = "pool-initialized";
7
- StreamPoolEvent["PoolClosed"] = "pool-closed";
8
- StreamPoolEvent["ReaderStarted"] = "reader-started";
9
- StreamPoolEvent["ReaderFailed"] = "reader-failed";
10
- StreamPoolEvent["ReaderRecovered"] = "reader-recovered";
11
- StreamPoolEvent["RecoveryFailed"] = "recovery-failed";
12
- StreamPoolEvent["StreamReplaced"] = "stream-replaced";
13
- StreamPoolEvent["StreamFailed"] = "stream-failed";
14
- })(StreamPoolEvent || (StreamPoolEvent = {}));