@olane/o-node 0.7.12-alpha.5 → 0.7.12-alpha.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts +1 -0
- 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 +1 -0
- package/dist/src/connection/interfaces/o-node-connection.config.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.d.ts +5 -2
- package/dist/src/connection/o-node-connection.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.js +86 -26
- package/dist/src/connection/o-node-connection.manager.d.ts +18 -4
- package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
- package/dist/src/connection/o-node-connection.manager.js +79 -65
- package/dist/src/connection/o-stream.request.d.ts +11 -0
- package/dist/src/connection/o-stream.request.d.ts.map +1 -0
- package/dist/src/connection/o-stream.request.js +7 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/interfaces/i-heartbeatable-node.d.ts +49 -0
- package/dist/src/interfaces/i-heartbeatable-node.d.ts.map +1 -0
- package/dist/src/interfaces/i-heartbeatable-node.js +1 -0
- package/dist/src/interfaces/i-reconnectable-node.d.ts +46 -0
- package/dist/src/interfaces/i-reconnectable-node.d.ts.map +1 -0
- package/dist/src/interfaces/i-reconnectable-node.js +1 -0
- package/dist/src/interfaces/o-node.config.d.ts +43 -0
- package/dist/src/interfaces/o-node.config.d.ts.map +1 -1
- package/dist/src/managers/o-connection-heartbeat.manager.d.ts +63 -0
- package/dist/src/managers/o-connection-heartbeat.manager.d.ts.map +1 -0
- package/dist/src/managers/o-connection-heartbeat.manager.js +227 -0
- package/dist/src/managers/o-reconnection.manager.d.ts +51 -0
- package/dist/src/managers/o-reconnection.manager.d.ts.map +1 -0
- package/dist/src/managers/o-reconnection.manager.js +266 -0
- package/dist/src/o-node.d.ts +30 -2
- package/dist/src/o-node.d.ts.map +1 -1
- package/dist/src/o-node.js +245 -33
- package/dist/src/o-node.notification-manager.d.ts +52 -0
- package/dist/src/o-node.notification-manager.d.ts.map +1 -0
- package/dist/src/o-node.notification-manager.js +188 -0
- package/dist/src/o-node.tool.d.ts.map +1 -1
- package/dist/src/o-node.tool.js +33 -22
- package/dist/src/router/o-node.router.d.ts +1 -0
- package/dist/src/router/o-node.router.d.ts.map +1 -1
- package/dist/src/router/o-node.router.js +62 -9
- package/dist/src/router/o-node.routing-policy.d.ts.map +1 -1
- package/dist/src/router/o-node.routing-policy.js +7 -2
- package/dist/src/router/resolvers/o-node.resolver.d.ts.map +1 -1
- package/dist/src/router/resolvers/o-node.resolver.js +5 -1
- package/dist/src/router/resolvers/o-node.search-resolver.d.ts.map +1 -1
- package/dist/src/router/resolvers/o-node.search-resolver.js +34 -9
- package/dist/src/utils/index.d.ts +3 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +2 -0
- package/dist/src/utils/stream.utils.d.ts +6 -0
- package/dist/src/utils/stream.utils.d.ts.map +1 -0
- package/dist/src/utils/stream.utils.js +31 -0
- package/dist/test/helpers/test-node.tool.d.ts +15 -0
- package/dist/test/helpers/test-node.tool.d.ts.map +1 -0
- package/dist/test/helpers/test-node.tool.js +27 -0
- package/package.json +6 -6
- package/dist/src/router/resolvers/o-node.child-resolver.d.ts +0 -11
- package/dist/src/router/resolvers/o-node.child-resolver.d.ts.map +0 -1
- package/dist/src/router/resolvers/o-node.child-resolver.js +0 -58
|
@@ -2,5 +2,6 @@ import { oConnectionManagerConfig } from '@olane/o-core';
|
|
|
2
2
|
import { Libp2p } from '@olane/o-config';
|
|
3
3
|
export interface oNodeConnectionManagerConfig extends oConnectionManagerConfig {
|
|
4
4
|
p2pNode: Libp2p;
|
|
5
|
+
runOnLimitedConnection?: boolean;
|
|
5
6
|
}
|
|
6
7
|
//# sourceMappingURL=o-node-connection-manager.config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection-manager.config.d.ts","sourceRoot":"","sources":["../../../../src/connection/interfaces/o-node-connection-manager.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,MAAM,WAAW,4BAA6B,SAAQ,wBAAwB;IAC5E,OAAO,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"o-node-connection-manager.config.d.ts","sourceRoot":"","sources":["../../../../src/connection/interfaces/o-node-connection-manager.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,MAAM,WAAW,4BAA6B,SAAQ,wBAAwB;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC"}
|
|
@@ -2,5 +2,6 @@ import { Connection } from '@olane/o-config';
|
|
|
2
2
|
import { oConnectionConfig } from '@olane/o-core';
|
|
3
3
|
export interface oNodeConnectionConfig extends oConnectionConfig {
|
|
4
4
|
p2pConnection: Connection;
|
|
5
|
+
runOnLimitedConnection?: boolean;
|
|
5
6
|
}
|
|
6
7
|
//# sourceMappingURL=o-node-connection.config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection.config.d.ts","sourceRoot":"","sources":["../../../../src/connection/interfaces/o-node-connection.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;IAC9D,aAAa,EAAE,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"o-node-connection.config.d.ts","sourceRoot":"","sources":["../../../../src/connection/interfaces/o-node-connection.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;IAC9D,aAAa,EAAE,UAAU,CAAC;IAC1B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC"}
|
|
@@ -5,9 +5,12 @@ export declare class oNodeConnection extends oConnection {
|
|
|
5
5
|
protected readonly config: oNodeConnectionConfig;
|
|
6
6
|
p2pConnection: Connection;
|
|
7
7
|
constructor(config: oNodeConnectionConfig);
|
|
8
|
-
|
|
9
|
-
validate(): void;
|
|
8
|
+
setupConnectionListeners(): void;
|
|
9
|
+
validate(stream?: Stream): void;
|
|
10
|
+
getOrCreateStream(): Promise<Stream>;
|
|
10
11
|
transmit(request: oRequest): Promise<oResponse>;
|
|
12
|
+
postTransmit(stream: Stream): Promise<void>;
|
|
13
|
+
abort(error: Error): Promise<void>;
|
|
11
14
|
close(): Promise<void>;
|
|
12
15
|
}
|
|
13
16
|
//# sourceMappingURL=o-node-connection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"o-node-connection.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAEL,WAAW,EAGX,QAAQ,EACR,SAAS,EACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AAEjF,qBAAa,eAAgB,SAAQ,WAAW;IAGlC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IAFrD,aAAa,EAAE,UAAU,CAAC;gBAEF,MAAM,EAAE,qBAAqB;IAM5D,wBAAwB;IAYxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IAmBlB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAcpC,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;IA2E/C,YAAY,CAAC,MAAM,EAAE,MAAM;IAU3B,KAAK,CAAC,KAAK,EAAE,KAAK;IAMlB,KAAK;CAKZ"}
|
|
@@ -1,52 +1,97 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { oConnection, oError, oErrorCodes, oResponse, } from '@olane/o-core';
|
|
1
|
+
import { CoreUtils, oConnection, oError, oErrorCodes, oResponse, } from '@olane/o-core';
|
|
3
2
|
export class oNodeConnection extends oConnection {
|
|
4
3
|
constructor(config) {
|
|
5
4
|
super(config);
|
|
6
5
|
this.config = config;
|
|
7
6
|
this.p2pConnection = config.p2pConnection;
|
|
7
|
+
this.setupConnectionListeners();
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
setupConnectionListeners() {
|
|
10
|
+
this.logger.debug('Setting up connection listeners for address: ' +
|
|
11
|
+
this.nextHopAddress.toString());
|
|
12
|
+
this.p2pConnection?.addEventListener('close', () => {
|
|
13
|
+
this.logger.debug('Connection closed for address: ' + this.nextHopAddress.toString());
|
|
13
14
|
});
|
|
14
|
-
const outputObj = output instanceof Uint8ArrayList ? output.subarray() : output;
|
|
15
|
-
const jsonStr = new TextDecoder().decode(outputObj);
|
|
16
|
-
return JSON.parse(jsonStr);
|
|
17
15
|
}
|
|
18
|
-
validate() {
|
|
16
|
+
validate(stream) {
|
|
19
17
|
if (this.config.p2pConnection.status !== 'open') {
|
|
20
18
|
throw new Error('Connection is not valid');
|
|
21
19
|
}
|
|
22
20
|
// do nothing
|
|
21
|
+
if (!stream || (stream.status !== 'open' && stream.status !== 'reset')) {
|
|
22
|
+
throw new oError(oErrorCodes.FAILED_TO_DIAL_TARGET, 'Failed to dial target');
|
|
23
|
+
}
|
|
24
|
+
if (stream.status === 'reset') {
|
|
25
|
+
throw new oError(oErrorCodes.CONNECTION_LIMIT_REACHED, 'Connection limit reached');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async getOrCreateStream() {
|
|
29
|
+
if (this.p2pConnection.status !== 'open') {
|
|
30
|
+
throw new oError(oErrorCodes.INVALID_STATE, 'Connection not open');
|
|
31
|
+
}
|
|
32
|
+
return this.p2pConnection.newStream(this.nextHopAddress.protocol, {
|
|
33
|
+
signal: this.abortSignal,
|
|
34
|
+
maxOutboundStreams: process.env.MAX_OUTBOUND_STREAMS
|
|
35
|
+
? parseInt(process.env.MAX_OUTBOUND_STREAMS)
|
|
36
|
+
: 1000,
|
|
37
|
+
runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
|
|
38
|
+
});
|
|
23
39
|
}
|
|
24
40
|
async transmit(request) {
|
|
25
41
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
runOnLimitedConnection: true, // TODO: should this be configurable?
|
|
29
|
-
});
|
|
30
|
-
if (!stream || (stream.status !== 'open' && stream.status !== 'reset')) {
|
|
31
|
-
throw new oError(oErrorCodes.FAILED_TO_DIAL_TARGET, 'Failed to dial target');
|
|
32
|
-
}
|
|
33
|
-
if (stream.status === 'reset') {
|
|
34
|
-
throw new oError(oErrorCodes.CONNECTION_LIMIT_REACHED, 'Connection limit reached');
|
|
42
|
+
if (this.config.runOnLimitedConnection) {
|
|
43
|
+
this.logger.debug('Running on limited connection...');
|
|
35
44
|
}
|
|
45
|
+
const stream = await this.getOrCreateStream();
|
|
46
|
+
this.validate(stream);
|
|
36
47
|
// Send the data with backpressure handling (libp2p v3 best practice)
|
|
37
48
|
const data = new TextEncoder().encode(request.toString());
|
|
38
49
|
const sent = stream.send(data);
|
|
50
|
+
let lastResponse;
|
|
51
|
+
await new Promise((resolve, reject) => {
|
|
52
|
+
const abortHandler = async () => {
|
|
53
|
+
try {
|
|
54
|
+
await stream.abort(new Error('Request aborted'));
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
// Stream may already be closed
|
|
58
|
+
}
|
|
59
|
+
reject(new Error('Request aborted'));
|
|
60
|
+
};
|
|
61
|
+
// Listen for abort signal
|
|
62
|
+
if (this.abortSignal) {
|
|
63
|
+
this.abortSignal.addEventListener('abort', abortHandler);
|
|
64
|
+
}
|
|
65
|
+
stream.addEventListener('message', async (event) => {
|
|
66
|
+
const response = await CoreUtils.processStreamResponse(event);
|
|
67
|
+
this.emitter.emit('chunk', response);
|
|
68
|
+
// marked as the last chunk let's close
|
|
69
|
+
if (response.result._last || !response.result._isStreaming) {
|
|
70
|
+
lastResponse = response;
|
|
71
|
+
// Clean up abort listener before closing
|
|
72
|
+
if (this.abortSignal) {
|
|
73
|
+
this.abortSignal.removeEventListener('abort', abortHandler);
|
|
74
|
+
}
|
|
75
|
+
resolve(true);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
stream.addEventListener('close', async () => {
|
|
80
|
+
this.logger.debug('Stream closed by remote peer, closing connection...');
|
|
81
|
+
stream.close().catch((error) => {
|
|
82
|
+
this.logger.error('Error closing stream: ', error);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
39
85
|
// If send() returns false, wait for the stream to drain before continuing
|
|
40
86
|
if (!sent) {
|
|
41
87
|
this.logger.debug('Stream buffer full, waiting for drain...');
|
|
42
|
-
await stream.onDrain({
|
|
88
|
+
await stream.onDrain({
|
|
89
|
+
signal: AbortSignal.timeout(this.config.drainTimeoutMs ?? 30000),
|
|
90
|
+
}); // Default: 30 second timeout
|
|
43
91
|
}
|
|
44
|
-
|
|
45
|
-
await
|
|
46
|
-
|
|
47
|
-
const response = new oResponse({
|
|
48
|
-
...res.result,
|
|
49
|
-
});
|
|
92
|
+
// handle cleanup of the stream
|
|
93
|
+
await this.postTransmit(stream);
|
|
94
|
+
const response = oResponse.fromJSON(lastResponse);
|
|
50
95
|
return response;
|
|
51
96
|
}
|
|
52
97
|
catch (error) {
|
|
@@ -56,6 +101,21 @@ export class oNodeConnection extends oConnection {
|
|
|
56
101
|
throw error;
|
|
57
102
|
}
|
|
58
103
|
}
|
|
104
|
+
async postTransmit(stream) {
|
|
105
|
+
if (stream.status === 'open') {
|
|
106
|
+
stream.close().catch((error) => {
|
|
107
|
+
this.logger.error('Error closing stream after transmission: ', error);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.logger.debug('Stream is not open, skipping cleanup');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async abort(error) {
|
|
115
|
+
this.logger.debug('Aborting connection');
|
|
116
|
+
await this.p2pConnection.abort(error);
|
|
117
|
+
this.logger.debug('Connection aborted');
|
|
118
|
+
}
|
|
59
119
|
async close() {
|
|
60
120
|
this.logger.debug('Closing connection');
|
|
61
121
|
await this.p2pConnection.close();
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import { oAddress, oConnectionConfig, oConnectionManager } from '@olane/o-core';
|
|
2
|
-
import {
|
|
2
|
+
import { Connection } from '@olane/o-config';
|
|
3
3
|
import { oNodeConnectionManagerConfig } from './interfaces/o-node-connection-manager.config.js';
|
|
4
4
|
import { oNodeConnection } from './o-node-connection.js';
|
|
5
5
|
export declare class oNodeConnectionManager extends oConnectionManager {
|
|
6
|
+
readonly config: oNodeConnectionManagerConfig;
|
|
6
7
|
private p2pNode;
|
|
8
|
+
private defaultReadTimeoutMs?;
|
|
9
|
+
private defaultDrainTimeoutMs?;
|
|
7
10
|
constructor(config: oNodeConnectionManagerConfig);
|
|
11
|
+
getOrCreateConnection(nextHopAddress: oAddress, address: oAddress): Promise<Connection>;
|
|
8
12
|
/**
|
|
9
|
-
* Connect to a given address
|
|
10
|
-
* @param
|
|
13
|
+
* Connect to a given address, reusing libp2p connections when possible
|
|
14
|
+
* @param config - Connection configuration
|
|
11
15
|
* @returns The connection object
|
|
12
16
|
*/
|
|
13
17
|
connect(config: oConnectionConfig): Promise<oNodeConnection>;
|
|
18
|
+
/**
|
|
19
|
+
* Check if libp2p has an active connection to the target peer
|
|
20
|
+
* @param address - The address to check
|
|
21
|
+
* @returns true if an active connection exists
|
|
22
|
+
*/
|
|
14
23
|
isCached(address: oAddress): boolean;
|
|
15
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Get an existing libp2p connection to the target peer
|
|
26
|
+
* @param address - The address to get a connection for
|
|
27
|
+
* @returns The libp2p Connection object or null if not found
|
|
28
|
+
*/
|
|
29
|
+
getCachedLibp2pConnection(address: oAddress): Connection | null;
|
|
16
30
|
}
|
|
17
31
|
//# sourceMappingURL=o-node-connection.manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node-connection.manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,
|
|
1
|
+
{"version":3,"file":"o-node-connection.manager.d.ts","sourceRoot":"","sources":["../../../src/connection/o-node-connection.manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAU,UAAU,EAAU,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,4BAA4B,EAAE,MAAM,kDAAkD,CAAC;AAEhG,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,qBAAa,sBAAuB,SAAQ,kBAAkB;IAKhD,QAAQ,CAAC,MAAM,EAAE,4BAA4B;IAJzD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,OAAO,CAAC,qBAAqB,CAAC,CAAS;gBAElB,MAAM,EAAE,4BAA4B;IAOnD,qBAAqB,CACzB,cAAc,EAAE,QAAQ,EACxB,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,UAAU,CAAC;IA2BtB;;;;OAIG;IACG,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IA6BlE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO;IA4BpC;;;;OAIG;IACH,yBAAyB,CAAC,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,IAAI;CA0BhE"}
|
|
@@ -3,86 +3,100 @@ import { oNodeConnection } from './o-node-connection.js';
|
|
|
3
3
|
export class oNodeConnectionManager extends oConnectionManager {
|
|
4
4
|
constructor(config) {
|
|
5
5
|
super(config);
|
|
6
|
+
this.config = config;
|
|
6
7
|
this.p2pNode = config.p2pNode;
|
|
8
|
+
this.defaultReadTimeoutMs = config.defaultReadTimeoutMs;
|
|
9
|
+
this.defaultDrainTimeoutMs = config.defaultDrainTimeoutMs;
|
|
10
|
+
}
|
|
11
|
+
async getOrCreateConnection(nextHopAddress, address) {
|
|
12
|
+
// Check if libp2p already has an active connection to this peer
|
|
13
|
+
const existingConnection = this.getCachedLibp2pConnection(nextHopAddress);
|
|
14
|
+
let p2pConnection;
|
|
15
|
+
if (existingConnection && existingConnection.status === 'open') {
|
|
16
|
+
this.logger.debug('Reusing existing libp2p connection for address: ' + address.toString());
|
|
17
|
+
p2pConnection = existingConnection;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
// No existing connection or connection is closed, dial a new one
|
|
21
|
+
this.logger.debug('Dialing new connection for address: ' + address.toString());
|
|
22
|
+
p2pConnection = await this.p2pNode.dial(nextHopAddress.libp2pTransports.map((ma) => ma.toMultiaddr()));
|
|
23
|
+
}
|
|
24
|
+
return p2pConnection;
|
|
7
25
|
}
|
|
8
26
|
/**
|
|
9
|
-
* Connect to a given address
|
|
10
|
-
* @param
|
|
27
|
+
* Connect to a given address, reusing libp2p connections when possible
|
|
28
|
+
* @param config - Connection configuration
|
|
11
29
|
* @returns The connection object
|
|
12
30
|
*/
|
|
13
31
|
async connect(config) {
|
|
14
|
-
const { address, nextHopAddress, callerAddress } = config;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
const { address, nextHopAddress, callerAddress, readTimeoutMs, drainTimeoutMs, } = config;
|
|
33
|
+
const p2pConnection = await this.getOrCreateConnection(nextHopAddress, address);
|
|
34
|
+
const connection = new oNodeConnection({
|
|
35
|
+
nextHopAddress: nextHopAddress,
|
|
36
|
+
address: address,
|
|
37
|
+
p2pConnection: p2pConnection,
|
|
38
|
+
callerAddress: callerAddress,
|
|
39
|
+
readTimeoutMs: readTimeoutMs ?? this.defaultReadTimeoutMs,
|
|
40
|
+
drainTimeoutMs: drainTimeoutMs ?? this.defaultDrainTimeoutMs,
|
|
41
|
+
isStream: config.isStream ?? false,
|
|
42
|
+
abortSignal: config.abortSignal,
|
|
43
|
+
runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
|
|
44
|
+
});
|
|
45
|
+
return connection;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if libp2p has an active connection to the target peer
|
|
49
|
+
* @param address - The address to check
|
|
50
|
+
* @returns true if an active connection exists
|
|
51
|
+
*/
|
|
52
|
+
isCached(address) {
|
|
53
|
+
try {
|
|
54
|
+
const nodeAddress = address;
|
|
55
|
+
if (!nodeAddress.libp2pTransports ||
|
|
56
|
+
nodeAddress.libp2pTransports.length === 0) {
|
|
57
|
+
return false;
|
|
23
58
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
59
|
+
// Extract peer ID from the first transport
|
|
60
|
+
const peerIdString = nodeAddress.libp2pTransports[0].toPeerId();
|
|
61
|
+
if (!peerIdString) {
|
|
62
|
+
return false;
|
|
27
63
|
}
|
|
64
|
+
this.logger.debug('Peer ID string:', peerIdString);
|
|
65
|
+
// the following works since the peer id param is not really required: https://github.com/libp2p/js-libp2p/blob/0bbf5021b53938b2bffcffca6c13c479a95c2a60/packages/libp2p/src/connection-manager/index.ts#L508
|
|
66
|
+
const connections = this.p2pNode.getConnections(peerIdString); // ignore since converting to a proper peer id breaks the browser implementation
|
|
67
|
+
// Check if we have at least one open connection
|
|
68
|
+
return connections.some((conn) => conn.status === 'open');
|
|
28
69
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const MAX_DELAY_MS = 10000; // Cap at 10 seconds
|
|
33
|
-
// first time setup connection with retry logic
|
|
34
|
-
let lastError;
|
|
35
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
36
|
-
try {
|
|
37
|
-
if (attempt > 0) {
|
|
38
|
-
// Calculate exponential backoff delay: 1s, 2s, 4s, 8s (capped at MAX_DELAY_MS)
|
|
39
|
-
const delay = Math.min(BASE_DELAY_MS * Math.pow(2, attempt - 1), MAX_DELAY_MS);
|
|
40
|
-
this.logger.debug(`Retry attempt ${attempt}/${MAX_RETRIES} for ${nextHopAddress.toString()} after ${delay}ms delay`);
|
|
41
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
42
|
-
}
|
|
43
|
-
const p2pConnection = await this.p2pNode.dial(nextHopAddress.libp2pTransports.map((ma) => ma.toMultiaddr()));
|
|
44
|
-
const connection = new oNodeConnection({
|
|
45
|
-
nextHopAddress: nextHopAddress,
|
|
46
|
-
address: address,
|
|
47
|
-
p2pConnection: p2pConnection,
|
|
48
|
-
callerAddress: callerAddress,
|
|
49
|
-
});
|
|
50
|
-
if (attempt > 0) {
|
|
51
|
-
this.logger.info(`Successfully connected to ${nextHopAddress.toString()} on retry attempt ${attempt}`);
|
|
52
|
-
}
|
|
53
|
-
// this.cache.set(nextHopAddress.toString(), connection);
|
|
54
|
-
return connection;
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
lastError = error;
|
|
58
|
-
this.logger.warn(`[${callerAddress?.toString() || 'unknown'}] Connection attempt ${attempt + 1}/${MAX_RETRIES + 1} failed for ${nextHopAddress.toString()}: ${error instanceof Error ? error.message : String(error)}`);
|
|
59
|
-
// Don't retry on the last attempt
|
|
60
|
-
if (attempt === MAX_RETRIES) {
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
this.logger.debug('Error checking cached connection:', error);
|
|
72
|
+
return false;
|
|
64
73
|
}
|
|
65
|
-
// All retries exhausted
|
|
66
|
-
this.logger.error(`[${callerAddress?.toString() || 'unknown'}] Failed to connect after ${MAX_RETRIES + 1} attempts to address! Next hop: ${nextHopAddress} With Address: ${address.toString()}`, lastError);
|
|
67
|
-
throw lastError;
|
|
68
|
-
}
|
|
69
|
-
isCached(address) {
|
|
70
|
-
return this.cache.has(address.toString());
|
|
71
74
|
}
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Get an existing libp2p connection to the target peer
|
|
77
|
+
* @param address - The address to get a connection for
|
|
78
|
+
* @returns The libp2p Connection object or null if not found
|
|
79
|
+
*/
|
|
80
|
+
getCachedLibp2pConnection(address) {
|
|
74
81
|
try {
|
|
75
|
-
const
|
|
76
|
-
if (!
|
|
77
|
-
|
|
82
|
+
const nodeAddress = address;
|
|
83
|
+
if (!nodeAddress.libp2pTransports ||
|
|
84
|
+
nodeAddress.libp2pTransports.length === 0) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
// Extract peer ID from the first transport
|
|
88
|
+
const peerIdString = nodeAddress.libp2pTransports[0].toPeerId();
|
|
89
|
+
if (!peerIdString) {
|
|
90
|
+
return null;
|
|
78
91
|
}
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
const connections = this.p2pNode.getConnections(peerIdString); // ignore since converting to a proper peer id breaks the browser implementation
|
|
93
|
+
// Return the first open connection, or null if none exist
|
|
94
|
+
const openConnection = connections.find((conn) => conn.status === 'open');
|
|
95
|
+
return openConnection || null;
|
|
81
96
|
}
|
|
82
97
|
catch (error) {
|
|
83
|
-
this.
|
|
84
|
-
|
|
98
|
+
this.logger.debug('Error getting cached connection:', error);
|
|
99
|
+
return null;
|
|
85
100
|
}
|
|
86
|
-
return null;
|
|
87
101
|
}
|
|
88
102
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Request, RequestId } from '@olane/o-protocol';
|
|
2
|
+
import { oRequest } from '@olane/o-core';
|
|
3
|
+
import { Stream } from '@olane/o-config';
|
|
4
|
+
export declare class oStreamRequest extends oRequest {
|
|
5
|
+
stream: Stream;
|
|
6
|
+
constructor(config: Request & {
|
|
7
|
+
id: RequestId;
|
|
8
|
+
stream: Stream;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=o-stream.request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"o-stream.request.d.ts","sourceRoot":"","sources":["../../../src/connection/o-stream.request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,qBAAa,cAAe,SAAQ,QAAQ;IAC1C,MAAM,EAAE,MAAM,CAAC;gBACH,MAAM,EAAE,OAAO,GAAG;QAAE,EAAE,EAAE,SAAS,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;CAIhE"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './o-node.js';
|
|
2
|
-
export * from './utils/
|
|
2
|
+
export * from './utils/index.js';
|
|
3
3
|
export * from './o-node.hierarchy-manager.js';
|
|
4
4
|
export * from './interfaces/o-node.config.js';
|
|
5
5
|
export * from './connection/index.js';
|
|
@@ -7,4 +7,5 @@ export * from './o-node.tool.js';
|
|
|
7
7
|
export * from './nodes/index.js';
|
|
8
8
|
export * from './interfaces/o-node.tool-config.js';
|
|
9
9
|
export * from './router/index.js';
|
|
10
|
+
export * from './connection/o-stream.request.js';
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oCAAoC,CAAC;AACnD,cAAc,mBAAmB,CAAC;AAClC,cAAc,kCAAkC,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './o-node.js';
|
|
2
|
-
export * from './utils/
|
|
2
|
+
export * from './utils/index.js';
|
|
3
3
|
export * from './o-node.hierarchy-manager.js';
|
|
4
4
|
export * from './interfaces/o-node.config.js';
|
|
5
5
|
export * from './connection/index.js';
|
|
@@ -7,3 +7,4 @@ export * from './o-node.tool.js';
|
|
|
7
7
|
export * from './nodes/index.js';
|
|
8
8
|
export * from './interfaces/o-node.tool-config.js';
|
|
9
9
|
export * from './router/index.js';
|
|
10
|
+
export * from './connection/o-stream.request.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { oNotificationManager } from '@olane/o-core';
|
|
2
|
+
import { Libp2p } from '@olane/o-config';
|
|
3
|
+
import { oNodeAddress } from '../router/o-node.address.js';
|
|
4
|
+
/**
|
|
5
|
+
* Interface for nodes that support connection heartbeat monitoring.
|
|
6
|
+
* This interface defines the contract that oConnectionHeartbeatManager needs
|
|
7
|
+
* to access live hierarchy state without holding stale references.
|
|
8
|
+
*/
|
|
9
|
+
export interface IHeartbeatableNode {
|
|
10
|
+
/**
|
|
11
|
+
* The node's current address
|
|
12
|
+
*/
|
|
13
|
+
address: oNodeAddress;
|
|
14
|
+
/**
|
|
15
|
+
* The notification manager for emitting heartbeat events
|
|
16
|
+
*/
|
|
17
|
+
notificationManager: oNotificationManager;
|
|
18
|
+
/**
|
|
19
|
+
* The underlying libp2p node for ping operations
|
|
20
|
+
*/
|
|
21
|
+
p2pNode: Libp2p;
|
|
22
|
+
/**
|
|
23
|
+
* The current parent address (with live transport updates)
|
|
24
|
+
* @returns Parent address or null if no parent
|
|
25
|
+
*/
|
|
26
|
+
parent: oNodeAddress | null;
|
|
27
|
+
/**
|
|
28
|
+
* Get the current list of leader addresses
|
|
29
|
+
* @returns Array of leader addresses (empty if this node is the leader)
|
|
30
|
+
*/
|
|
31
|
+
getLeaders(): oNodeAddress[];
|
|
32
|
+
/**
|
|
33
|
+
* Get the current list of parent addresses
|
|
34
|
+
* @returns Array of parent addresses
|
|
35
|
+
*/
|
|
36
|
+
getParents(): oNodeAddress[];
|
|
37
|
+
/**
|
|
38
|
+
* Get the current list of child addresses
|
|
39
|
+
* @returns Array of child addresses
|
|
40
|
+
*/
|
|
41
|
+
getChildren(): oNodeAddress[];
|
|
42
|
+
/**
|
|
43
|
+
* Remove a child from the hierarchy
|
|
44
|
+
* @param childAddress The address of the child to remove
|
|
45
|
+
*/
|
|
46
|
+
removeChild(childAddress: oNodeAddress): void;
|
|
47
|
+
use(param1: any, param2: any): Promise<any>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=i-heartbeatable-node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i-heartbeatable-node.d.ts","sourceRoot":"","sources":["../../../src/interfaces/i-heartbeatable-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,OAAO,EAAE,YAAY,CAAC;IAEtB;;OAEG;IACH,mBAAmB,EAAE,oBAAoB,CAAC;IAE1C;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAE5B;;;OAGG;IACH,UAAU,IAAI,YAAY,EAAE,CAAC;IAE7B;;;OAGG;IACH,UAAU,IAAI,YAAY,EAAE,CAAC;IAE7B;;;OAGG;IACH,WAAW,IAAI,YAAY,EAAE,CAAC;IAE9B;;;OAGG;IACH,WAAW,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAE9C,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;CAC7C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { oAddress, NodeState, oNotificationManager } from '@olane/o-core';
|
|
2
|
+
import { oNodeAddress } from '../router/o-node.address.js';
|
|
3
|
+
import { oNodeConfig } from './o-node.config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Interface for nodes that support reconnection management.
|
|
6
|
+
* This interface defines the contract that oReconnectionManager needs
|
|
7
|
+
* to perform reconnection operations without creating a circular dependency.
|
|
8
|
+
*/
|
|
9
|
+
export interface IReconnectableNode {
|
|
10
|
+
/**
|
|
11
|
+
* The node's configuration
|
|
12
|
+
*/
|
|
13
|
+
config: oNodeConfig;
|
|
14
|
+
/**
|
|
15
|
+
* The node's current address
|
|
16
|
+
*/
|
|
17
|
+
address: oNodeAddress;
|
|
18
|
+
/**
|
|
19
|
+
* The node's current state
|
|
20
|
+
*/
|
|
21
|
+
state: NodeState;
|
|
22
|
+
/**
|
|
23
|
+
* The notification manager for subscribing to events
|
|
24
|
+
*/
|
|
25
|
+
notificationManager: oNotificationManager;
|
|
26
|
+
/**
|
|
27
|
+
* Register with the parent node
|
|
28
|
+
*/
|
|
29
|
+
registerParent(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Register with the leader's global registry
|
|
32
|
+
*/
|
|
33
|
+
register(): Promise<void>;
|
|
34
|
+
useSelf(request?: any): Promise<any>;
|
|
35
|
+
/**
|
|
36
|
+
* Execute a method on another node
|
|
37
|
+
*/
|
|
38
|
+
use(address: oAddress, data?: {
|
|
39
|
+
method?: string;
|
|
40
|
+
params?: {
|
|
41
|
+
[key: string]: any;
|
|
42
|
+
};
|
|
43
|
+
id?: string;
|
|
44
|
+
}): Promise<any>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=i-reconnectable-node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i-reconnectable-node.d.ts","sourceRoot":"","sources":["../../../src/interfaces/i-reconnectable-node.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,SAAS,EACT,oBAAoB,EAErB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,MAAM,EAAE,WAAW,CAAC;IAEpB;;OAEG;IACH,OAAO,EAAE,YAAY,CAAC;IAEtB;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IAEjB;;OAEG;IACH,mBAAmB,EAAE,oBAAoB,CAAC;IAE1C;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;OAEG;IACH,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1B,OAAO,CAAC,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAErC;;OAEG;IACH,GAAG,CACD,OAAO,EAAE,QAAQ,EACjB,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;SAAE,CAAC;QAChC,EAAE,CAAC,EAAE,MAAM,CAAC;KACb,GACA,OAAO,CAAC,GAAG,CAAC,CAAC;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -3,5 +3,48 @@ import { oNodeAddress } from '../router/o-node.address.js';
|
|
|
3
3
|
export interface oNodeConfig extends oCoreConfig {
|
|
4
4
|
leader: oNodeAddress | null;
|
|
5
5
|
parent: oNodeAddress | null;
|
|
6
|
+
/**
|
|
7
|
+
* Connection heartbeat configuration (libp2p-native pings)
|
|
8
|
+
* Detects dead connections via periodic pings using libp2p's ping service
|
|
9
|
+
*/
|
|
10
|
+
connectionHeartbeat?: {
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
intervalMs?: number;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
failureThreshold?: number;
|
|
15
|
+
checkChildren?: boolean;
|
|
16
|
+
checkParent?: boolean;
|
|
17
|
+
checkLeader?: boolean;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Automatic reconnection configuration
|
|
21
|
+
* Handles parent connection failures and attempts to reconnect
|
|
22
|
+
*/
|
|
23
|
+
reconnection?: {
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
maxAttempts?: number;
|
|
26
|
+
baseDelayMs?: number;
|
|
27
|
+
maxDelayMs?: number;
|
|
28
|
+
useLeaderFallback?: boolean;
|
|
29
|
+
parentDiscoveryIntervalMs?: number;
|
|
30
|
+
parentDiscoveryMaxDelayMs?: number;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Connection timeout configuration
|
|
34
|
+
* Controls timeouts for stream read and drain operations in connections
|
|
35
|
+
*/
|
|
36
|
+
connectionTimeouts?: {
|
|
37
|
+
/**
|
|
38
|
+
* Timeout in milliseconds for reading response data from a stream
|
|
39
|
+
* Default: 120000 (2 minutes)
|
|
40
|
+
*/
|
|
41
|
+
readTimeoutMs?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Timeout in milliseconds for waiting for stream buffer to drain when backpressure occurs
|
|
44
|
+
* Default: 30000 (30 seconds)
|
|
45
|
+
*/
|
|
46
|
+
drainTimeoutMs?: number;
|
|
47
|
+
};
|
|
48
|
+
runOnLimitedConnection?: boolean;
|
|
6
49
|
}
|
|
7
50
|
//# sourceMappingURL=o-node.config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"o-node.config.d.ts","sourceRoot":"","sources":["../../../src/interfaces/o-node.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"o-node.config.d.ts","sourceRoot":"","sources":["../../../src/interfaces/o-node.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAE5B;;;OAGG;IACH,mBAAmB,CAAC,EAAE;QACpB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB,CAAC;IAEF;;;OAGG;IACH,YAAY,CAAC,EAAE;QACb,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,yBAAyB,CAAC,EAAE,MAAM,CAAC;QACnC,yBAAyB,CAAC,EAAE,MAAM,CAAC;KACpC,CAAC;IAEF;;;OAGG;IACH,kBAAkB,CAAC,EAAE;QACnB;;;WAGG;QACH,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB;;;WAGG;QACH,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IAEF,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC"}
|