@olane/o-client-limited 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/o-limited-connection.d.ts +23 -1
- package/dist/src/connection/o-limited-connection.d.ts.map +1 -1
- package/dist/src/connection/o-limited-connection.js +48 -3
- package/dist/src/connection/o-limited.stream-manager.d.ts +79 -0
- package/dist/src/connection/o-limited.stream-manager.d.ts.map +1 -0
- package/dist/src/connection/o-limited.stream-manager.js +234 -0
- package/dist/src/o-limited.tool.d.ts +0 -7
- package/dist/src/o-limited.tool.d.ts.map +1 -1
- package/dist/src/o-limited.tool.js +1 -39
- package/dist/test/bidirectional-communication.spec.d.ts +2 -0
- package/dist/test/bidirectional-communication.spec.d.ts.map +1 -0
- package/dist/test/bidirectional-communication.spec.js +265 -0
- package/dist/test/helpers/index.d.ts +2 -3
- package/dist/test/helpers/index.d.ts.map +1 -1
- package/dist/test/helpers/index.js +2 -3
- package/dist/test/helpers/limited-test-tool.d.ts +41 -0
- package/dist/test/helpers/limited-test-tool.d.ts.map +1 -0
- package/dist/test/helpers/limited-test-tool.js +98 -0
- package/dist/test/helpers/receiver-test-tool.d.ts +34 -0
- package/dist/test/helpers/receiver-test-tool.d.ts.map +1 -0
- package/dist/test/helpers/receiver-test-tool.js +67 -0
- package/dist/test/limited-connection-lifecycle.spec.d.ts +2 -0
- package/dist/test/limited-connection-lifecycle.spec.d.ts.map +1 -0
- package/dist/test/limited-connection-lifecycle.spec.js +209 -0
- package/dist/test/limited-stream-manager.spec.d.ts +2 -0
- package/dist/test/limited-stream-manager.spec.d.ts.map +1 -0
- package/dist/test/limited-stream-manager.spec.js +222 -0
- package/dist/test/reader-stream-recovery.spec.d.ts +2 -0
- package/dist/test/reader-stream-recovery.spec.d.ts.map +1 -0
- package/dist/test/reader-stream-recovery.spec.js +188 -0
- package/package.json +8 -8
- package/dist/test/configuration.spec.d.ts +0 -1
- package/dist/test/configuration.spec.d.ts.map +0 -1
- package/dist/test/configuration.spec.js +0 -335
- package/dist/test/error-handling.spec.d.ts +0 -2
- package/dist/test/error-handling.spec.d.ts.map +0 -1
- package/dist/test/error-handling.spec.js +0 -378
- package/dist/test/helpers/mock-p2p-connection.d.ts +0 -38
- package/dist/test/helpers/mock-p2p-connection.d.ts.map +0 -1
- package/dist/test/helpers/mock-p2p-connection.js +0 -66
- package/dist/test/helpers/mock-stream-handler.d.ts +0 -43
- package/dist/test/helpers/mock-stream-handler.d.ts.map +0 -1
- package/dist/test/helpers/mock-stream-handler.js +0 -71
- package/dist/test/helpers/mock-stream.d.ts +0 -46
- package/dist/test/helpers/mock-stream.d.ts.map +0 -1
- package/dist/test/helpers/mock-stream.js +0 -59
- package/dist/test/method.spec.d.ts +0 -1
- package/dist/test/method.spec.d.ts.map +0 -1
- package/dist/test/method.spec.js +0 -29
- package/dist/test/stream-reuse.spec.d.ts +0 -1
- package/dist/test/stream-reuse.spec.d.ts.map +0 -1
- package/dist/test/stream-reuse.spec.js +0 -267
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import { TestEnvironment, createConnectionSpy, } from '@olane/o-node/test/helpers';
|
|
3
|
+
import { oNodeAddress } from '@olane/o-node';
|
|
4
|
+
import { LimitedTestTool, ReceiverTestTool } from './helpers/index.js';
|
|
5
|
+
describe('Limited Connection Lifecycle', () => {
|
|
6
|
+
const env = new TestEnvironment();
|
|
7
|
+
let caller;
|
|
8
|
+
let receiver;
|
|
9
|
+
afterEach(async () => {
|
|
10
|
+
await env.cleanup();
|
|
11
|
+
});
|
|
12
|
+
describe('Connection Creation', () => {
|
|
13
|
+
it('should create limited connection with reuse policy', async () => {
|
|
14
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
15
|
+
address: new oNodeAddress('o://caller'),
|
|
16
|
+
});
|
|
17
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
18
|
+
address: new oNodeAddress('o://receiver'),
|
|
19
|
+
});
|
|
20
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
21
|
+
await caller.use(receiverAddr, {
|
|
22
|
+
method: 'echo',
|
|
23
|
+
params: { message: 'test' },
|
|
24
|
+
});
|
|
25
|
+
const callerConn = caller.getFirstConnection();
|
|
26
|
+
expect(callerConn).to.exist;
|
|
27
|
+
expect(callerConn.reusePolicy).to.equal('reuse');
|
|
28
|
+
});
|
|
29
|
+
it('should initialize stream manager on first use', async () => {
|
|
30
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
31
|
+
address: new oNodeAddress('o://caller'),
|
|
32
|
+
});
|
|
33
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
34
|
+
address: new oNodeAddress('o://receiver'),
|
|
35
|
+
});
|
|
36
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
37
|
+
// Before first use, no connections
|
|
38
|
+
const connectionsBefore = Array.from(caller.connectionManager.connections.values());
|
|
39
|
+
expect(connectionsBefore).to.have.lengthOf(0);
|
|
40
|
+
// Make first request
|
|
41
|
+
await caller.use(receiverAddr, {
|
|
42
|
+
method: 'echo',
|
|
43
|
+
params: { message: 'test' },
|
|
44
|
+
});
|
|
45
|
+
// After first use, connection and stream manager should exist
|
|
46
|
+
const callerConn = caller.getFirstConnection();
|
|
47
|
+
expect(callerConn).to.exist;
|
|
48
|
+
expect(callerConn.streamManager).to.exist;
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('Connection Reuse', () => {
|
|
52
|
+
it('should maintain single connection across multiple requests', async () => {
|
|
53
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
54
|
+
address: new oNodeAddress('o://caller'),
|
|
55
|
+
});
|
|
56
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
57
|
+
address: new oNodeAddress('o://receiver'),
|
|
58
|
+
});
|
|
59
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
60
|
+
const spy = createConnectionSpy(caller);
|
|
61
|
+
spy.start();
|
|
62
|
+
// Make multiple requests
|
|
63
|
+
for (let i = 0; i < 5; i++) {
|
|
64
|
+
await caller.use(receiverAddr, {
|
|
65
|
+
method: 'echo',
|
|
66
|
+
params: { message: `request-${i}` },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// Should only have 1 connection
|
|
70
|
+
const summary = spy.getSummary();
|
|
71
|
+
expect(summary.currentConnections).to.equal(1);
|
|
72
|
+
spy.stop();
|
|
73
|
+
});
|
|
74
|
+
it('should properly initialize when using ConnectionSpy', async () => {
|
|
75
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
76
|
+
address: new oNodeAddress('o://caller'),
|
|
77
|
+
});
|
|
78
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
79
|
+
address: new oNodeAddress('o://receiver'),
|
|
80
|
+
});
|
|
81
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
82
|
+
const spy = createConnectionSpy(caller);
|
|
83
|
+
spy.start();
|
|
84
|
+
// Make requests
|
|
85
|
+
await caller.use(receiverAddr, {
|
|
86
|
+
method: 'echo',
|
|
87
|
+
params: { message: 'test1' },
|
|
88
|
+
});
|
|
89
|
+
await caller.use(receiverAddr, {
|
|
90
|
+
method: 'echo',
|
|
91
|
+
params: { message: 'test2' },
|
|
92
|
+
});
|
|
93
|
+
const stats = spy.getConnectionStats();
|
|
94
|
+
expect(stats).to.have.lengthOf(1);
|
|
95
|
+
expect(stats[0].status).to.equal('open');
|
|
96
|
+
spy.stop();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('Cleanup', () => {
|
|
100
|
+
it('should cleanup all streams on close', async () => {
|
|
101
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
102
|
+
address: new oNodeAddress('o://caller'),
|
|
103
|
+
});
|
|
104
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
105
|
+
address: new oNodeAddress('o://receiver'),
|
|
106
|
+
});
|
|
107
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
108
|
+
// Create connection
|
|
109
|
+
await caller.use(receiverAddr, {
|
|
110
|
+
method: 'echo',
|
|
111
|
+
params: { message: 'test' },
|
|
112
|
+
});
|
|
113
|
+
const callerConn = caller.getFirstConnection();
|
|
114
|
+
expect(callerConn).to.exist;
|
|
115
|
+
// Close connection
|
|
116
|
+
await callerConn.close();
|
|
117
|
+
// Connection should be closed
|
|
118
|
+
expect(callerConn.p2pConnection.status).to.equal('closed');
|
|
119
|
+
});
|
|
120
|
+
it('should handle cleanup during node stop', async () => {
|
|
121
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
122
|
+
address: new oNodeAddress('o://caller'),
|
|
123
|
+
});
|
|
124
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
125
|
+
address: new oNodeAddress('o://receiver'),
|
|
126
|
+
});
|
|
127
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
128
|
+
// Create connection
|
|
129
|
+
await caller.use(receiverAddr, {
|
|
130
|
+
method: 'echo',
|
|
131
|
+
params: { message: 'test' },
|
|
132
|
+
});
|
|
133
|
+
const callerConn = caller.getFirstConnection();
|
|
134
|
+
const connectionId = callerConn.p2pConnection.id;
|
|
135
|
+
// Stop caller (should cleanup all connections)
|
|
136
|
+
await caller.stop();
|
|
137
|
+
// Connection should be closed
|
|
138
|
+
expect(callerConn.p2pConnection.status).to.equal('closed');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe('Multiple Concurrent Connections', () => {
|
|
142
|
+
it('should handle multiple concurrent connections to different receivers', async () => {
|
|
143
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
144
|
+
address: new oNodeAddress('o://caller'),
|
|
145
|
+
});
|
|
146
|
+
const receiver1 = await env.createNode(ReceiverTestTool, {
|
|
147
|
+
address: new oNodeAddress('o://receiver1'),
|
|
148
|
+
});
|
|
149
|
+
const receiver2 = await env.createNode(ReceiverTestTool, {
|
|
150
|
+
address: new oNodeAddress('o://receiver2'),
|
|
151
|
+
});
|
|
152
|
+
const receiver1Addr = new oNodeAddress(receiver1.address.toString(), receiver1.address.libp2pTransports);
|
|
153
|
+
const receiver2Addr = new oNodeAddress(receiver2.address.toString(), receiver2.address.libp2pTransports);
|
|
154
|
+
const spy = createConnectionSpy(caller);
|
|
155
|
+
spy.start();
|
|
156
|
+
// Make concurrent requests to both receivers
|
|
157
|
+
const promises = [];
|
|
158
|
+
for (let i = 0; i < 3; i++) {
|
|
159
|
+
promises.push(caller.use(receiver1Addr, {
|
|
160
|
+
method: 'echo',
|
|
161
|
+
params: { message: `r1-${i}` },
|
|
162
|
+
}));
|
|
163
|
+
promises.push(caller.use(receiver2Addr, {
|
|
164
|
+
method: 'echo',
|
|
165
|
+
params: { message: `r2-${i}` },
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
const responses = await Promise.all(promises);
|
|
169
|
+
responses.forEach((response) => {
|
|
170
|
+
expect(response.result.success).to.be.true;
|
|
171
|
+
});
|
|
172
|
+
// Should have 2 connections (one to each receiver)
|
|
173
|
+
const summary = spy.getSummary();
|
|
174
|
+
expect(summary.currentConnections).to.equal(2);
|
|
175
|
+
// Each receiver should have received 3 requests
|
|
176
|
+
expect(receiver1.receivedRequests).to.have.lengthOf(3);
|
|
177
|
+
expect(receiver2.receivedRequests).to.have.lengthOf(3);
|
|
178
|
+
spy.stop();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
describe('Connection State', () => {
|
|
182
|
+
it('should maintain connection state across requests', async () => {
|
|
183
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
184
|
+
address: new oNodeAddress('o://caller'),
|
|
185
|
+
});
|
|
186
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
187
|
+
address: new oNodeAddress('o://receiver'),
|
|
188
|
+
});
|
|
189
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
190
|
+
// Make first request
|
|
191
|
+
await caller.use(receiverAddr, {
|
|
192
|
+
method: 'echo',
|
|
193
|
+
params: { message: 'first' },
|
|
194
|
+
});
|
|
195
|
+
const callerConnFirst = caller.getFirstConnection();
|
|
196
|
+
const connectionIdFirst = callerConnFirst.p2pConnection.id;
|
|
197
|
+
// Make second request
|
|
198
|
+
await caller.use(receiverAddr, {
|
|
199
|
+
method: 'echo',
|
|
200
|
+
params: { message: 'second' },
|
|
201
|
+
});
|
|
202
|
+
const callerConnSecond = caller.getFirstConnection();
|
|
203
|
+
const connectionIdSecond = callerConnSecond.p2pConnection.id;
|
|
204
|
+
// Should be the same connection
|
|
205
|
+
expect(connectionIdFirst).to.equal(connectionIdSecond);
|
|
206
|
+
expect(callerConnFirst).to.equal(callerConnSecond);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limited-stream-manager.spec.d.ts","sourceRoot":"","sources":["../../test/limited-stream-manager.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import { TestEnvironment } from '@olane/o-node/test/helpers';
|
|
3
|
+
import { oNodeAddress } from '@olane/o-node';
|
|
4
|
+
import { LimitedTestTool, ReceiverTestTool } from './helpers/index.js';
|
|
5
|
+
describe('Limited Stream Manager', () => {
|
|
6
|
+
const env = new TestEnvironment();
|
|
7
|
+
let caller;
|
|
8
|
+
let receiver;
|
|
9
|
+
afterEach(async () => {
|
|
10
|
+
await env.cleanup();
|
|
11
|
+
});
|
|
12
|
+
describe('Initialization', () => {
|
|
13
|
+
it('should create dedicated reader stream on initialization', async () => {
|
|
14
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
15
|
+
address: new oNodeAddress('o://caller'),
|
|
16
|
+
});
|
|
17
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
18
|
+
address: new oNodeAddress('o://receiver'),
|
|
19
|
+
});
|
|
20
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
21
|
+
// Make a connection to trigger initialization
|
|
22
|
+
await caller.use(receiverAddr, {
|
|
23
|
+
method: 'echo',
|
|
24
|
+
params: { message: 'test' },
|
|
25
|
+
});
|
|
26
|
+
// Set up event listeners after connection
|
|
27
|
+
const callerConn = caller.getFirstConnection();
|
|
28
|
+
const receiverConn = receiver.getFirstConnection();
|
|
29
|
+
if (callerConn) {
|
|
30
|
+
caller.setupEventListeners(callerConn);
|
|
31
|
+
}
|
|
32
|
+
if (receiverConn) {
|
|
33
|
+
receiver.setupStreamListeners(receiverConn);
|
|
34
|
+
}
|
|
35
|
+
// Wait for events
|
|
36
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
37
|
+
// Verify reader stream was created
|
|
38
|
+
const readerEvents = caller.getReaderEvents();
|
|
39
|
+
expect(readerEvents.length).to.be.greaterThan(0);
|
|
40
|
+
// Verify receiver identified the reader stream
|
|
41
|
+
expect(receiver.identifiedStreams.length).to.be.greaterThan(0);
|
|
42
|
+
expect(receiver.identifiedStreams[0].role).to.equal('reader');
|
|
43
|
+
});
|
|
44
|
+
it('should send stream-init message with role=reader', async () => {
|
|
45
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
46
|
+
address: new oNodeAddress('o://caller'),
|
|
47
|
+
});
|
|
48
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
49
|
+
address: new oNodeAddress('o://receiver'),
|
|
50
|
+
});
|
|
51
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
52
|
+
await caller.use(receiverAddr, {
|
|
53
|
+
method: 'echo',
|
|
54
|
+
params: { message: 'test' },
|
|
55
|
+
});
|
|
56
|
+
const receiverConn = receiver.getFirstConnection();
|
|
57
|
+
if (receiverConn) {
|
|
58
|
+
receiver.setupStreamListeners(receiverConn);
|
|
59
|
+
}
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
61
|
+
// Receiver should have identified reader stream with correct role
|
|
62
|
+
expect(receiver.identifiedStreams.length).to.be.greaterThan(0);
|
|
63
|
+
const identified = receiver.identifiedStreams.find((s) => s.role === 'reader');
|
|
64
|
+
expect(identified).to.exist;
|
|
65
|
+
});
|
|
66
|
+
it('should start background reader loop', async () => {
|
|
67
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
68
|
+
address: new oNodeAddress('o://caller'),
|
|
69
|
+
});
|
|
70
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
71
|
+
address: new oNodeAddress('o://receiver'),
|
|
72
|
+
});
|
|
73
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
74
|
+
await caller.use(receiverAddr, {
|
|
75
|
+
method: 'echo',
|
|
76
|
+
params: { message: 'test' },
|
|
77
|
+
});
|
|
78
|
+
const callerConn = caller.getFirstConnection();
|
|
79
|
+
if (callerConn) {
|
|
80
|
+
caller.setupEventListeners(callerConn);
|
|
81
|
+
}
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
83
|
+
// Reader started event should be emitted
|
|
84
|
+
const readerStarted = caller
|
|
85
|
+
.getReaderEvents()
|
|
86
|
+
.find((e) => e.type === 'reader-started');
|
|
87
|
+
expect(readerStarted).to.exist;
|
|
88
|
+
});
|
|
89
|
+
it('should auto-initialize on first getOrCreateStream', async () => {
|
|
90
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
91
|
+
address: new oNodeAddress('o://caller'),
|
|
92
|
+
});
|
|
93
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
94
|
+
address: new oNodeAddress('o://receiver'),
|
|
95
|
+
});
|
|
96
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
97
|
+
// First use should trigger auto-initialization
|
|
98
|
+
const response = await caller.use(receiverAddr, {
|
|
99
|
+
method: 'echo',
|
|
100
|
+
params: { message: 'first-request' },
|
|
101
|
+
});
|
|
102
|
+
expect(response.result.success).to.be.true;
|
|
103
|
+
// Verify initialization happened
|
|
104
|
+
const callerConn = caller.getFirstConnection();
|
|
105
|
+
expect(callerConn).to.exist;
|
|
106
|
+
expect(callerConn.streamManager).to.exist;
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe('Stream Reuse', () => {
|
|
110
|
+
it('should reuse outbound streams across multiple requests', async () => {
|
|
111
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
112
|
+
address: new oNodeAddress('o://caller'),
|
|
113
|
+
});
|
|
114
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
115
|
+
address: new oNodeAddress('o://receiver'),
|
|
116
|
+
});
|
|
117
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
118
|
+
// Make multiple requests
|
|
119
|
+
for (let i = 0; i < 5; i++) {
|
|
120
|
+
const response = await caller.use(receiverAddr, {
|
|
121
|
+
method: 'echo',
|
|
122
|
+
params: { message: `request-${i}` },
|
|
123
|
+
});
|
|
124
|
+
expect(response.result.success).to.be.true;
|
|
125
|
+
}
|
|
126
|
+
// All requests should have been processed
|
|
127
|
+
expect(receiver.receivedRequests).to.have.lengthOf(5);
|
|
128
|
+
// Connection should still be active
|
|
129
|
+
const callerConn = caller.getFirstConnection();
|
|
130
|
+
expect(callerConn).to.exist;
|
|
131
|
+
});
|
|
132
|
+
it('should NOT close streams after releaseStream', async () => {
|
|
133
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
134
|
+
address: new oNodeAddress('o://caller'),
|
|
135
|
+
});
|
|
136
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
137
|
+
address: new oNodeAddress('o://receiver'),
|
|
138
|
+
});
|
|
139
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
140
|
+
// Make first request
|
|
141
|
+
await caller.use(receiverAddr, {
|
|
142
|
+
method: 'echo',
|
|
143
|
+
params: { message: 'first' },
|
|
144
|
+
});
|
|
145
|
+
const callerConn = caller.getFirstConnection();
|
|
146
|
+
const streamsBefore = callerConn?.p2pConnection?.streams?.length || 0;
|
|
147
|
+
// Make second request (should reuse stream)
|
|
148
|
+
await caller.use(receiverAddr, {
|
|
149
|
+
method: 'echo',
|
|
150
|
+
params: { message: 'second' },
|
|
151
|
+
});
|
|
152
|
+
const streamsAfter = callerConn?.p2pConnection?.streams?.length || 0;
|
|
153
|
+
// Stream count should not decrease (streams kept open)
|
|
154
|
+
expect(streamsAfter).to.be.greaterThanOrEqual(streamsBefore);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
describe('Cleanup', () => {
|
|
158
|
+
it('should close reader stream on manager close', async () => {
|
|
159
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
160
|
+
address: new oNodeAddress('o://caller'),
|
|
161
|
+
});
|
|
162
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
163
|
+
address: new oNodeAddress('o://receiver'),
|
|
164
|
+
});
|
|
165
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
166
|
+
await caller.use(receiverAddr, {
|
|
167
|
+
method: 'echo',
|
|
168
|
+
params: { message: 'test' },
|
|
169
|
+
});
|
|
170
|
+
const callerConn = caller.getFirstConnection();
|
|
171
|
+
expect(callerConn).to.exist;
|
|
172
|
+
// Close the connection
|
|
173
|
+
await callerConn.close();
|
|
174
|
+
// Verify cleanup (stream manager should be closed)
|
|
175
|
+
// We can't directly check private fields, but connection should be closed
|
|
176
|
+
expect(callerConn.p2pConnection.status).to.equal('closed');
|
|
177
|
+
});
|
|
178
|
+
it('should close outbound stream on manager close', async () => {
|
|
179
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
180
|
+
address: new oNodeAddress('o://caller'),
|
|
181
|
+
});
|
|
182
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
183
|
+
address: new oNodeAddress('o://receiver'),
|
|
184
|
+
});
|
|
185
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
186
|
+
// Create outbound stream
|
|
187
|
+
await caller.use(receiverAddr, {
|
|
188
|
+
method: 'echo',
|
|
189
|
+
params: { message: 'test' },
|
|
190
|
+
});
|
|
191
|
+
const callerConn = caller.getFirstConnection();
|
|
192
|
+
// Stop caller (should close all connections and streams)
|
|
193
|
+
await caller.stop();
|
|
194
|
+
// Connection should be closed
|
|
195
|
+
expect(callerConn.p2pConnection.status).to.equal('closed');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe('Events', () => {
|
|
199
|
+
it('should emit ReaderStarted event', async () => {
|
|
200
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
201
|
+
address: new oNodeAddress('o://caller'),
|
|
202
|
+
});
|
|
203
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
204
|
+
address: new oNodeAddress('o://receiver'),
|
|
205
|
+
});
|
|
206
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
207
|
+
await caller.use(receiverAddr, {
|
|
208
|
+
method: 'echo',
|
|
209
|
+
params: { message: 'test' },
|
|
210
|
+
});
|
|
211
|
+
const callerConn = caller.getFirstConnection();
|
|
212
|
+
if (callerConn) {
|
|
213
|
+
caller.setupEventListeners(callerConn);
|
|
214
|
+
}
|
|
215
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
216
|
+
const events = caller.streamManagerEvents;
|
|
217
|
+
const readerStarted = events.find((e) => e.type === 'reader-started');
|
|
218
|
+
expect(readerStarted).to.exist;
|
|
219
|
+
expect(readerStarted?.data.streamId).to.be.a('string');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader-stream-recovery.spec.d.ts","sourceRoot":"","sources":["../../test/reader-stream-recovery.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import { TestEnvironment } from '@olane/o-node/test/helpers';
|
|
3
|
+
import { oNodeAddress } from '@olane/o-node';
|
|
4
|
+
import { LimitedTestTool, ReceiverTestTool } from './helpers/index.js';
|
|
5
|
+
describe('Reader Stream Recovery', () => {
|
|
6
|
+
const env = new TestEnvironment();
|
|
7
|
+
let caller;
|
|
8
|
+
let receiver;
|
|
9
|
+
afterEach(async () => {
|
|
10
|
+
await env.cleanup();
|
|
11
|
+
});
|
|
12
|
+
describe('Recovery Events', () => {
|
|
13
|
+
it('should emit ReaderStarted on initial creation', async () => {
|
|
14
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
15
|
+
address: new oNodeAddress('o://caller'),
|
|
16
|
+
});
|
|
17
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
18
|
+
address: new oNodeAddress('o://receiver'),
|
|
19
|
+
});
|
|
20
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
21
|
+
await caller.use(receiverAddr, {
|
|
22
|
+
method: 'echo',
|
|
23
|
+
params: { message: 'test' },
|
|
24
|
+
});
|
|
25
|
+
const callerConn = caller.getFirstConnection();
|
|
26
|
+
if (callerConn) {
|
|
27
|
+
caller.setupEventListeners(callerConn);
|
|
28
|
+
}
|
|
29
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
30
|
+
const readerEvents = caller.getReaderEvents();
|
|
31
|
+
const startedEvent = readerEvents.find((e) => e.type === 'reader-started');
|
|
32
|
+
expect(startedEvent).to.exist;
|
|
33
|
+
expect(startedEvent?.data).to.have.property('streamId');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe('Graceful Degradation', () => {
|
|
37
|
+
it('should continue processing caller->receiver requests even if reader fails', async () => {
|
|
38
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
39
|
+
address: new oNodeAddress('o://caller'),
|
|
40
|
+
});
|
|
41
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
42
|
+
address: new oNodeAddress('o://receiver'),
|
|
43
|
+
});
|
|
44
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
45
|
+
// Establish connection
|
|
46
|
+
await caller.use(receiverAddr, {
|
|
47
|
+
method: 'echo',
|
|
48
|
+
params: { message: 'initial' },
|
|
49
|
+
});
|
|
50
|
+
const callerConn = caller.getFirstConnection();
|
|
51
|
+
if (callerConn) {
|
|
52
|
+
caller.setupEventListeners(callerConn);
|
|
53
|
+
}
|
|
54
|
+
// Even if reader stream has issues, outbound requests should still work
|
|
55
|
+
const response = await caller.use(receiverAddr, {
|
|
56
|
+
method: 'echo',
|
|
57
|
+
params: { message: 'after-setup' },
|
|
58
|
+
});
|
|
59
|
+
expect(response.result.success).to.be.true;
|
|
60
|
+
expect(response.result.data.message).to.equal('after-setup');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('Reader Stream Lifecycle', () => {
|
|
64
|
+
it('should create reader stream only once per connection', async () => {
|
|
65
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
66
|
+
address: new oNodeAddress('o://caller'),
|
|
67
|
+
});
|
|
68
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
69
|
+
address: new oNodeAddress('o://receiver'),
|
|
70
|
+
});
|
|
71
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
72
|
+
// Make multiple requests
|
|
73
|
+
for (let i = 0; i < 5; i++) {
|
|
74
|
+
await caller.use(receiverAddr, {
|
|
75
|
+
method: 'echo',
|
|
76
|
+
params: { message: `request-${i}` },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const callerConn = caller.getFirstConnection();
|
|
80
|
+
if (callerConn) {
|
|
81
|
+
caller.setupEventListeners(callerConn);
|
|
82
|
+
}
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
84
|
+
// Should only have one reader-started event
|
|
85
|
+
const readerStartedEvents = caller
|
|
86
|
+
.getReaderEvents()
|
|
87
|
+
.filter((e) => e.type === 'reader-started');
|
|
88
|
+
expect(readerStartedEvents.length).to.be.lessThanOrEqual(1);
|
|
89
|
+
});
|
|
90
|
+
it('should maintain reader stream across many requests', async () => {
|
|
91
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
92
|
+
address: new oNodeAddress('o://caller'),
|
|
93
|
+
});
|
|
94
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
95
|
+
address: new oNodeAddress('o://receiver'),
|
|
96
|
+
});
|
|
97
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
98
|
+
const callerAddr = new oNodeAddress(caller.address.toString(), caller.address.libp2pTransports);
|
|
99
|
+
// Establish connection
|
|
100
|
+
await caller.use(receiverAddr, {
|
|
101
|
+
method: 'echo',
|
|
102
|
+
params: { message: 'initial' },
|
|
103
|
+
});
|
|
104
|
+
const callerConn = caller.getFirstConnection();
|
|
105
|
+
const receiverConn = receiver.getFirstConnection();
|
|
106
|
+
if (callerConn) {
|
|
107
|
+
caller.setupEventListeners(callerConn);
|
|
108
|
+
}
|
|
109
|
+
if (receiverConn) {
|
|
110
|
+
receiver.setupStreamListeners(receiverConn);
|
|
111
|
+
}
|
|
112
|
+
// Wait for reader stream identification
|
|
113
|
+
await env.waitFor(() => receiver.identifiedStreams.length > 0, 5000, 100);
|
|
114
|
+
// Make many bidirectional requests
|
|
115
|
+
for (let i = 0; i < 10; i++) {
|
|
116
|
+
await caller.use(receiverAddr, {
|
|
117
|
+
method: 'echo',
|
|
118
|
+
params: { message: `c2r-${i}` },
|
|
119
|
+
});
|
|
120
|
+
await receiver.use(callerAddr, {
|
|
121
|
+
method: 'echo',
|
|
122
|
+
params: { message: `r2c-${i}` },
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// Reader stream should still be identified
|
|
126
|
+
expect(receiver.identifiedStreams.length).to.be.greaterThan(0);
|
|
127
|
+
expect(receiver.identifiedStreams[0].role).to.equal('reader');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('Error Handling', () => {
|
|
131
|
+
it('should not crash if receiver closes connection during communication', async () => {
|
|
132
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
133
|
+
address: new oNodeAddress('o://caller'),
|
|
134
|
+
});
|
|
135
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
136
|
+
address: new oNodeAddress('o://receiver'),
|
|
137
|
+
});
|
|
138
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
139
|
+
// Establish connection
|
|
140
|
+
await caller.use(receiverAddr, {
|
|
141
|
+
method: 'echo',
|
|
142
|
+
params: { message: 'initial' },
|
|
143
|
+
});
|
|
144
|
+
// Close receiver connection
|
|
145
|
+
const receiverConn = receiver.getFirstConnection();
|
|
146
|
+
if (receiverConn) {
|
|
147
|
+
await receiverConn.close();
|
|
148
|
+
}
|
|
149
|
+
// Caller should handle the error gracefully
|
|
150
|
+
try {
|
|
151
|
+
await caller.use(receiverAddr, {
|
|
152
|
+
method: 'echo',
|
|
153
|
+
params: { message: 'after-close' },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
// Error is expected, but should not crash
|
|
158
|
+
expect(error).to.exist;
|
|
159
|
+
}
|
|
160
|
+
// Caller should still be running
|
|
161
|
+
expect(caller.state).to.equal('started');
|
|
162
|
+
});
|
|
163
|
+
it('should handle concurrent requests during reader stream setup', async () => {
|
|
164
|
+
caller = await env.createNode(LimitedTestTool, {
|
|
165
|
+
address: new oNodeAddress('o://caller'),
|
|
166
|
+
});
|
|
167
|
+
receiver = await env.createNode(ReceiverTestTool, {
|
|
168
|
+
address: new oNodeAddress('o://receiver'),
|
|
169
|
+
});
|
|
170
|
+
const receiverAddr = new oNodeAddress(receiver.address.toString(), receiver.address.libp2pTransports);
|
|
171
|
+
// Make concurrent requests immediately (reader stream might still be setting up)
|
|
172
|
+
const promises = [];
|
|
173
|
+
for (let i = 0; i < 5; i++) {
|
|
174
|
+
promises.push(caller.use(receiverAddr, {
|
|
175
|
+
method: 'echo',
|
|
176
|
+
params: { message: `concurrent-${i}` },
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
const responses = await Promise.all(promises);
|
|
180
|
+
// All should succeed
|
|
181
|
+
responses.forEach((response, i) => {
|
|
182
|
+
expect(response.result.success).to.be.true;
|
|
183
|
+
expect(response.result.data.message).to.equal(`concurrent-${i}`);
|
|
184
|
+
});
|
|
185
|
+
expect(receiver.receivedRequests).to.have.lengthOf(5);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olane/o-client-limited",
|
|
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",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"test": "aegir test",
|
|
20
20
|
"test:node": "aegir test -t node",
|
|
21
21
|
"test:browser": "aegir test -t browser",
|
|
22
|
-
"build": "tsc",
|
|
22
|
+
"build": "rm -rf dist && tsc",
|
|
23
23
|
"deep:clean": "rm -rf node_modules && rm package-lock.json",
|
|
24
24
|
"start:prod": "node dist/index.js",
|
|
25
25
|
"prepublishOnly": "npm run build",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"typescript": "5.4.5"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@olane/o-config": "0.7.
|
|
57
|
-
"@olane/o-core": "0.7.
|
|
58
|
-
"@olane/o-node": "0.7.
|
|
59
|
-
"@olane/o-protocol": "0.7.
|
|
60
|
-
"@olane/o-tool": "0.7.
|
|
56
|
+
"@olane/o-config": "0.7.52",
|
|
57
|
+
"@olane/o-core": "0.7.52",
|
|
58
|
+
"@olane/o-node": "0.7.52",
|
|
59
|
+
"@olane/o-protocol": "0.7.52",
|
|
60
|
+
"@olane/o-tool": "0.7.52",
|
|
61
61
|
"debug": "^4.4.1",
|
|
62
62
|
"dotenv": "^16.5.0"
|
|
63
63
|
},
|
|
64
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "706ee0d5b546e96b6c05270b39035ee7e08dadbd"
|
|
65
65
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=configuration.spec.d.ts.map
|