@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,424 @@
1
+ "use strict";
2
+ // import { expect } from 'chai';
3
+ // import { StreamPoolManager } from '../src/connection/stream-pool-manager.js';
4
+ // import { NetworkBuilder, NetworkTopologies } from './helpers/network-builder.js';
5
+ // import { createConnectionSpy } from './helpers/connection-spy.js';
6
+ // import { StreamPoolEvent } from '../src/connection/stream-pool-manager.events.js';
7
+ // import { oNodeConnectionStream } from '../src/connection/o-node-connection-stream.js';
8
+ // import type { TestTool } from './helpers/network-builder.js';
9
+ // import type { Connection } from '@olane/o-config';
10
+ // /**
11
+ // * Integration tests for StreamPoolManager using real libp2p nodes and connections
12
+ // */
13
+ // describe('StreamPoolManager Integration Tests', () => {
14
+ // let builder: NetworkBuilder;
15
+ // let leader: TestTool;
16
+ // let child: TestTool;
17
+ // let p2pConnection: Connection;
18
+ // let poolManager: StreamPoolManager | undefined;
19
+ // /**
20
+ // * Helper to create a real libp2p connection between two nodes
21
+ // */
22
+ // async function getP2pConnection(): Promise<Connection> {
23
+ // // Make a call to establish connection
24
+ // await leader.use(child.address, {
25
+ // method: 'echo',
26
+ // params: { message: 'establish connection' }
27
+ // });
28
+ // // Get the connection
29
+ // const connections = leader.p2pNode.getConnections();
30
+ // const connection = connections.find(
31
+ // conn => conn.remotePeer.toString() === child.peerId.toString()
32
+ // );
33
+ // if (!connection) {
34
+ // throw new Error('Failed to establish connection between nodes');
35
+ // }
36
+ // return connection;
37
+ // }
38
+ // /**
39
+ // * Helper to create a StreamPoolManager with real connection
40
+ // */
41
+ // async function createStreamPoolManager(config?: Partial<any>): Promise<StreamPoolManager> {
42
+ // const manager = new StreamPoolManager({
43
+ // poolSize: config?.poolSize || 10,
44
+ // readerStreamIndex: config?.readerStreamIndex ?? 0,
45
+ // streamHandler: {
46
+ // handleIncomingStream: config?.streamHandler?.handleIncomingStream || (async (stream: any) => {
47
+ // // Default handler: just keep stream alive
48
+ // return new Promise(() => {}); // Never resolves (keeps reader active)
49
+ // })
50
+ // },
51
+ // p2pConnection,
52
+ // requestHandler: config?.requestHandler,
53
+ // createStream: async () => {
54
+ // // Create a real libp2p stream
55
+ // const stream = await p2pConnection.newStream('/o-protocol/1.0.0');
56
+ // return new oNodeConnectionStream({
57
+ // p2pStream: stream,
58
+ // streamType: 'request-response',
59
+ // readTimeoutMs: 30000,
60
+ // drainTimeoutMs: 5000,
61
+ // });
62
+ // },
63
+ // ...config,
64
+ // });
65
+ // return manager;
66
+ // }
67
+ // beforeEach(async () => {
68
+ // // Create real node network
69
+ // builder = await NetworkTopologies.twoNode();
70
+ // leader = builder.getNode('o://leader')!;
71
+ // child = builder.getNode('o://child')!;
72
+ // // Establish real p2p connection
73
+ // p2pConnection = await getP2pConnection();
74
+ // });
75
+ // afterEach(async () => {
76
+ // if (poolManager) {
77
+ // await poolManager.close();
78
+ // poolManager = undefined;
79
+ // }
80
+ // await builder.cleanup();
81
+ // });
82
+ // describe('Initialization', () => {
83
+ // it('should initialize pool with default size of 10 streams', async () => {
84
+ // poolManager = await createStreamPoolManager();
85
+ // await poolManager.initialize();
86
+ // const stats = poolManager.getStats();
87
+ // expect(stats.totalStreams).to.equal(10);
88
+ // });
89
+ // it('should initialize pool with custom size', async () => {
90
+ // poolManager = await createStreamPoolManager({ poolSize: 5 });
91
+ // await poolManager.initialize();
92
+ // const stats = poolManager.getStats();
93
+ // expect(stats.totalStreams).to.equal(5);
94
+ // });
95
+ // it('should throw error when pool size is less than 2', async () => {
96
+ // expect(() => {
97
+ // poolManager = new StreamPoolManager({
98
+ // poolSize: 1,
99
+ // readerStreamIndex: 0,
100
+ // streamHandler: {} as any,
101
+ // p2pConnection,
102
+ // createStream: async () => ({} as any),
103
+ // });
104
+ // }).to.throw('Pool size must be at least 2');
105
+ // });
106
+ // it('should start dedicated reader automatically', async () => {
107
+ // let readerStarted = false;
108
+ // poolManager = await createStreamPoolManager();
109
+ // poolManager.on(StreamPoolEvent.ReaderStarted, () => {
110
+ // readerStarted = true;
111
+ // });
112
+ // await poolManager.initialize();
113
+ // // Wait a bit for reader to start
114
+ // await new Promise(resolve => setTimeout(resolve, 100));
115
+ // expect(readerStarted).to.be.true;
116
+ // });
117
+ // it('should emit pool-initialized event', async () => {
118
+ // let eventData: any;
119
+ // poolManager = await createStreamPoolManager();
120
+ // poolManager.on(StreamPoolEvent.PoolInitialized, (data: any) => {
121
+ // eventData = data;
122
+ // });
123
+ // await poolManager.initialize();
124
+ // expect(eventData).to.exist;
125
+ // expect(eventData.poolSize).to.equal(10);
126
+ // });
127
+ // it('should be idempotent (calling initialize twice is safe)', async () => {
128
+ // poolManager = await createStreamPoolManager();
129
+ // await poolManager.initialize();
130
+ // const statsFirst = poolManager.getStats();
131
+ // await poolManager.initialize();
132
+ // const statsSecond = poolManager.getStats();
133
+ // expect(statsFirst.totalStreams).to.equal(statsSecond.totalStreams);
134
+ // expect(statsSecond.totalStreams).to.equal(10);
135
+ // });
136
+ // it('should assign correct stream types', async () => {
137
+ // poolManager = await createStreamPoolManager({ poolSize: 3 });
138
+ // await poolManager.initialize();
139
+ // const stats = poolManager.getStats();
140
+ // expect(stats.requestResponseStreams).to.equal(2); // All except stream[0]
141
+ // });
142
+ // });
143
+ // describe('Round-Robin Stream Selection', () => {
144
+ // beforeEach(async () => {
145
+ // poolManager = await createStreamPoolManager({ poolSize: 5 });
146
+ // await poolManager.initialize();
147
+ // });
148
+ // it('should return streams in round-robin order', async () => {
149
+ // const stream1 = await poolManager!.getStream();
150
+ // const stream2 = await poolManager!.getStream();
151
+ // const stream3 = await poolManager!.getStream();
152
+ // const stream4 = await poolManager!.getStream();
153
+ // // Should cycle through streams[1-4]
154
+ // expect(stream1.p2pStream.id).to.not.equal(stream2.p2pStream.id);
155
+ // expect(stream2.p2pStream.id).to.not.equal(stream3.p2pStream.id);
156
+ // expect(stream3.p2pStream.id).to.not.equal(stream4.p2pStream.id);
157
+ // });
158
+ // it('should wrap around after last stream', async () => {
159
+ // // Pool size is 5, so we have streams 1-4 for request-response (4 streams total)
160
+ // const firstRound: any[] = [];
161
+ // for (let i = 0; i < 4; i++) {
162
+ // firstRound.push(await poolManager!.getStream());
163
+ // }
164
+ // const nextStream = await poolManager!.getStream();
165
+ // // Should wrap back to first request-response stream
166
+ // expect(nextStream.p2pStream.id).to.equal(firstRound[0].p2pStream.id);
167
+ // });
168
+ // it('should throw error when getStream called before initialize', async () => {
169
+ // const uninitializedManager = await createStreamPoolManager();
170
+ // try {
171
+ // await uninitializedManager.getStream();
172
+ // expect.fail('Should have thrown error');
173
+ // } catch (error: any) {
174
+ // expect(error.message).to.include('not initialized');
175
+ // } finally {
176
+ // await uninitializedManager.close();
177
+ // }
178
+ // });
179
+ // });
180
+ // describe('Stream Validation and Replacement', () => {
181
+ // it('should detect invalid stream and replace it', async () => {
182
+ // poolManager = await createStreamPoolManager({ poolSize: 3 });
183
+ // await poolManager.initialize();
184
+ // // Get first stream and forcibly close it
185
+ // const firstStream = await poolManager.getStream();
186
+ // await firstStream.p2pStream.close();
187
+ // // Wait for stream to be marked invalid
188
+ // await new Promise(resolve => setTimeout(resolve, 100));
189
+ // // Next getStream should detect and replace the invalid stream
190
+ // const nextStream = await poolManager.getStream();
191
+ // expect(nextStream.isValid).to.be.true;
192
+ // });
193
+ // it('should emit stream-replaced event when replacing stream', async () => {
194
+ // let replacedEventFired = false;
195
+ // poolManager = await createStreamPoolManager({ poolSize: 3 });
196
+ // poolManager.on(StreamPoolEvent.StreamReplaced, () => {
197
+ // replacedEventFired = true;
198
+ // });
199
+ // await poolManager.initialize();
200
+ // // Close a request-response stream
201
+ // const stream = await poolManager.getStream();
202
+ // await stream.p2pStream.close();
203
+ // // Wait for detection and replacement
204
+ // await new Promise(resolve => setTimeout(resolve, 100));
205
+ // // Trigger getStream to force validation
206
+ // await poolManager.getStream();
207
+ // expect(replacedEventFired).to.be.true;
208
+ // });
209
+ // });
210
+ // describe('Dedicated Reader Recovery', () => {
211
+ // it('should detect reader failure and trigger recovery', async () => {
212
+ // let failureDetected = false;
213
+ // const mockStreamHandler = {
214
+ // handleIncomingStream: async () => {
215
+ // failureDetected = true;
216
+ // throw new Error('Reader stream failed');
217
+ // },
218
+ // };
219
+ // poolManager = await createStreamPoolManager({
220
+ // poolSize: 3,
221
+ // streamHandler: mockStreamHandler,
222
+ // });
223
+ // poolManager.on(StreamPoolEvent.ReaderFailed, () => {
224
+ // failureDetected = true;
225
+ // });
226
+ // await poolManager.initialize();
227
+ // // Wait for reader failure event
228
+ // await new Promise(resolve => setTimeout(resolve, 500));
229
+ // const stats = poolManager.getStats();
230
+ // expect(stats.failureCount).to.be.greaterThan(0);
231
+ // });
232
+ // it('should emit reader-recovered event on successful recovery', async () => {
233
+ // let callCount = 0;
234
+ // let recoveryEventFired = false;
235
+ // const mockStreamHandler = {
236
+ // handleIncomingStream: async () => {
237
+ // callCount++;
238
+ // if (callCount === 1) {
239
+ // throw new Error('Initial failure');
240
+ // }
241
+ // // Second call succeeds (recovery)
242
+ // return new Promise(() => {}); // Never resolves (keeps reader active)
243
+ // },
244
+ // };
245
+ // poolManager = await createStreamPoolManager({
246
+ // poolSize: 3,
247
+ // streamHandler: mockStreamHandler,
248
+ // });
249
+ // poolManager.on(StreamPoolEvent.ReaderRecovered, () => {
250
+ // recoveryEventFired = true;
251
+ // });
252
+ // await poolManager.initialize();
253
+ // // Wait for recovery
254
+ // await new Promise(resolve => setTimeout(resolve, 1000));
255
+ // expect(recoveryEventFired).to.be.true;
256
+ // });
257
+ // it('should not attempt recovery during close', async () => {
258
+ // let recoveryAttempted = false;
259
+ // const mockStreamHandler = {
260
+ // handleIncomingStream: async () => {
261
+ // await new Promise((resolve) => setTimeout(resolve, 50));
262
+ // throw new Error('Reader failed');
263
+ // },
264
+ // };
265
+ // poolManager = await createStreamPoolManager({
266
+ // poolSize: 3,
267
+ // streamHandler: mockStreamHandler,
268
+ // });
269
+ // poolManager.on(StreamPoolEvent.ReaderRecovered, () => {
270
+ // recoveryAttempted = true;
271
+ // });
272
+ // await poolManager.initialize();
273
+ // // Close immediately
274
+ // await poolManager.close();
275
+ // // Wait a bit
276
+ // await new Promise((resolve) => setTimeout(resolve, 200));
277
+ // // Should not have attempted recovery
278
+ // expect(recoveryAttempted).to.be.false;
279
+ // });
280
+ // });
281
+ // describe('Statistics', () => {
282
+ // it('should return accurate totalStreams count', async () => {
283
+ // poolManager = await createStreamPoolManager({ poolSize: 7 });
284
+ // await poolManager.initialize();
285
+ // const stats = poolManager.getStats();
286
+ // expect(stats.totalStreams).to.equal(7);
287
+ // });
288
+ // it('should return accurate healthyStreams count', async () => {
289
+ // poolManager = await createStreamPoolManager({ poolSize: 5 });
290
+ // await poolManager.initialize();
291
+ // const stats = poolManager.getStats();
292
+ // expect(stats.healthyStreams).to.be.greaterThan(0);
293
+ // });
294
+ // it('should report reader health status', async () => {
295
+ // poolManager = await createStreamPoolManager({ poolSize: 3 });
296
+ // await poolManager.initialize();
297
+ // const stats = poolManager.getStats();
298
+ // expect(stats.readerStreamHealth).to.be.oneOf([
299
+ // 'healthy',
300
+ // 'unhealthy',
301
+ // 'not-initialized',
302
+ // ]);
303
+ // });
304
+ // it('should track request-response stream count', async () => {
305
+ // poolManager = await createStreamPoolManager({ poolSize: 6 });
306
+ // await poolManager.initialize();
307
+ // const stats = poolManager.getStats();
308
+ // expect(stats.requestResponseStreams).to.equal(5); // 6 total - 1 reader
309
+ // });
310
+ // it('should increment failureCount on reader failures', async () => {
311
+ // let callCount = 0;
312
+ // const mockStreamHandler = {
313
+ // handleIncomingStream: async () => {
314
+ // callCount++;
315
+ // if (callCount <= 2) {
316
+ // throw new Error('Failure');
317
+ // }
318
+ // return new Promise(() => {}); // Then succeed
319
+ // },
320
+ // };
321
+ // poolManager = await createStreamPoolManager({
322
+ // poolSize: 3,
323
+ // streamHandler: mockStreamHandler,
324
+ // });
325
+ // await poolManager.initialize();
326
+ // // Wait for failures
327
+ // await new Promise(resolve => setTimeout(resolve, 1000));
328
+ // const stats = poolManager.getStats();
329
+ // expect(stats.failureCount).to.be.at.least(1);
330
+ // });
331
+ // });
332
+ // describe('Event Emission', () => {
333
+ // it('should emit all lifecycle events with correct data', async () => {
334
+ // let poolInitialized = false;
335
+ // let readerStarted = false;
336
+ // let poolClosed = false;
337
+ // poolManager = await createStreamPoolManager({ poolSize: 3 });
338
+ // poolManager.on(StreamPoolEvent.PoolInitialized, () => {
339
+ // poolInitialized = true;
340
+ // });
341
+ // poolManager.on(StreamPoolEvent.ReaderStarted, () => {
342
+ // readerStarted = true;
343
+ // });
344
+ // poolManager.on(StreamPoolEvent.PoolClosed, () => {
345
+ // poolClosed = true;
346
+ // });
347
+ // await poolManager.initialize();
348
+ // await new Promise(resolve => setTimeout(resolve, 100));
349
+ // await poolManager.close();
350
+ // expect(poolInitialized).to.be.true;
351
+ // expect(readerStarted).to.be.true;
352
+ // expect(poolClosed).to.be.true;
353
+ // });
354
+ // it('should support multiple listeners for same event', async () => {
355
+ // let listener1Called = false;
356
+ // let listener2Called = false;
357
+ // poolManager = await createStreamPoolManager({ poolSize: 3 });
358
+ // poolManager.on(StreamPoolEvent.PoolInitialized, () => {
359
+ // listener1Called = true;
360
+ // });
361
+ // poolManager.on(StreamPoolEvent.PoolInitialized, () => {
362
+ // listener2Called = true;
363
+ // });
364
+ // await poolManager.initialize();
365
+ // expect(listener1Called).to.be.true;
366
+ // expect(listener2Called).to.be.true;
367
+ // });
368
+ // it('should allow removing event listeners', async () => {
369
+ // let callCount = 0;
370
+ // const listener = () => {
371
+ // callCount++;
372
+ // };
373
+ // poolManager = await createStreamPoolManager({ poolSize: 3 });
374
+ // poolManager.on(StreamPoolEvent.PoolInitialized, listener);
375
+ // await poolManager.initialize();
376
+ // expect(callCount).to.equal(1);
377
+ // poolManager.off(StreamPoolEvent.PoolInitialized, listener);
378
+ // // Verify off doesn't throw
379
+ // expect(() =>
380
+ // poolManager!.off(StreamPoolEvent.PoolInitialized, listener),
381
+ // ).to.not.throw();
382
+ // });
383
+ // });
384
+ // describe('Cleanup and Lifecycle', () => {
385
+ // it('should close all streams on close()', async () => {
386
+ // poolManager = await createStreamPoolManager({ poolSize: 5 });
387
+ // await poolManager.initialize();
388
+ // const statsBefore = poolManager.getStats();
389
+ // expect(statsBefore.totalStreams).to.equal(5);
390
+ // await poolManager.close();
391
+ // const statsAfter = poolManager.getStats();
392
+ // expect(statsAfter.totalStreams).to.equal(0);
393
+ // });
394
+ // it('should emit pool-closed event', async () => {
395
+ // let closedEventFired = false;
396
+ // poolManager = await createStreamPoolManager({ poolSize: 3 });
397
+ // poolManager.on(StreamPoolEvent.PoolClosed, () => {
398
+ // closedEventFired = true;
399
+ // });
400
+ // await poolManager.initialize();
401
+ // await poolManager.close();
402
+ // expect(closedEventFired).to.be.true;
403
+ // });
404
+ // it('should be safe to call close() multiple times', async () => {
405
+ // poolManager = await createStreamPoolManager({ poolSize: 3 });
406
+ // await poolManager.initialize();
407
+ // await poolManager.close();
408
+ // await poolManager.close(); // Should not throw
409
+ // const stats = poolManager.getStats();
410
+ // expect(stats.totalStreams).to.equal(0);
411
+ // });
412
+ // it('should prevent getStream() after close()', async () => {
413
+ // poolManager = await createStreamPoolManager({ poolSize: 3 });
414
+ // await poolManager.initialize();
415
+ // await poolManager.close();
416
+ // try {
417
+ // await poolManager.getStream();
418
+ // expect.fail('Should have thrown error');
419
+ // } catch (error: any) {
420
+ // expect(error.message).to.include('not initialized');
421
+ // }
422
+ // });
423
+ // });
424
+ // });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olane/o-node",
3
- "version": "0.7.49",
3
+ "version": "0.7.51",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "devDependencies": {
41
41
  "@eslint/eslintrc": "^3.3.1",
42
42
  "@eslint/js": "^9.29.0",
43
- "@olane/o-test": "0.7.49",
43
+ "@olane/o-test": "0.7.51",
44
44
  "@tsconfig/node20": "^20.1.6",
45
45
  "@types/jest": "^30.0.0",
46
46
  "@types/json5": "^2.2.0",
@@ -60,13 +60,13 @@
60
60
  "typescript": "5.4.5"
61
61
  },
62
62
  "dependencies": {
63
- "@olane/o-config": "0.7.49",
64
- "@olane/o-core": "0.7.49",
65
- "@olane/o-protocol": "0.7.49",
66
- "@olane/o-tool": "0.7.49",
63
+ "@olane/o-config": "0.7.51",
64
+ "@olane/o-core": "0.7.51",
65
+ "@olane/o-protocol": "0.7.51",
66
+ "@olane/o-tool": "0.7.51",
67
67
  "debug": "^4.4.1",
68
68
  "dotenv": "^16.5.0",
69
69
  "json5": "^2.2.3"
70
70
  },
71
- "gitHead": "710399363610686d2c245af0ac6773fb794add25"
71
+ "gitHead": "f770a110369cb9791d9e9208c7a084feecbb9b2c"
72
72
  }