@olane/o-node 0.7.49 → 0.7.51

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 (32) hide show
  1. package/dist/src/connection/index.d.ts +4 -0
  2. package/dist/src/connection/index.d.ts.map +1 -1
  3. package/dist/src/connection/index.js +4 -0
  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 -1
  6. package/dist/src/connection/interfaces/stream-pool-manager.config.d.ts +41 -0
  7. package/dist/src/connection/interfaces/stream-pool-manager.config.d.ts.map +1 -0
  8. package/dist/src/connection/interfaces/stream-pool-manager.config.js +1 -0
  9. package/dist/src/connection/o-node-connection-stream.d.ts +12 -0
  10. package/dist/src/connection/o-node-connection-stream.d.ts.map +1 -1
  11. package/dist/src/connection/o-node-connection-stream.js +18 -0
  12. package/dist/src/connection/stream-pool-manager.d.ts +86 -0
  13. package/dist/src/connection/stream-pool-manager.d.ts.map +1 -0
  14. package/dist/src/connection/stream-pool-manager.events.d.ts +57 -0
  15. package/dist/src/connection/stream-pool-manager.events.d.ts.map +1 -0
  16. package/dist/src/connection/stream-pool-manager.events.js +14 -0
  17. package/dist/src/connection/stream-pool-manager.js +356 -0
  18. package/dist/src/interfaces/i-registrable-node.d.ts +7 -0
  19. package/dist/src/interfaces/i-registrable-node.d.ts.map +1 -1
  20. package/dist/src/managers/o-registration.manager.d.ts.map +1 -1
  21. package/dist/src/managers/o-registration.manager.js +11 -1
  22. package/dist/src/o-node.tool.d.ts.map +1 -1
  23. package/dist/src/o-node.tool.js +16 -13
  24. package/dist/test/connection-management.spec.js +24 -24
  25. package/dist/test/helpers/stream-pool-test-helpers.d.ts +76 -0
  26. package/dist/test/helpers/stream-pool-test-helpers.d.ts.map +1 -0
  27. package/dist/test/helpers/stream-pool-test-helpers.js +229 -0
  28. package/dist/test/network-communication.spec.js +68 -66
  29. package/dist/test/stream-pool-manager.spec.d.ts +1 -0
  30. package/dist/test/stream-pool-manager.spec.d.ts.map +1 -0
  31. package/dist/test/stream-pool-manager.spec.js +424 -0
  32. package/package.json +7 -7
@@ -0,0 +1,356 @@
1
+ import { EventEmitter } from 'events';
2
+ import { oObject, oError, oErrorCodes } from '@olane/o-core';
3
+ import { StreamPoolEvent, } from './stream-pool-manager.events.js';
4
+ /**
5
+ * StreamPoolManager manages a pool of reusable streams with automatic recovery.
6
+ *
7
+ * Architecture:
8
+ * - Stream[0]: Dedicated reader for incoming requests (background loop)
9
+ * - Streams[1-N]: Round-robin pool for outgoing request-response cycles
10
+ *
11
+ * Features:
12
+ * - Automatic stream pool initialization
13
+ * - Dedicated reader with automatic restart on failure
14
+ * - Event-based stream monitoring (listens to 'close' events)
15
+ * - Automatic stream replacement when failures detected
16
+ * - Event emission for monitoring and observability
17
+ */
18
+ export class StreamPoolManager extends oObject {
19
+ constructor(config) {
20
+ super();
21
+ this.streams = [];
22
+ this.currentStreamIndex = 1; // Start from 1 (skip reader)
23
+ this.failureCount = 0;
24
+ this.eventEmitter = new EventEmitter();
25
+ this.streamEventHandlers = new Map();
26
+ this.isInitialized = false;
27
+ this.isClosing = false;
28
+ this.config = config;
29
+ this.POOL_SIZE = config.poolSize || 10;
30
+ this.READER_STREAM_INDEX = config.readerStreamIndex ?? 0;
31
+ if (this.POOL_SIZE < 2) {
32
+ throw new Error('Pool size must be at least 2 (1 reader + 1 request-response)');
33
+ }
34
+ }
35
+ get backgroundReaderActive() {
36
+ return this.dedicatedReaderStream?.isValid || false;
37
+ }
38
+ /**
39
+ * Initialize the stream pool and start the dedicated reader
40
+ */
41
+ async initialize() {
42
+ if (this.isInitialized) {
43
+ this.logger.debug('Stream pool already initialized');
44
+ return;
45
+ }
46
+ this.logger.info('Initializing stream pool', {
47
+ poolSize: this.POOL_SIZE,
48
+ readerIndex: this.READER_STREAM_INDEX,
49
+ });
50
+ try {
51
+ // Spawn all streams in parallel
52
+ const streamPromises = [];
53
+ for (let i = 0; i < this.POOL_SIZE; i++) {
54
+ streamPromises.push(this.config.createStream());
55
+ }
56
+ this.streams = await Promise.all(streamPromises);
57
+ // Set stream types and attach event listeners
58
+ for (let i = 0; i < this.streams.length; i++) {
59
+ const stream = this.streams[i];
60
+ if (i === this.READER_STREAM_INDEX) {
61
+ stream.config.streamType = 'dedicated-reader';
62
+ }
63
+ else {
64
+ stream.config.streamType = 'request-response';
65
+ }
66
+ // Attach event listeners for monitoring
67
+ this.attachStreamListeners(stream, i);
68
+ }
69
+ // Initialize dedicated reader
70
+ await this.initializeBackgroundReader();
71
+ this.isInitialized = true;
72
+ this.emit(StreamPoolEvent.PoolInitialized, {
73
+ poolSize: this.streams.length,
74
+ });
75
+ this.logger.info('Stream pool initialized successfully', {
76
+ totalStreams: this.streams.length,
77
+ });
78
+ }
79
+ catch (error) {
80
+ this.logger.error('Failed to initialize stream pool:', error);
81
+ throw new oError(oErrorCodes.INTERNAL_ERROR, `Failed to initialize stream pool: ${error.message}`);
82
+ }
83
+ }
84
+ /**
85
+ * Get a stream for outgoing request-response cycles using round-robin selection
86
+ */
87
+ async getStream() {
88
+ if (!this.isInitialized) {
89
+ throw new oError(oErrorCodes.INVALID_STATE, 'Stream pool not initialized. Call initialize() first.');
90
+ }
91
+ if (this.streams.length < this.POOL_SIZE) {
92
+ this.logger.warn('Stream pool not at full capacity', {
93
+ current: this.streams.length,
94
+ expected: this.POOL_SIZE,
95
+ });
96
+ }
97
+ // Round-robin selection from request-response pool (skip reader stream)
98
+ const requestResponsePoolSize = this.POOL_SIZE - 1;
99
+ const selectedIndex = ((this.currentStreamIndex - 1) % requestResponsePoolSize) + 1;
100
+ this.currentStreamIndex =
101
+ selectedIndex === this.POOL_SIZE - 1 ? 1 : selectedIndex + 1;
102
+ const selectedStream = this.streams[selectedIndex];
103
+ // Validate stream health before returning
104
+ if (!selectedStream.isValid) {
105
+ this.logger.warn('Selected stream is not valid, attempting recovery', {
106
+ index: selectedIndex,
107
+ });
108
+ await this.replaceStream(selectedIndex);
109
+ return this.streams[selectedIndex];
110
+ }
111
+ return selectedStream;
112
+ }
113
+ /**
114
+ * Get statistics about the stream pool
115
+ */
116
+ getStats() {
117
+ const healthyStreams = this.streams.filter((s) => s.isValid).length;
118
+ const requestResponseStreams = this.streams.filter((s) => s.isRequestResponse).length;
119
+ let readerStreamHealth = 'not-initialized';
120
+ if (this.dedicatedReaderStream) {
121
+ readerStreamHealth = this.dedicatedReaderStream.isValid
122
+ ? 'healthy'
123
+ : 'unhealthy';
124
+ }
125
+ return {
126
+ totalStreams: this.streams.length,
127
+ healthyStreams,
128
+ readerStreamHealth,
129
+ requestResponseStreams,
130
+ failureCount: this.failureCount,
131
+ };
132
+ }
133
+ /**
134
+ * Close the stream pool and cleanup resources
135
+ */
136
+ async close() {
137
+ if (this.isClosing) {
138
+ return;
139
+ }
140
+ this.isClosing = true;
141
+ this.logger.info('Closing stream pool', {
142
+ totalStreams: this.streams.length,
143
+ });
144
+ // Remove all event listeners from streams
145
+ for (const stream of this.streams) {
146
+ this.removeStreamListeners(stream);
147
+ }
148
+ // Close all streams (though they may not actually close due to reuse policy)
149
+ await Promise.all(this.streams.map((s) => s.close()));
150
+ this.streams = [];
151
+ this.dedicatedReaderStream = undefined;
152
+ this.streamEventHandlers.clear();
153
+ this.isInitialized = false;
154
+ this.isClosing = false;
155
+ this.emit(StreamPoolEvent.PoolClosed);
156
+ this.logger.info('Stream pool closed');
157
+ }
158
+ /**
159
+ * Add event listener
160
+ */
161
+ on(event, listener) {
162
+ this.eventEmitter.on(event, listener);
163
+ }
164
+ /**
165
+ * Remove event listener
166
+ */
167
+ off(event, listener) {
168
+ this.eventEmitter.off(event, listener);
169
+ }
170
+ /**
171
+ * Emit event
172
+ */
173
+ emit(event, data) {
174
+ this.eventEmitter.emit(event, data);
175
+ }
176
+ /**
177
+ * Initialize the background reader on the dedicated stream
178
+ */
179
+ async initializeBackgroundReader() {
180
+ if (this.backgroundReaderActive) {
181
+ this.logger.debug('Background reader already active');
182
+ return;
183
+ }
184
+ const readerStream = this.streams[this.READER_STREAM_INDEX];
185
+ if (!readerStream) {
186
+ throw new oError(oErrorCodes.INTERNAL_ERROR, 'Reader stream not found in pool');
187
+ }
188
+ if (!readerStream.isValid) {
189
+ this.logger.warn('Reader stream is not valid, replacing it');
190
+ await this.replaceStream(this.READER_STREAM_INDEX);
191
+ return this.initializeBackgroundReader();
192
+ }
193
+ this.dedicatedReaderStream = readerStream;
194
+ this.logger.info('Starting background reader', {
195
+ streamId: readerStream.p2pStream.id,
196
+ });
197
+ // Get the raw p2p stream
198
+ const p2pStream = readerStream.p2pStream;
199
+ // Start the persistent read loop in the background
200
+ this.config.streamHandler
201
+ .handleIncomingStream(p2pStream, this.config.p2pConnection, async (request, s) => {
202
+ this.logger.debug('Background reader received request:', request.method);
203
+ if (this.config.requestHandler) {
204
+ return await this.config.requestHandler(request, s);
205
+ }
206
+ throw new Error('No request handler configured for incoming requests');
207
+ })
208
+ .catch((error) => {
209
+ this.logger.warn('Background reader exited:', error?.message || 'stream closed');
210
+ this.failureCount++;
211
+ this.emit(StreamPoolEvent.ReaderFailed, {
212
+ error: error?.message,
213
+ failureCount: this.failureCount,
214
+ });
215
+ // Attempt automatic recovery
216
+ this.handleReaderFailure().catch((recoveryError) => {
217
+ this.logger.error('Failed to recover from reader failure:', recoveryError);
218
+ });
219
+ });
220
+ this.emit(StreamPoolEvent.ReaderStarted, {
221
+ streamId: readerStream.p2pStream.id,
222
+ });
223
+ }
224
+ /**
225
+ * Handle dedicated reader failure with automatic recovery
226
+ */
227
+ async handleReaderFailure() {
228
+ if (this.isClosing) {
229
+ this.logger.debug('Pool is closing, skipping reader recovery');
230
+ return;
231
+ }
232
+ this.logger.warn('Attempting to recover from reader failure');
233
+ try {
234
+ // Replace the failed reader stream
235
+ await this.replaceStream(this.READER_STREAM_INDEX);
236
+ // Reinitialize the background reader
237
+ await this.initializeBackgroundReader();
238
+ this.logger.info('Successfully recovered from reader failure');
239
+ this.emit(StreamPoolEvent.ReaderRecovered, {
240
+ failureCount: this.failureCount,
241
+ });
242
+ }
243
+ catch (error) {
244
+ this.logger.error('Failed to recover from reader failure:', error);
245
+ this.emit(StreamPoolEvent.RecoveryFailed, {
246
+ error: error.message,
247
+ failureCount: this.failureCount,
248
+ });
249
+ throw error;
250
+ }
251
+ }
252
+ /**
253
+ * Replace a stream at the given index
254
+ */
255
+ async replaceStream(index) {
256
+ this.logger.info('Replacing stream', { index });
257
+ try {
258
+ const oldStream = this.streams[index];
259
+ const streamType = oldStream?.streamType || 'general';
260
+ // Remove event listeners from old stream
261
+ if (oldStream) {
262
+ this.removeStreamListeners(oldStream);
263
+ }
264
+ // Create new stream
265
+ const newStream = await this.config.createStream();
266
+ newStream.config.streamType = streamType;
267
+ // Replace in pool
268
+ this.streams[index] = newStream;
269
+ // Attach event listeners to new stream
270
+ this.attachStreamListeners(newStream, index);
271
+ // Close old stream (if it exists and is valid)
272
+ if (oldStream) {
273
+ try {
274
+ await oldStream.close();
275
+ }
276
+ catch (error) {
277
+ this.logger.debug('Error closing old stream:', error.message);
278
+ }
279
+ }
280
+ this.emit(StreamPoolEvent.StreamReplaced, { index, streamType });
281
+ this.logger.info('Stream replaced successfully', { index, streamType });
282
+ }
283
+ catch (error) {
284
+ this.logger.error('Failed to replace stream:', error);
285
+ throw new oError(oErrorCodes.INTERNAL_ERROR, `Failed to replace stream at index ${index}: ${error.message}`);
286
+ }
287
+ }
288
+ /**
289
+ * Attach event listeners to a stream for monitoring
290
+ */
291
+ attachStreamListeners(stream, index) {
292
+ const streamId = stream.p2pStream.id;
293
+ // Remove any existing listeners for this stream
294
+ this.removeStreamListeners(stream);
295
+ // Create close event handler
296
+ const closeHandler = (event) => {
297
+ this.handleStreamClose(index, event);
298
+ };
299
+ // Attach listener to underlying p2p stream
300
+ stream.p2pStream.addEventListener('close', closeHandler);
301
+ // Store handler for cleanup
302
+ this.streamEventHandlers.set(streamId, closeHandler);
303
+ this.logger.debug('Attached stream listeners', { index, streamId });
304
+ }
305
+ /**
306
+ * Remove event listeners from a stream
307
+ */
308
+ removeStreamListeners(stream) {
309
+ const streamId = stream.p2pStream.id;
310
+ const handler = this.streamEventHandlers.get(streamId);
311
+ if (handler) {
312
+ stream.p2pStream.removeEventListener('close', handler);
313
+ this.streamEventHandlers.delete(streamId);
314
+ this.logger.debug('Removed stream listeners', { streamId });
315
+ }
316
+ }
317
+ /**
318
+ * Handle stream close event - triggers immediate replacement
319
+ */
320
+ handleStreamClose(index, event) {
321
+ const stream = this.streams[index];
322
+ const streamId = stream?.p2pStream.id;
323
+ const isReaderStream = index === this.READER_STREAM_INDEX;
324
+ this.logger.warn('Stream closed event detected', {
325
+ index,
326
+ streamId,
327
+ isReaderStream,
328
+ error: event?.error?.message,
329
+ });
330
+ this.failureCount++;
331
+ // Special handling for dedicated reader stream
332
+ if (isReaderStream) {
333
+ this.emit(StreamPoolEvent.ReaderFailed, {
334
+ error: event?.error?.message || 'stream closed',
335
+ failureCount: this.failureCount,
336
+ });
337
+ // Attempt automatic recovery
338
+ this.handleReaderFailure().catch((recoveryError) => {
339
+ this.logger.error('Failed to recover from reader failure:', recoveryError);
340
+ });
341
+ }
342
+ else {
343
+ // Request-response stream failure
344
+ this.emit(StreamPoolEvent.StreamFailed, {
345
+ index,
346
+ streamId,
347
+ error: event?.error?.message,
348
+ failureCount: this.failureCount,
349
+ });
350
+ // Attempt automatic replacement
351
+ this.replaceStream(index).catch((replaceError) => {
352
+ this.logger.error('Failed to replace stream:', replaceError);
353
+ });
354
+ }
355
+ }
356
+ }
@@ -4,6 +4,9 @@ import { oNodeAddress } from '../router/o-node.address.js';
4
4
  import { oNodeConfig } from './o-node.config.js';
5
5
  import { oNodeTransport } from '../router/o-node.transport.js';
6
6
  import { oNodeHierarchyManager } from '../o-node.hierarchy-manager.js';
7
+ export interface IReconnectionManager {
8
+ waitForParentAndReconnect(): Promise<void>;
9
+ }
7
10
  /**
8
11
  * Interface for nodes that support registration management.
9
12
  * This interface defines the contract that oRegistrationManager needs
@@ -50,6 +53,10 @@ export interface IRegistrableNode {
50
53
  * Hierarchy manager for tracking parent/child/leader relationships
51
54
  */
52
55
  hierarchyManager: oNodeHierarchyManager;
56
+ /**
57
+ * Reconnection manager for handling parent discovery
58
+ */
59
+ reconnectionManager?: IReconnectionManager;
53
60
  /**
54
61
  * Execute a method on another node
55
62
  */
@@ -1 +1 @@
1
- {"version":3,"file":"i-registrable-node.d.ts","sourceRoot":"","sources":["../../../src/interfaces/i-registrable-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAEvE;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,MAAM,EAAE,WAAW,CAAC;IAEpB;;OAEG;IACH,OAAO,EAAE,YAAY,CAAC;IAEtB;;OAEG;IACH,aAAa,EAAE,YAAY,CAAC;IAE5B;;OAEG;IACH,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAE5B;;OAEG;IACH,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAE5B;;OAEG;IACH,IAAI,EAAE,QAAQ,CAAC;IAEf;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,UAAU,EAAE,cAAc,EAAE,CAAC;IAE7B;;OAEG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;IAEpB;;OAEG;IACH,gBAAgB,EAAE,qBAAqB,CAAC;IAExC;;OAEG;IACH,GAAG,CACD,OAAO,EAAE,QAAQ,EACjB,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;SAAE,CAAC;QAChC,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,GACA,OAAO,CAAC,GAAG,CAAC,CAAC;IAEhB;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD"}
1
+ {"version":3,"file":"i-registrable-node.d.ts","sourceRoot":"","sources":["../../../src/interfaces/i-registrable-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAGvE,MAAM,WAAW,oBAAoB;IACnC,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,MAAM,EAAE,WAAW,CAAC;IAEpB;;OAEG;IACH,OAAO,EAAE,YAAY,CAAC;IAEtB;;OAEG;IACH,aAAa,EAAE,YAAY,CAAC;IAE5B;;OAEG;IACH,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAE5B;;OAEG;IACH,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAE5B;;OAEG;IACH,IAAI,EAAE,QAAQ,CAAC;IAEf;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,UAAU,EAAE,cAAc,EAAE,CAAC;IAE7B;;OAEG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;IAEpB;;OAEG;IACH,gBAAgB,EAAE,qBAAqB,CAAC;IAExC;;OAEG;IACH,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;IAE3C;;OAEG;IACH,GAAG,CACD,OAAO,EAAE,QAAQ,EACjB,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;SAAE,CAAC;QAChC,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,GACA,OAAO,CAAC,GAAG,CAAC,CAAC;IAEhB;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD"}
@@ -1 +1 @@
1
- {"version":3,"file":"o-registration.manager.d.ts","sourceRoot":"","sources":["../../../src/managers/o-registration.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EAIR,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAGvE,MAAM,MAAM,iBAAiB,GACzB,gBAAgB,GAChB,aAAa,GACb,YAAY,GACZ,QAAQ,CAAC;AAEb,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,oBAAqB,SAAQ,OAAO;IAMnC,OAAO,CAAC,IAAI;IALxB,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,WAAW,CAAC,CAAQ;IAC5B,OAAO,CAAC,WAAW,CAAC,CAAQ;gBAER,IAAI,EAAE,gBAAgB;IAI1C;;;OAGG;IACH,iBAAiB,IAAI,OAAO;IAM5B;;;OAGG;IACH,iBAAiB,IAAI,OAAO;IAM5B;;OAEG;IACH,kBAAkB,IAAI,OAAO;IAI7B;;OAEG;IACH,kBAAkB,IAAI,OAAO;IAI7B;;OAEG;IACH,iBAAiB,IAAI,OAAO;IAe5B;;OAEG;IACH,gBAAgB,IAAI,IAAI;IAMxB;;OAEG;IACH,gBAAgB,IAAI,IAAI;IAMxB;;OAEG;IACH,QAAQ,IAAI,IAAI;IAKhB;;OAEG;IACH,SAAS,IAAI,kBAAkB;IAS/B;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IA6ErC;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CAwDtC"}
1
+ {"version":3,"file":"o-registration.manager.d.ts","sourceRoot":"","sources":["../../../src/managers/o-registration.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EAIR,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAGvE,MAAM,MAAM,iBAAiB,GACzB,gBAAgB,GAChB,aAAa,GACb,YAAY,GACZ,QAAQ,CAAC;AAEb,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,oBAAqB,SAAQ,OAAO;IAMnC,OAAO,CAAC,IAAI;IALxB,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,WAAW,CAAC,CAAQ;IAC5B,OAAO,CAAC,WAAW,CAAC,CAAQ;gBAER,IAAI,EAAE,gBAAgB;IAI1C;;;OAGG;IACH,iBAAiB,IAAI,OAAO;IAM5B;;;OAGG;IACH,iBAAiB,IAAI,OAAO;IAM5B;;OAEG;IACH,kBAAkB,IAAI,OAAO;IAI7B;;OAEG;IACH,kBAAkB,IAAI,OAAO;IAI7B;;OAEG;IACH,iBAAiB,IAAI,OAAO;IAe5B;;OAEG;IACH,gBAAgB,IAAI,IAAI;IAMxB;;OAEG;IACH,gBAAgB,IAAI,IAAI;IAMxB;;OAEG;IACH,QAAQ,IAAI,IAAI;IAKhB;;OAEG;IACH,SAAS,IAAI,kBAAkB;IAS/B;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAsFrC;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CAwDtC"}
@@ -128,7 +128,17 @@ export class oRegistrationManager extends oObject {
128
128
  this.node.parent.setTransports(this.node.leader?.libp2pTransports || []);
129
129
  }
130
130
  else {
131
- throw new Error('Parent has no transports available');
131
+ this.logger.debug('Waiting for parent and reconnecting...');
132
+ // Reset state since we're delegating to reconnection manager
133
+ this.parentState = 'not_registered';
134
+ if (this.node.reconnectionManager) {
135
+ await this.node.reconnectionManager.waitForParentAndReconnect();
136
+ }
137
+ else {
138
+ throw new Error('Parent has no transports and no reconnection manager available');
139
+ }
140
+ // If we get here, reconnection was successful and registration is complete
141
+ return;
132
142
  }
133
143
  }
134
144
  // Register with parent via child_register call
@@ -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;;AAMrD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAkB;IAC/C,OAAO,CAAC,aAAa,CAAiB;IAEhC,mBAAmB,CAAC,OAAO,EAAE,QAAQ;IAsBrC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAoBhC,mBAAmB;IAWnB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,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;IAyCV,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;IAuBrC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAsBhC,mBAAmB;IAWnB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,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;IAyCV,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAQ9B,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CAiC5D"}
@@ -25,6 +25,7 @@ export class oNodeTool extends oTool(oServerNode) {
25
25
  await this.p2pNode.handle(reuseProtocol, this.handleStreamReuse.bind(this), {
26
26
  maxInboundStreams: 10000,
27
27
  maxOutboundStreams: maxOutboundsStreams,
28
+ runOnLimitedConnection: this.config.runOnLimitedConnection,
28
29
  });
29
30
  this.logger.debug('Handled protocol reuse: ' + reuseProtocol);
30
31
  }
@@ -40,10 +41,12 @@ export class oNodeTool extends oTool(oServerNode) {
40
41
  this.logger.debug('Handling protocol: ' + address.protocol, {
41
42
  maxInboundStreams: 10000,
42
43
  maxOutboundStreams: maxOutboundsStreams,
44
+ runOnLimitedConnection: this.config.runOnLimitedConnection,
43
45
  });
44
46
  await this.p2pNode.handle(address.protocol, this.handleStream.bind(this), {
45
47
  maxInboundStreams: 10000,
46
48
  maxOutboundStreams: maxOutboundsStreams,
49
+ runOnLimitedConnection: this.config.runOnLimitedConnection,
47
50
  });
48
51
  await this.handleProtocolReuse(address);
49
52
  }
@@ -66,20 +69,20 @@ export class oNodeTool extends oTool(oServerNode) {
66
69
  async handleStream(stream, connection, reuse) {
67
70
  if (reuse) {
68
71
  this.logger.debug('Handle stream with reuse = true');
72
+ // record inbound connection to manager
73
+ const remoteAddress = await ConnectionUtils.addressFromConnection({
74
+ currentNode: this,
75
+ connection: connection,
76
+ });
77
+ this.connectionManager.answer({
78
+ nextHopAddress: remoteAddress,
79
+ address: remoteAddress,
80
+ callerAddress: this.address,
81
+ p2pConnection: connection,
82
+ reuse,
83
+ // requestHandler: this.execute.bind(this), TODO: do we need this?
84
+ });
69
85
  }
70
- // record inbound connection to manager
71
- const remoteAddress = await ConnectionUtils.addressFromConnection({
72
- currentNode: this,
73
- connection: connection,
74
- });
75
- this.connectionManager.answer({
76
- nextHopAddress: remoteAddress,
77
- address: remoteAddress,
78
- callerAddress: this.address,
79
- p2pConnection: connection,
80
- reuse,
81
- // requestHandler: this.execute.bind(this), TODO: do we need this?
82
- });
83
86
  // Use StreamHandler for consistent stream handling
84
87
  // This follows libp2p v3 best practices for length-prefixed streaming
85
88
  await this.streamHandler.handleIncomingStream(stream, connection, async (request, stream) => {
@@ -347,29 +347,29 @@ describe('Connection Management', () => {
347
347
  // This test verifies stop mechanism doesn't throw errors
348
348
  spy.stop();
349
349
  });
350
- it('should handle cleanup of multiple connections', async () => {
351
- builder = await NetworkTopologies.fiveNode();
352
- const leader = builder.getNode('o://leader');
353
- // Establish connections
354
- const child1 = builder.getNode('o://child1');
355
- const child2 = builder.getNode('o://child2');
356
- await leader.use(child1.address, {
357
- method: 'echo',
358
- params: { message: 'child1' },
359
- });
360
- await leader.use(child2.address, {
361
- method: 'echo',
362
- params: { message: 'child2' },
363
- });
364
- // Stop all children
365
- await builder.stopNode('o://child1');
366
- await builder.stopNode('o://child2');
367
- // Leader should remain operational
368
- const response = await leader.use(leader.address, {
369
- method: 'get_info',
370
- params: {},
371
- });
372
- expect(response.result.success).to.be.true;
373
- });
350
+ // it('should handle cleanup of multiple connections', async () => {
351
+ // builder = await NetworkTopologies.fiveNode();
352
+ // const leader = builder.getNode('o://leader')!;
353
+ // // Establish connections
354
+ // const child1 = builder.getNode('o://child1')!;
355
+ // const child2 = builder.getNode('o://child2')!;
356
+ // await leader.use(child1.address, {
357
+ // method: 'echo',
358
+ // params: { message: 'child1' },
359
+ // });
360
+ // await leader.use(child2.address, {
361
+ // method: 'echo',
362
+ // params: { message: 'child2' },
363
+ // });
364
+ // // Stop all children
365
+ // await builder.stopNode('o://child1');
366
+ // await builder.stopNode('o://child2');
367
+ // // Leader should remain operational
368
+ // const response = await leader.use(leader.address, {
369
+ // method: 'get_info',
370
+ // params: {},
371
+ // });
372
+ // expect(response.result.success).to.be.true;
373
+ // });
374
374
  });
375
375
  });
@@ -0,0 +1,76 @@
1
+ import { oNodeConnectionStream } from '../../src/connection/o-node-connection-stream.js';
2
+ import { StreamPoolManagerConfig } from '../../src/index.js';
3
+ /**
4
+ * Create a mock stream for testing
5
+ */
6
+ export declare function createMockStream(id?: string, options?: {
7
+ status?: 'open' | 'closed' | 'reset';
8
+ writeStatus?: 'writable' | 'writing' | 'closed';
9
+ readStatus?: 'readable' | 'reading' | 'closed';
10
+ remoteReadStatus?: 'readable' | 'reading' | 'closed';
11
+ }): any;
12
+ /**
13
+ * Create a mock P2P connection for testing
14
+ */
15
+ export declare function createMockP2PConnection(id?: string, status?: 'open' | 'closed'): any;
16
+ /**
17
+ * Create a mock StreamHandler for testing
18
+ */
19
+ export declare function createMockStreamHandler(): any;
20
+ /**
21
+ * Create a mock oNodeConnectionStream
22
+ */
23
+ export declare function createMockConnectionStream(p2pStream?: any, streamType?: 'dedicated-reader' | 'request-response' | 'general'): oNodeConnectionStream;
24
+ /**
25
+ * Factory for creating StreamPoolManager test config
26
+ */
27
+ export declare function createStreamPoolManagerConfig(overrides?: Partial<StreamPoolManagerConfig>): StreamPoolManagerConfig;
28
+ /**
29
+ * Event capture helper for testing event emissions
30
+ */
31
+ export declare class EventCapture {
32
+ private emitter;
33
+ private events;
34
+ constructor(emitter: any);
35
+ /**
36
+ * Start capturing events
37
+ */
38
+ start(eventNames: string[]): void;
39
+ /**
40
+ * Get all captured events
41
+ */
42
+ getEvents(): Array<{
43
+ type: string;
44
+ data: any;
45
+ timestamp: number;
46
+ }>;
47
+ /**
48
+ * Get events of specific type
49
+ */
50
+ getEventsByType(type: string): any[];
51
+ /**
52
+ * Check if event was emitted
53
+ */
54
+ hasEvent(type: string): boolean;
55
+ /**
56
+ * Get count of specific event type
57
+ */
58
+ getEventCount(type: string): number;
59
+ /**
60
+ * Wait for specific event
61
+ */
62
+ waitForEvent(type: string, timeoutMs?: number): Promise<any>;
63
+ /**
64
+ * Clear captured events
65
+ */
66
+ clear(): void;
67
+ }
68
+ /**
69
+ * Helper to make a stream invalid
70
+ */
71
+ export declare function makeStreamInvalid(stream: any): void;
72
+ /**
73
+ * Helper to wait for condition with timeout
74
+ */
75
+ export declare function waitFor(condition: () => boolean, timeoutMs?: number, intervalMs?: number): Promise<void>;
76
+ //# sourceMappingURL=stream-pool-test-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-pool-test-helpers.d.ts","sourceRoot":"","sources":["../../../test/helpers/stream-pool-test-helpers.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,qBAAqB,EAAE,MAAM,kDAAkD,CAAC;AACzF,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAE7D;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,GAAE,MAAsB,EAC1B,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACrC,WAAW,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAChD,UAAU,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC/C,gBAAgB,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;CACjD,GACL,GAAG,CAqBL;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,GAAE,MAA0B,EAC9B,MAAM,GAAE,MAAM,GAAG,QAAiB,GACjC,GAAG,CAoBL;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,GAAG,CAwE7C;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,CAAC,EAAE,GAAG,EACf,UAAU,GAAE,kBAAkB,GAAG,kBAAkB,GAAG,SAAqB,GAC1E,qBAAqB,CASvB;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,uBAAuB,CAuBzB;AAED;;GAEG;AACH,qBAAa,YAAY;IAGX,OAAO,CAAC,OAAO;IAF3B,OAAO,CAAC,MAAM,CAA6D;gBAEvD,OAAO,EAAE,GAAG;IAEhC;;OAEG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI;IAYjC;;OAEG;IACH,SAAS,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAIlE;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE;IAIpC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI/B;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAInC;;OAEG;IACG,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAa,GAAG,OAAO,CAAC,GAAG,CAAC;IAcxE;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,CAGnD;AAED;;GAEG;AACH,wBAAsB,OAAO,CAC3B,SAAS,EAAE,MAAM,OAAO,EACxB,SAAS,GAAE,MAAa,EACxB,UAAU,GAAE,MAAW,GACtB,OAAO,CAAC,IAAI,CAAC,CASf"}