@olane/o-node 0.7.55 → 0.7.57
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/enums/o-node-message-event.d.ts +14 -0
- package/dist/src/connection/enums/o-node-message-event.d.ts.map +1 -0
- package/dist/src/connection/enums/o-node-message-event.js +5 -0
- package/dist/src/connection/index.d.ts +0 -3
- package/dist/src/connection/index.d.ts.map +1 -1
- package/dist/src/connection/index.js +0 -3
- package/dist/src/connection/interfaces/abort-signal.config.d.ts +5 -0
- package/dist/src/connection/interfaces/abort-signal.config.d.ts.map +1 -0
- package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts +13 -1
- package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts.map +1 -1
- package/dist/src/connection/interfaces/o-node-connection.config.d.ts +18 -5
- package/dist/src/connection/interfaces/o-node-connection.config.d.ts.map +1 -1
- package/dist/src/connection/interfaces/o-node-stream.config.d.ts +3 -11
- package/dist/src/connection/interfaces/o-node-stream.config.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.d.ts +29 -53
- package/dist/src/connection/o-node-connection.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.js +102 -149
- package/dist/src/connection/o-node-connection.manager.d.ts +12 -8
- package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.manager.js +49 -35
- package/dist/src/connection/o-node-stream.d.ts +48 -15
- package/dist/src/connection/o-node-stream.d.ts.map +1 -1
- package/dist/src/connection/o-node-stream.js +143 -31
- package/dist/src/connection/stream-handler.config.d.ts +0 -11
- package/dist/src/connection/stream-handler.config.d.ts.map +1 -1
- package/dist/src/lib/interfaces/o-node-request-manager.config.d.ts +9 -0
- package/dist/src/lib/interfaces/o-node-request-manager.config.d.ts.map +1 -0
- package/dist/src/lib/interfaces/o-node-request-manager.config.js +1 -0
- package/dist/src/lib/o-node-request-manager.d.ts +46 -0
- package/dist/src/lib/o-node-request-manager.d.ts.map +1 -0
- package/dist/src/lib/o-node-request-manager.js +173 -0
- package/dist/src/managers/o-reconnection.manager.d.ts.map +1 -1
- package/dist/src/managers/o-reconnection.manager.js +4 -0
- package/dist/src/managers/o-registration.manager.d.ts.map +1 -1
- package/dist/src/managers/o-registration.manager.js +9 -4
- package/dist/src/o-node.d.ts +6 -7
- package/dist/src/o-node.d.ts.map +1 -1
- package/dist/src/o-node.js +22 -37
- package/dist/src/o-node.tool.d.ts +3 -3
- package/dist/src/o-node.tool.d.ts.map +1 -1
- package/dist/src/o-node.tool.js +28 -56
- package/dist/src/router/o-node.router.d.ts.map +1 -1
- package/dist/src/router/o-node.router.js +4 -2
- package/dist/test/connection-management.spec.js +3 -0
- package/package.json +7 -7
- package/dist/src/connection/interfaces/stream-init-message.d.ts +0 -64
- package/dist/src/connection/interfaces/stream-init-message.d.ts.map +0 -1
- package/dist/src/connection/interfaces/stream-init-message.js +0 -18
- package/dist/src/connection/interfaces/stream-manager.config.d.ts +0 -8
- package/dist/src/connection/interfaces/stream-manager.config.d.ts.map +0 -1
- package/dist/src/connection/o-node-stream.manager.d.ts +0 -210
- package/dist/src/connection/o-node-stream.manager.d.ts.map +0 -1
- package/dist/src/connection/o-node-stream.manager.js +0 -696
- /package/dist/src/connection/interfaces/{stream-manager.config.js → abort-signal.config.js} +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { oConnectionManager } from '@olane/o-core';
|
|
2
2
|
import { oNodeConnection } from './o-node-connection.js';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import { oNodeStream } from './o-node-stream.js';
|
|
3
5
|
/**
|
|
4
6
|
* Manages oNodeConnection instances, reusing connections when possible.
|
|
5
7
|
*/
|
|
@@ -10,10 +12,11 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
10
12
|
/** Single cache of oNodeConnection instances keyed by address */
|
|
11
13
|
this.cachedConnections = new Map();
|
|
12
14
|
this.pendingDialsByAddress = new Map();
|
|
15
|
+
this.eventEmitter = new EventEmitter();
|
|
13
16
|
this.p2pNode = config.p2pNode;
|
|
14
17
|
this.defaultReadTimeoutMs = config.defaultReadTimeoutMs;
|
|
15
18
|
this.defaultDrainTimeoutMs = config.defaultDrainTimeoutMs;
|
|
16
|
-
this.logger.setNamespace(`oNodeConnectionManager[${config.
|
|
19
|
+
this.logger.setNamespace(`oNodeConnectionManager[${config.callerAddress}]`);
|
|
17
20
|
// Set up connection lifecycle listeners for cache management
|
|
18
21
|
this.setupConnectionListeners();
|
|
19
22
|
}
|
|
@@ -54,7 +57,7 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
54
57
|
* @param addressKey - The address key to cache under
|
|
55
58
|
*/
|
|
56
59
|
cacheConnection(conn) {
|
|
57
|
-
this.logger.debug('Caching connection for address:', conn.p2pConnection.id, conn.p2pConnection.direction, conn.nextHopAddress.value, conn.p2pConnection.streams.map((s) => s.protocol).join(', '));
|
|
60
|
+
this.logger.debug('Caching connection for address:', conn.p2pConnection.id, conn.p2pConnection.direction, conn.nextHopAddress.value, conn.p2pConnection.streams.map((s) => s.protocol).join(', '), conn.remotePeerId);
|
|
58
61
|
this.cachedConnections.set(conn.p2pConnection.id, conn);
|
|
59
62
|
}
|
|
60
63
|
/**
|
|
@@ -92,66 +95,63 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
92
95
|
});
|
|
93
96
|
return connection;
|
|
94
97
|
}
|
|
95
|
-
async
|
|
96
|
-
|
|
98
|
+
async createConnection(config) {
|
|
99
|
+
return new oNodeConnection(config);
|
|
100
|
+
}
|
|
101
|
+
async answer(config, stream) {
|
|
102
|
+
const { targetAddress, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, p2pConnection, } = config;
|
|
103
|
+
if (!p2pConnection) {
|
|
104
|
+
throw new Error('Failed to answer connection, p2pConnection is undefined');
|
|
105
|
+
}
|
|
97
106
|
this.logger.debug('Answering connection for address:', {
|
|
98
107
|
address: nextHopAddress?.value,
|
|
99
108
|
connectionId: p2pConnection.id,
|
|
100
109
|
direction: p2pConnection.direction,
|
|
101
|
-
reuse,
|
|
102
110
|
});
|
|
103
111
|
// Check if we already have a cached connection for this address with the same connection id
|
|
104
112
|
const existingConnection = this.cachedConnections.get(p2pConnection.id);
|
|
105
113
|
if (existingConnection) {
|
|
106
|
-
this.logger.debug('Reusing cached connection for answer:', existingConnection.
|
|
114
|
+
this.logger.debug('Reusing cached connection for answer:', existingConnection.id);
|
|
115
|
+
existingConnection.trackStream(new oNodeStream(stream, {
|
|
116
|
+
remoteAddress: nextHopAddress,
|
|
117
|
+
limited: this.config.runOnLimitedConnection,
|
|
118
|
+
}), config);
|
|
107
119
|
return existingConnection;
|
|
108
120
|
}
|
|
109
|
-
const connection =
|
|
121
|
+
const connection = await this.createConnection({
|
|
110
122
|
nextHopAddress: nextHopAddress,
|
|
111
|
-
|
|
112
|
-
p2pConnection: p2pConnection,
|
|
113
|
-
p2pNode: this.p2pNode,
|
|
123
|
+
targetAddress: targetAddress,
|
|
114
124
|
callerAddress: callerAddress,
|
|
115
125
|
readTimeoutMs: readTimeoutMs ?? this.defaultReadTimeoutMs,
|
|
116
126
|
drainTimeoutMs: drainTimeoutMs ?? this.defaultDrainTimeoutMs,
|
|
117
127
|
isStream: config.isStream ?? false,
|
|
118
128
|
abortSignal: config.abortSignal,
|
|
119
129
|
runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
|
|
120
|
-
|
|
130
|
+
p2pConnection: p2pConnection,
|
|
121
131
|
});
|
|
132
|
+
connection.trackStream(new oNodeStream(stream, {
|
|
133
|
+
remoteAddress: nextHopAddress,
|
|
134
|
+
limited: this.config.runOnLimitedConnection,
|
|
135
|
+
}), config);
|
|
122
136
|
// Cache the new connection
|
|
123
137
|
this.cacheConnection(connection);
|
|
124
138
|
return connection;
|
|
125
139
|
}
|
|
126
|
-
getConnectionFromAddress(address) {
|
|
127
|
-
const protocol = address.protocol;
|
|
128
|
-
this.logger.debug('Searching cached connections for protocol:', protocol);
|
|
129
|
-
for (const conn of this.cachedConnections.values()) {
|
|
130
|
-
// if nextHopAddress protocol matches, return conn
|
|
131
|
-
if (conn.nextHopAddress.protocol === protocol) {
|
|
132
|
-
this.logger.debug('local reuse cache found:', protocol);
|
|
133
|
-
return conn;
|
|
134
|
-
}
|
|
135
|
-
// if remote protocols include protocol, return conn
|
|
136
|
-
if (conn.remoteProtocols.includes(protocol)) {
|
|
137
|
-
this.logger.debug('remote reuse cache found', protocol);
|
|
138
|
-
return conn;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
140
|
/**
|
|
144
141
|
* Connect to a given address, reusing oNodeConnection when possible
|
|
145
142
|
* @param config - Connection configuration
|
|
146
143
|
* @returns The connection object
|
|
147
144
|
*/
|
|
148
145
|
async connect(config) {
|
|
149
|
-
const {
|
|
146
|
+
const { targetAddress, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, } = config;
|
|
150
147
|
if (!nextHopAddress) {
|
|
151
148
|
throw new Error('Invalid address passed');
|
|
152
149
|
}
|
|
150
|
+
if (nextHopAddress.libp2pTransports?.length === 0) {
|
|
151
|
+
throw new Error('No transports provided for the address, cannot connect');
|
|
152
|
+
}
|
|
153
153
|
// Check for existing valid cached connection
|
|
154
|
-
const existingConnection = this.
|
|
154
|
+
const existingConnection = this.getCachedConnectionFromAddress(nextHopAddress);
|
|
155
155
|
if (existingConnection) {
|
|
156
156
|
this.logger.debug('Reusing cached connection for address:', existingConnection.p2pConnection.id);
|
|
157
157
|
return existingConnection;
|
|
@@ -159,15 +159,14 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
159
159
|
else {
|
|
160
160
|
this.logger.debug('No cached connection found for address:', {
|
|
161
161
|
address: nextHopAddress.value,
|
|
162
|
+
peerId: nextHopAddress.libp2pTransports?.[0].toPeerId(),
|
|
162
163
|
});
|
|
163
164
|
}
|
|
164
165
|
// Get or create the underlying p2p connection
|
|
165
166
|
const p2pConnection = await this.getOrCreateP2pConnection(nextHopAddress, nextHopAddress.value);
|
|
166
|
-
|
|
167
|
-
const connection = new oNodeConnection({
|
|
167
|
+
const connection = await this.createConnection({
|
|
168
168
|
nextHopAddress: nextHopAddress,
|
|
169
|
-
|
|
170
|
-
p2pNode: this.p2pNode,
|
|
169
|
+
targetAddress: targetAddress,
|
|
171
170
|
p2pConnection: p2pConnection,
|
|
172
171
|
callerAddress: callerAddress,
|
|
173
172
|
readTimeoutMs: readTimeoutMs ?? this.defaultReadTimeoutMs,
|
|
@@ -175,10 +174,25 @@ export class oNodeConnectionManager extends oConnectionManager {
|
|
|
175
174
|
isStream: config.isStream ?? false,
|
|
176
175
|
abortSignal: config.abortSignal,
|
|
177
176
|
runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
|
|
178
|
-
requestHandler: config.requestHandler,
|
|
179
177
|
});
|
|
180
178
|
// Cache the new connection
|
|
181
179
|
this.cacheConnection(connection);
|
|
182
180
|
return connection;
|
|
183
181
|
}
|
|
182
|
+
getCachedConnectionFromAddress(address) {
|
|
183
|
+
const vals = Array.from(this.cachedConnections.values());
|
|
184
|
+
for (let i = 0; i < vals.length; ++i) {
|
|
185
|
+
const c = vals[i];
|
|
186
|
+
const connection = c;
|
|
187
|
+
const peerId = address.libp2pTransports?.[0].toPeerId();
|
|
188
|
+
if (connection.p2pConnection?.remotePeer.toString() === peerId &&
|
|
189
|
+
connection.isOpen) {
|
|
190
|
+
return connection;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
get connectionCount() {
|
|
196
|
+
return this.cachedConnections.size;
|
|
197
|
+
}
|
|
184
198
|
}
|
|
@@ -1,21 +1,53 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
import type { Stream } from '@libp2p/interface';
|
|
2
|
-
import { oObject } from '@olane/o-core';
|
|
3
|
+
import { oObject, oRequest, oResponse } from '@olane/o-core';
|
|
3
4
|
import { oNodeStreamConfig } from './interfaces/o-node-stream.config.js';
|
|
5
|
+
import { LengthPrefixedStream } from '@olane/o-config';
|
|
6
|
+
import { AbortSignalConfig } from './interfaces/abort-signal.config.js';
|
|
7
|
+
import { EventEmitter } from 'events';
|
|
8
|
+
import { oNodeMessageEvent, oNodeMessageEventData } from './enums/o-node-message-event.js';
|
|
4
9
|
/**
|
|
5
|
-
* oNodeStream wraps a libp2p Stream
|
|
6
|
-
* to enable proper stream reuse based on address pairs rather than protocol only.
|
|
7
|
-
*
|
|
8
|
-
* Key features:
|
|
9
|
-
* - Bidirectional cache keys: A↔B === B↔A
|
|
10
|
-
* - Automatic reusability checking
|
|
11
|
-
* - Idle time tracking for cleanup
|
|
10
|
+
* oNodeStream wraps a libp2p Stream and transmits or receives messages across the libp2p streams
|
|
12
11
|
*/
|
|
13
12
|
export declare class oNodeStream extends oObject {
|
|
14
13
|
readonly p2pStream: Stream;
|
|
15
14
|
readonly config: oNodeStreamConfig;
|
|
16
15
|
readonly createdAt: number;
|
|
16
|
+
protected readonly lp: LengthPrefixedStream;
|
|
17
|
+
protected readonly eventEmitter: EventEmitter;
|
|
17
18
|
constructor(p2pStream: Stream, config: oNodeStreamConfig);
|
|
19
|
+
listenForLibp2pEvents(): void;
|
|
18
20
|
validate(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Extracts and parses JSON from various formats including:
|
|
23
|
+
* - Already parsed objects
|
|
24
|
+
* - Plain JSON
|
|
25
|
+
* - Markdown code blocks (```json ... ``` or ``` ... ```)
|
|
26
|
+
* - Mixed content with explanatory text
|
|
27
|
+
* - JSON5 format (trailing commas, comments, unquoted keys, etc.)
|
|
28
|
+
*
|
|
29
|
+
* @param decoded - The decoded string that may contain JSON, or an already parsed object
|
|
30
|
+
* @returns Parsed JSON object
|
|
31
|
+
* @throws Error if JSON parsing fails even with JSON5 fallback
|
|
32
|
+
*/
|
|
33
|
+
protected extractAndParseJSON(decoded: string | any): any;
|
|
34
|
+
send(request: oRequest, options: AbortSignalConfig): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* listen - process every message inbound on the stream and emit it for the connection to bubble up
|
|
37
|
+
* @param options
|
|
38
|
+
*/
|
|
39
|
+
listen(options: AbortSignalConfig): Promise<void>;
|
|
40
|
+
listenOnce(options: AbortSignalConfig): Promise<void>;
|
|
41
|
+
waitForResponse(requestId: string): Promise<oResponse>;
|
|
42
|
+
/**
|
|
43
|
+
* Detects if a decoded message is a request
|
|
44
|
+
* Requests have a 'method' field and no 'result' field
|
|
45
|
+
*/
|
|
46
|
+
isRequest(message: any): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Detects if a decoded message is a response
|
|
49
|
+
*/
|
|
50
|
+
isResponse(message: any): boolean;
|
|
19
51
|
/**
|
|
20
52
|
* Checks if the stream is in a valid state:
|
|
21
53
|
* - Stream status is 'open'
|
|
@@ -29,18 +61,19 @@ export declare class oNodeStream extends oObject {
|
|
|
29
61
|
* Gets the age of the stream in milliseconds
|
|
30
62
|
*/
|
|
31
63
|
get age(): number;
|
|
64
|
+
close(): Promise<void>;
|
|
65
|
+
get id(): string;
|
|
32
66
|
/**
|
|
33
|
-
*
|
|
67
|
+
* Add event listener
|
|
34
68
|
*/
|
|
35
|
-
|
|
69
|
+
on<K extends oNodeMessageEvent>(event: K, listener: (data: oNodeMessageEventData[K]) => void): void;
|
|
36
70
|
/**
|
|
37
|
-
*
|
|
71
|
+
* Remove event listener
|
|
38
72
|
*/
|
|
39
|
-
|
|
73
|
+
off<K extends oNodeMessageEvent>(event: K, listener: (data: oNodeMessageEventData[K]) => void): void;
|
|
40
74
|
/**
|
|
41
|
-
*
|
|
75
|
+
* Emit event
|
|
42
76
|
*/
|
|
43
|
-
|
|
44
|
-
close(): Promise<void>;
|
|
77
|
+
private emit;
|
|
45
78
|
}
|
|
46
79
|
//# sourceMappingURL=o-node-stream.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-stream.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,
|
|
1
|
+
{"version":3,"file":"o-node-stream.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-stream.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAGL,OAAO,EACP,QAAQ,EACR,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAY,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAExE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AAGzC;;GAEG;AACH,qBAAa,WAAY,SAAQ,OAAO;aAMpB,SAAS,EAAE,MAAM;aACjB,MAAM,EAAE,iBAAiB;IAN3C,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,oBAAoB,CAAC;IAC5C,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAsB;gBAGjD,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,iBAAiB;IAQ3C,qBAAqB;IAOrB,QAAQ;IA6BR;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,GAAG,GAAG;IAqCnD,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB;IAUxD;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjD,UAAU,CAAC,OAAO,EAAE,iBAAiB;IAqBrC,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAe5D;;;OAGG;IACH,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAQhC;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO;IAIjC;;;;;;;OAOG;IACH,IAAI,OAAO,IAAI,OAAO,CAMrB;IAED;;OAEG;IACH,IAAI,GAAG,IAAI,MAAM,CAEhB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,IAAI,EAAE,IAAI,MAAM,CAEf;IAED;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,iBAAiB,EAC5B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,GACjD,IAAI;IAIP;;OAEG;IACH,GAAG,CAAC,CAAC,SAAS,iBAAiB,EAC7B,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,GACjD,IAAI;IAIP;;OAEG;IACH,OAAO,CAAC,IAAI;CAMb"}
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
import { oError, oErrorCodes, oObject } from '@olane/o-core';
|
|
1
|
+
import { oError, oErrorCodes, oObject, oResponse, } from '@olane/o-core';
|
|
2
|
+
import { lpStream } from '@olane/o-config';
|
|
3
|
+
import JSON5 from 'json5';
|
|
4
|
+
import { EventEmitter } from 'events';
|
|
5
|
+
import { oNodeMessageEvent, } from './enums/o-node-message-event.js';
|
|
6
|
+
import { oStreamRequest } from './o-stream.request.js';
|
|
2
7
|
/**
|
|
3
|
-
* oNodeStream wraps a libp2p Stream
|
|
4
|
-
* to enable proper stream reuse based on address pairs rather than protocol only.
|
|
5
|
-
*
|
|
6
|
-
* Key features:
|
|
7
|
-
* - Bidirectional cache keys: A↔B === B↔A
|
|
8
|
-
* - Automatic reusability checking
|
|
9
|
-
* - Idle time tracking for cleanup
|
|
8
|
+
* oNodeStream wraps a libp2p Stream and transmits or receives messages across the libp2p streams
|
|
10
9
|
*/
|
|
11
10
|
export class oNodeStream extends oObject {
|
|
12
11
|
constructor(p2pStream, config) {
|
|
13
12
|
super();
|
|
14
13
|
this.p2pStream = p2pStream;
|
|
15
14
|
this.config = config;
|
|
15
|
+
this.eventEmitter = new EventEmitter();
|
|
16
16
|
this.createdAt = Date.now();
|
|
17
|
+
this.lp = lpStream(p2pStream);
|
|
18
|
+
this.listenForLibp2pEvents();
|
|
19
|
+
}
|
|
20
|
+
listenForLibp2pEvents() {
|
|
21
|
+
this.p2pStream.addEventListener('close', () => {
|
|
22
|
+
this.close();
|
|
23
|
+
});
|
|
17
24
|
}
|
|
18
25
|
// callable pattern to disrupt flow if not in valid state
|
|
19
26
|
validate() {
|
|
@@ -30,6 +37,113 @@ export class oNodeStream extends oObject {
|
|
|
30
37
|
throw new oError(oErrorCodes.INVALID_STATE, 'Session is not in a valid state');
|
|
31
38
|
}
|
|
32
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Extracts and parses JSON from various formats including:
|
|
42
|
+
* - Already parsed objects
|
|
43
|
+
* - Plain JSON
|
|
44
|
+
* - Markdown code blocks (```json ... ``` or ``` ... ```)
|
|
45
|
+
* - Mixed content with explanatory text
|
|
46
|
+
* - JSON5 format (trailing commas, comments, unquoted keys, etc.)
|
|
47
|
+
*
|
|
48
|
+
* @param decoded - The decoded string that may contain JSON, or an already parsed object
|
|
49
|
+
* @returns Parsed JSON object
|
|
50
|
+
* @throws Error if JSON parsing fails even with JSON5 fallback
|
|
51
|
+
*/
|
|
52
|
+
extractAndParseJSON(decoded) {
|
|
53
|
+
// If already an object (not a string), return it directly
|
|
54
|
+
if (typeof decoded !== 'string') {
|
|
55
|
+
return decoded;
|
|
56
|
+
}
|
|
57
|
+
let jsonString = decoded.trim();
|
|
58
|
+
// Attempt standard JSON.parse first
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(jsonString);
|
|
61
|
+
}
|
|
62
|
+
catch (jsonError) {
|
|
63
|
+
this.logger.debug('Standard JSON parse failed, trying JSON5', {
|
|
64
|
+
error: jsonError.message,
|
|
65
|
+
position: jsonError.message.match(/position (\d+)/)?.[1],
|
|
66
|
+
preview: jsonString,
|
|
67
|
+
});
|
|
68
|
+
// Fallback to JSON5 for more relaxed parsing
|
|
69
|
+
try {
|
|
70
|
+
return JSON5.parse(jsonString);
|
|
71
|
+
}
|
|
72
|
+
catch (json5Error) {
|
|
73
|
+
// Enhanced error with context
|
|
74
|
+
this.logger.error('JSON5 parse also failed', {
|
|
75
|
+
originalError: jsonError.message,
|
|
76
|
+
json5Error: json5Error.message,
|
|
77
|
+
preview: jsonString.substring(0, 200),
|
|
78
|
+
length: jsonString.length,
|
|
79
|
+
});
|
|
80
|
+
throw new Error(`Failed to parse JSON: ${jsonError.message}\nJSON5 also failed: ${json5Error.message}\nPreview: ${jsonString.substring(0, 200)}${jsonString.length > 200 ? '...' : ''}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async send(request, options) {
|
|
85
|
+
// Ensure stream is valid
|
|
86
|
+
this.validate();
|
|
87
|
+
// Send the request with backpressure handling
|
|
88
|
+
const data = new TextEncoder().encode(request.toString());
|
|
89
|
+
await this.lp.write(data, { signal: options?.abortSignal });
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* listen - process every message inbound on the stream and emit it for the connection to bubble up
|
|
93
|
+
* @param options
|
|
94
|
+
*/
|
|
95
|
+
async listen(options) {
|
|
96
|
+
while (this.isValid && !options?.abortSignal?.aborted) {
|
|
97
|
+
await this.listenOnce(options);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async listenOnce(options) {
|
|
101
|
+
const messageBytes = await this.lp.read({ signal: options?.abortSignal });
|
|
102
|
+
const decoded = new TextDecoder().decode(messageBytes.subarray());
|
|
103
|
+
const message = this.extractAndParseJSON(decoded);
|
|
104
|
+
if (this.isRequest(message)) {
|
|
105
|
+
// package up the request + stream and emit
|
|
106
|
+
const request = new oStreamRequest({
|
|
107
|
+
...message,
|
|
108
|
+
stream: this.p2pStream,
|
|
109
|
+
});
|
|
110
|
+
this.emit(oNodeMessageEvent.request, request);
|
|
111
|
+
}
|
|
112
|
+
else if (this.isResponse(message)) {
|
|
113
|
+
const response = new oResponse({
|
|
114
|
+
...message.result,
|
|
115
|
+
id: message.id,
|
|
116
|
+
});
|
|
117
|
+
this.emit(oNodeMessageEvent.response, response);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async waitForResponse(requestId) {
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
const handler = (data) => {
|
|
123
|
+
if (data.id === requestId) {
|
|
124
|
+
this.off(oNodeMessageEvent.response, handler);
|
|
125
|
+
this.logger.debug('Stream stopped listening for responses due to "waitForResponse", technically should continue if listen was called elsewhere');
|
|
126
|
+
resolve(data);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
this.on(oNodeMessageEvent.response, handler);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Detects if a decoded message is a request
|
|
134
|
+
* Requests have a 'method' field and no 'result' field
|
|
135
|
+
*/
|
|
136
|
+
isRequest(message) {
|
|
137
|
+
return (typeof message?.method === 'string' &&
|
|
138
|
+
message.result === undefined &&
|
|
139
|
+
message.error === undefined);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Detects if a decoded message is a response
|
|
143
|
+
*/
|
|
144
|
+
isResponse(message) {
|
|
145
|
+
return message?.result !== undefined || message?.error !== undefined;
|
|
146
|
+
}
|
|
33
147
|
/**
|
|
34
148
|
* Checks if the stream is in a valid state:
|
|
35
149
|
* - Stream status is 'open'
|
|
@@ -49,30 +163,7 @@ export class oNodeStream extends oObject {
|
|
|
49
163
|
get age() {
|
|
50
164
|
return Date.now() - this.createdAt;
|
|
51
165
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Returns the stream type (defaults to 'general' for backward compatibility)
|
|
54
|
-
*/
|
|
55
|
-
get streamType() {
|
|
56
|
-
return this.config.streamType || 'general';
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Checks if this stream is designated as a dedicated reader
|
|
60
|
-
*/
|
|
61
|
-
get isDedicatedReader() {
|
|
62
|
-
return this.streamType === 'dedicated-reader';
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Checks if this stream is designated for request-response cycles
|
|
66
|
-
*/
|
|
67
|
-
get isRequestResponse() {
|
|
68
|
-
return this.streamType === 'request-response';
|
|
69
|
-
}
|
|
70
166
|
async close() {
|
|
71
|
-
// Don't close if reuse policy is enabled
|
|
72
|
-
if (this.config.reusePolicy === 'reuse') {
|
|
73
|
-
this.logger.debug('Stream reuse enabled, not closing stream');
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
167
|
if (this.p2pStream.status === 'open') {
|
|
77
168
|
try {
|
|
78
169
|
// force the close for now until we can implement a proper close
|
|
@@ -84,4 +175,25 @@ export class oNodeStream extends oObject {
|
|
|
84
175
|
}
|
|
85
176
|
}
|
|
86
177
|
}
|
|
178
|
+
get id() {
|
|
179
|
+
return this.p2pStream?.id;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Add event listener
|
|
183
|
+
*/
|
|
184
|
+
on(event, listener) {
|
|
185
|
+
this.eventEmitter.on(event, listener);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Remove event listener
|
|
189
|
+
*/
|
|
190
|
+
off(event, listener) {
|
|
191
|
+
this.eventEmitter.off(event, listener);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Emit event
|
|
195
|
+
*/
|
|
196
|
+
emit(event, data) {
|
|
197
|
+
this.eventEmitter.emit(event, data);
|
|
198
|
+
}
|
|
87
199
|
}
|
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import type { Stream } from '@libp2p/interface';
|
|
3
|
-
/**
|
|
4
|
-
* Stream reuse policy determines how streams are managed across multiple requests
|
|
5
|
-
*/
|
|
6
|
-
export type StreamReusePolicy = 'none' | 'reuse' | 'pool';
|
|
7
3
|
/**
|
|
8
4
|
* Configuration for StreamHandler behavior
|
|
9
5
|
*/
|
|
10
6
|
export interface StreamHandlerConfig {
|
|
11
|
-
/**
|
|
12
|
-
* Stream reuse policy:
|
|
13
|
-
* - 'none': Create new stream for each request (default)
|
|
14
|
-
* - 'reuse': Reuse existing open streams
|
|
15
|
-
* - 'pool': (Future) Maintain a pool of streams
|
|
16
|
-
*/
|
|
17
|
-
reusePolicy?: StreamReusePolicy;
|
|
18
7
|
/**
|
|
19
8
|
* Timeout in milliseconds to wait for stream drain when buffer is full
|
|
20
9
|
* @default 30000 (30 seconds)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-handler.config.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-handler.config.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD;;GAEG;AACH,MAAM,
|
|
1
|
+
{"version":3,"file":"stream-handler.config.d.ts","sourceRoot":"","sources":["../../../src/connection/stream-handler.config.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IAErB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,mBAAmB,CAAC;CAC7B"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { oNodeAddress } from '../../router/o-node.address.js';
|
|
2
|
+
import { oNodeConnectionManager } from '../../connection/o-node-connection.manager.js';
|
|
3
|
+
import { oNodeRouter } from '../../router/o-node.router.js';
|
|
4
|
+
export interface oNodeRequestManagerConfig {
|
|
5
|
+
callerAddress: oNodeAddress;
|
|
6
|
+
connectionManager: oNodeConnectionManager;
|
|
7
|
+
router: oNodeRouter;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=o-node-request-manager.config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"o-node-request-manager.config.d.ts","sourceRoot":"","sources":["../../../../src/lib/interfaces/o-node-request-manager.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAE5D,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,YAAY,CAAC;IAC5B,iBAAiB,EAAE,sBAAsB,CAAC;IAC1C,MAAM,EAAE,WAAW,CAAC;CACrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { oRequestManager, oResponse, UseOptions } from '@olane/o-core';
|
|
3
|
+
import { oNodeRequestManagerConfig } from './interfaces/o-node-request-manager.config.js';
|
|
4
|
+
import { oNodeConnection, oNodeConnectionConfig, oNodeConnectionManager } from '../connection/index.js';
|
|
5
|
+
import { oNodeRouter } from '../router/o-node.router.js';
|
|
6
|
+
import { oNodeAddress } from '../router/o-node.address.js';
|
|
7
|
+
import { UseDataConfig } from '@olane/o-core/dist/src/core/interfaces/use-data.config.js';
|
|
8
|
+
import { oNode } from '../o-node.js';
|
|
9
|
+
import { Connection, Stream } from '@olane/o-config';
|
|
10
|
+
import { oNodeMessageEvent, oNodeMessageEventData } from '../connection/enums/o-node-message-event.js';
|
|
11
|
+
import { oStreamRequest } from '../connection/o-stream.request.js';
|
|
12
|
+
import { EventEmitter } from 'events';
|
|
13
|
+
import { AbortSignalConfig } from '../connection/interfaces/abort-signal.config.js';
|
|
14
|
+
import { RunResult } from '@olane/o-tool';
|
|
15
|
+
export declare class oNodeRequestManager extends oRequestManager {
|
|
16
|
+
readonly config: oNodeRequestManagerConfig;
|
|
17
|
+
connectionManager: oNodeConnectionManager;
|
|
18
|
+
router: oNodeRouter;
|
|
19
|
+
protected eventEmitter: EventEmitter;
|
|
20
|
+
constructor(config: oNodeRequestManagerConfig);
|
|
21
|
+
translateAddress(address: oNodeAddress, options?: UseOptions, nodeRef?: oNode): Promise<{
|
|
22
|
+
nextHopAddress: oNodeAddress;
|
|
23
|
+
targetAddress: oNodeAddress;
|
|
24
|
+
}>;
|
|
25
|
+
connectToNode(nextHopAddress: oNodeAddress, options: Omit<oNodeConnectionConfig, 'nextHopAddress'>): Promise<oNodeConnection>;
|
|
26
|
+
send(address: oNodeAddress, data: UseDataConfig, options?: UseOptions, nodeRef?: oNode): Promise<oResponse>;
|
|
27
|
+
receiveStream({ connection, stream, }: {
|
|
28
|
+
connection: Connection;
|
|
29
|
+
stream: Stream;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
sendResponse(request: oStreamRequest, result: RunResult): Promise<void>;
|
|
32
|
+
protected listenForMessages(connection: oNodeConnection, options: AbortSignalConfig): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Add event listener
|
|
35
|
+
*/
|
|
36
|
+
on<K extends oNodeMessageEvent>(event: K | string, listener: (data: oNodeMessageEventData[K]) => void): void;
|
|
37
|
+
/**
|
|
38
|
+
* Remove event listener
|
|
39
|
+
*/
|
|
40
|
+
off<K extends oNodeMessageEvent>(event: K | string, listener: (data: oNodeMessageEventData[K]) => void): void;
|
|
41
|
+
/**
|
|
42
|
+
* Emit event
|
|
43
|
+
*/
|
|
44
|
+
private emitAsync;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=o-node-request-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"o-node-request-manager.d.ts","sourceRoot":"","sources":["../../../src/lib/o-node-request-manager.ts"],"names":[],"mappings":";AAAA,OAAO,EAML,eAAe,EACf,SAAS,EAET,UAAU,EACX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,yBAAyB,EAAE,MAAM,+CAA+C,CAAC;AAC1F,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EAEvB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,2DAA2D,CAAC;AAC1F,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACtB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AACpF,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAG1C,qBAAa,mBAAoB,SAAQ,eAAe;IAK1C,QAAQ,CAAC,MAAM,EAAE,yBAAyB;IAJtD,iBAAiB,EAAE,sBAAsB,CAAC;IAC1C,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,CAAC,YAAY,EAAE,YAAY,CAAsB;gBAErC,MAAM,EAAE,yBAAyB;IAMhD,gBAAgB,CACpB,OAAO,EAAE,YAAY,EACrB,OAAO,CAAC,EAAE,UAAU,EACpB,OAAO,CAAC,EAAE,KAAK,GACd,OAAO,CAAC;QAAE,cAAc,EAAE,YAAY,CAAC;QAAC,aAAa,EAAE,YAAY,CAAA;KAAE,CAAC;IAenE,aAAa,CACjB,cAAc,EAAE,YAAY,EAC5B,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,gBAAgB,CAAC,GACrD,OAAO,CAAC,eAAe,CAAC;IAyBrB,IAAI,CACR,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE,aAAa,EACnB,OAAO,CAAC,EAAE,UAAU,EACpB,OAAO,CAAC,EAAE,KAAK,GACd,OAAO,CAAC,SAAS,CAAC;IAgEf,aAAa,CAAC,EAClB,UAAU,EACV,MAAM,GACP,EAAE;QACD,UAAU,EAAE,UAAU,CAAC;QACvB,MAAM,EAAE,MAAM,CAAC;KAChB;IAsBK,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS;cAsB7C,iBAAiB,CAC/B,UAAU,EAAE,eAAe,EAC3B,OAAO,EAAE,iBAAiB;IAyB5B;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,iBAAiB,EAC5B,KAAK,EAAE,CAAC,GAAG,MAAM,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,GACjD,IAAI;IAIP;;OAEG;IACH,GAAG,CAAC,CAAC,SAAS,iBAAiB,EAC7B,KAAK,EAAE,CAAC,GAAG,MAAM,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,GACjD,IAAI;IAIP;;OAEG;YAQW,SAAS;CAcxB"}
|