@olane/o-node 0.7.50 → 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.
- package/dist/src/connection/index.d.ts +6 -1
- package/dist/src/connection/index.d.ts.map +1 -1
- package/dist/src/connection/index.js +6 -1
- package/dist/src/connection/interfaces/o-node-stream.config.d.ts +16 -0
- package/dist/src/connection/interfaces/o-node-stream.config.d.ts.map +1 -0
- package/dist/src/connection/interfaces/stream-init-message.d.ts +29 -0
- package/dist/src/connection/interfaces/stream-init-message.d.ts.map +1 -0
- package/dist/src/connection/interfaces/stream-init-message.js +8 -0
- package/dist/src/connection/interfaces/stream-manager.config.d.ts +8 -0
- package/dist/src/connection/interfaces/stream-manager.config.d.ts.map +1 -0
- package/dist/src/connection/interfaces/stream-manager.config.js +1 -0
- package/dist/src/connection/o-node-connection.d.ts +5 -7
- package/dist/src/connection/o-node-connection.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.js +26 -56
- package/dist/src/connection/o-node-connection.manager.d.ts +7 -0
- package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.manager.js +23 -5
- package/dist/src/connection/{o-node-connection-stream.d.ts → o-node-stream.d.ts} +18 -6
- package/dist/src/connection/o-node-stream.d.ts.map +1 -0
- package/dist/src/connection/{o-node-connection-stream.js → o-node-stream.js} +20 -2
- package/dist/src/connection/o-node-stream.manager.d.ts +181 -0
- package/dist/src/connection/o-node-stream.manager.d.ts.map +1 -0
- package/dist/src/connection/o-node-stream.manager.js +526 -0
- package/dist/src/connection/stream-manager.events.d.ts +83 -0
- package/dist/src/connection/stream-manager.events.d.ts.map +1 -0
- package/dist/src/connection/stream-manager.events.js +18 -0
- package/dist/src/o-node.tool.d.ts +0 -1
- package/dist/src/o-node.tool.d.ts.map +1 -1
- package/dist/src/o-node.tool.js +30 -20
- package/dist/test/connection-management.spec.js +24 -24
- package/dist/test/helpers/stream-pool-test-helpers.d.ts +1 -0
- package/dist/test/helpers/stream-pool-test-helpers.d.ts.map +1 -0
- package/dist/test/helpers/stream-pool-test-helpers.js +262 -0
- package/dist/test/network-communication.spec.js +68 -66
- package/dist/test/parent-child-registration.spec.js +1 -0
- package/dist/test/stream-pool-manager.spec.d.ts +1 -0
- package/dist/test/stream-pool-manager.spec.d.ts.map +1 -0
- package/dist/test/stream-pool-manager.spec.js +424 -0
- package/package.json +7 -7
- package/dist/src/connection/interfaces/o-node-connection-stream.config.d.ts +0 -8
- package/dist/src/connection/interfaces/o-node-connection-stream.config.d.ts.map +0 -1
- package/dist/src/connection/o-node-connection-stream.d.ts.map +0 -1
- package/dist/src/connection/stream-handler.d.ts +0 -102
- package/dist/src/connection/stream-handler.d.ts.map +0 -1
- package/dist/src/connection/stream-handler.js +0 -357
- /package/dist/src/connection/interfaces/{o-node-connection-stream.config.js → o-node-stream.config.js} +0 -0
|
@@ -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.
|
|
3
|
+
"version": "0.7.52",
|
|
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.
|
|
43
|
+
"@olane/o-test": "0.7.52",
|
|
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.
|
|
64
|
-
"@olane/o-core": "0.7.
|
|
65
|
-
"@olane/o-protocol": "0.7.
|
|
66
|
-
"@olane/o-tool": "0.7.
|
|
63
|
+
"@olane/o-config": "0.7.52",
|
|
64
|
+
"@olane/o-core": "0.7.52",
|
|
65
|
+
"@olane/o-protocol": "0.7.52",
|
|
66
|
+
"@olane/o-tool": "0.7.52",
|
|
67
67
|
"debug": "^4.4.1",
|
|
68
68
|
"dotenv": "^16.5.0",
|
|
69
69
|
"json5": "^2.2.3"
|
|
70
70
|
},
|
|
71
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "706ee0d5b546e96b6c05270b39035ee7e08dadbd"
|
|
72
72
|
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { oAddress } from '@olane/o-core';
|
|
2
|
-
import { StreamReusePolicy } from '../stream-handler.config';
|
|
3
|
-
export interface oNodeConnectionStreamConfig {
|
|
4
|
-
direction: 'inbound' | 'outbound';
|
|
5
|
-
reusePolicy: StreamReusePolicy;
|
|
6
|
-
remoteAddress: oAddress;
|
|
7
|
-
}
|
|
8
|
-
//# sourceMappingURL=o-node-connection-stream.config.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection-stream.config.d.ts","sourceRoot":"","sources":["../../../../src/connection/interfaces/o-node-connection-stream.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC;IAClC,WAAW,EAAE,iBAAiB,CAAC;IAC/B,aAAa,EAAE,QAAQ,CAAC;CACzB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection-stream.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection-stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAuB,OAAO,EAAiB,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,2BAA2B,EAAE,MAAM,iDAAiD,CAAC;AAE9F;;;;;;;;GAQG;AACH,qBAAa,qBAAsB,SAAQ,OAAO;aAI9B,SAAS,EAAE,MAAM;aACjB,MAAM,EAAE,2BAA2B;IAJrD,SAAgB,SAAS,EAAE,MAAM,CAAC;gBAGhB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,2BAA2B;IAOrD,QAAQ;IA6BR;;;;;;;OAOG;IACH,IAAI,OAAO,IAAI,OAAO,CAMrB;IAED;;OAEG;IACH,IAAI,GAAG,IAAI,MAAM,CAEhB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB7B"}
|
|
@@ -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"}
|