@olane/o-node 0.7.12-alpha.5 → 0.7.12-alpha.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts +1 -0
  2. package/dist/src/connection/interfaces/o-node-connection-manager.config.d.ts.map +1 -1
  3. package/dist/src/connection/interfaces/o-node-connection.config.d.ts +1 -0
  4. package/dist/src/connection/interfaces/o-node-connection.config.d.ts.map +1 -1
  5. package/dist/src/connection/o-node-connection.d.ts +3 -2
  6. package/dist/src/connection/o-node-connection.d.ts.map +1 -1
  7. package/dist/src/connection/o-node-connection.js +58 -18
  8. package/dist/src/connection/o-node-connection.manager.d.ts +17 -4
  9. package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
  10. package/dist/src/connection/o-node-connection.manager.js +75 -65
  11. package/dist/src/connection/o-stream.request.d.ts +11 -0
  12. package/dist/src/connection/o-stream.request.d.ts.map +1 -0
  13. package/dist/src/connection/o-stream.request.js +7 -0
  14. package/dist/src/index.d.ts +2 -1
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/index.js +2 -1
  17. package/dist/src/interfaces/i-heartbeatable-node.d.ts +49 -0
  18. package/dist/src/interfaces/i-heartbeatable-node.d.ts.map +1 -0
  19. package/dist/src/interfaces/i-heartbeatable-node.js +1 -0
  20. package/dist/src/interfaces/i-reconnectable-node.d.ts +46 -0
  21. package/dist/src/interfaces/i-reconnectable-node.d.ts.map +1 -0
  22. package/dist/src/interfaces/i-reconnectable-node.js +1 -0
  23. package/dist/src/interfaces/o-node.config.d.ts +43 -0
  24. package/dist/src/interfaces/o-node.config.d.ts.map +1 -1
  25. package/dist/src/managers/o-connection-heartbeat.manager.d.ts +63 -0
  26. package/dist/src/managers/o-connection-heartbeat.manager.d.ts.map +1 -0
  27. package/dist/src/managers/o-connection-heartbeat.manager.js +227 -0
  28. package/dist/src/managers/o-reconnection.manager.d.ts +51 -0
  29. package/dist/src/managers/o-reconnection.manager.d.ts.map +1 -0
  30. package/dist/src/managers/o-reconnection.manager.js +266 -0
  31. package/dist/src/o-node.d.ts +30 -2
  32. package/dist/src/o-node.d.ts.map +1 -1
  33. package/dist/src/o-node.js +245 -33
  34. package/dist/src/o-node.notification-manager.d.ts +52 -0
  35. package/dist/src/o-node.notification-manager.d.ts.map +1 -0
  36. package/dist/src/o-node.notification-manager.js +188 -0
  37. package/dist/src/o-node.tool.d.ts.map +1 -1
  38. package/dist/src/o-node.tool.js +27 -22
  39. package/dist/src/router/o-node.router.d.ts +1 -0
  40. package/dist/src/router/o-node.router.d.ts.map +1 -1
  41. package/dist/src/router/o-node.router.js +62 -9
  42. package/dist/src/router/o-node.routing-policy.d.ts.map +1 -1
  43. package/dist/src/router/o-node.routing-policy.js +7 -2
  44. package/dist/src/router/resolvers/o-node.resolver.d.ts.map +1 -1
  45. package/dist/src/router/resolvers/o-node.resolver.js +5 -1
  46. package/dist/src/router/resolvers/o-node.search-resolver.d.ts.map +1 -1
  47. package/dist/src/router/resolvers/o-node.search-resolver.js +34 -9
  48. package/dist/src/utils/index.d.ts +3 -0
  49. package/dist/src/utils/index.d.ts.map +1 -0
  50. package/dist/src/utils/index.js +2 -0
  51. package/dist/src/utils/stream.utils.d.ts +6 -0
  52. package/dist/src/utils/stream.utils.d.ts.map +1 -0
  53. package/dist/src/utils/stream.utils.js +31 -0
  54. package/dist/test/helpers/test-node.tool.d.ts +15 -0
  55. package/dist/test/helpers/test-node.tool.d.ts.map +1 -0
  56. package/dist/test/helpers/test-node.tool.js +27 -0
  57. package/package.json +6 -6
  58. package/dist/src/router/resolvers/o-node.child-resolver.d.ts +0 -11
  59. package/dist/src/router/resolvers/o-node.child-resolver.d.ts.map +0 -1
  60. package/dist/src/router/resolvers/o-node.child-resolver.js +0 -58
@@ -0,0 +1,188 @@
1
+ import { oNotificationManager, NodeConnectedEvent, NodeDisconnectedEvent, NodeDiscoveredEvent, ChildJoinedEvent, ChildLeftEvent, ParentConnectedEvent, ParentDisconnectedEvent, } from '@olane/o-core';
2
+ /**
3
+ * libp2p-specific implementation of oNotificationManager
4
+ * Wraps libp2p events and enriches them with Olane context
5
+ */
6
+ export class oNodeNotificationManager extends oNotificationManager {
7
+ constructor(p2pNode, hierarchyManager, address) {
8
+ super();
9
+ this.p2pNode = p2pNode;
10
+ this.hierarchyManager = hierarchyManager;
11
+ this.address = address;
12
+ }
13
+ /**
14
+ * Wire up libp2p event listeners
15
+ */
16
+ setupListeners() {
17
+ this.logger.debug('Setting up libp2p event listeners...');
18
+ // Peer connection events
19
+ this.p2pNode.addEventListener('peer:connect', this.handlePeerConnect.bind(this));
20
+ // this.p2pNode.addEventListener(
21
+ // 'peer:disconnect',
22
+ // this.handlePeerDisconnect.bind(this),
23
+ // );
24
+ // Peer discovery events
25
+ this.p2pNode.addEventListener('peer:discovery', this.handlePeerDiscovery.bind(this));
26
+ // Connection events
27
+ this.p2pNode.addEventListener('connection:open', this.handleConnectionOpen.bind(this));
28
+ this.p2pNode.addEventListener('connection:close', this.handleConnectionClose.bind(this));
29
+ this.logger.debug('libp2p event listeners configured');
30
+ }
31
+ /**
32
+ * Handle peer connect event from libp2p
33
+ */
34
+ handlePeerConnect(evt) {
35
+ const peerId = evt.detail;
36
+ // this.logger.debug(`Peer connected: ${peerId.toString()}`);
37
+ // Try to resolve peer ID to Olane address
38
+ const nodeAddress = this.peerIdToAddress(peerId.toString());
39
+ if (!nodeAddress) {
40
+ // this.logger.debug(
41
+ // `Could not resolve peer ID ${peerId.toString()} to address`,
42
+ // );
43
+ return;
44
+ }
45
+ // Emit generic node connected event
46
+ this.emit(new NodeConnectedEvent({
47
+ source: this.address,
48
+ nodeAddress,
49
+ connectionMetadata: {
50
+ peerId: peerId.toString(),
51
+ transport: 'libp2p',
52
+ },
53
+ }));
54
+ // Check if this is a child node
55
+ if (this.isChild(nodeAddress)) {
56
+ // this.logger.debug(`Child node connected: ${nodeAddress.toString()}`);
57
+ this.emit(new ChildJoinedEvent({
58
+ source: this.address,
59
+ childAddress: nodeAddress,
60
+ parentAddress: this.address,
61
+ }));
62
+ }
63
+ // Check if this is a parent node
64
+ if (this.isParent(nodeAddress)) {
65
+ // this.logger.debug(`Parent node connected: ${nodeAddress.toString()}`);
66
+ this.emit(new ParentConnectedEvent({
67
+ source: this.address,
68
+ parentAddress: nodeAddress,
69
+ }));
70
+ }
71
+ }
72
+ /**
73
+ * Handle peer disconnect event from libp2p
74
+ */
75
+ handlePeerDisconnect(evt) {
76
+ const peerId = evt.detail;
77
+ // this.logger.debug(`Peer disconnected: ${peerId.toString()}`);
78
+ // Try to resolve peer ID to Olane address
79
+ const nodeAddress = this.peerIdToAddress(peerId.toString());
80
+ if (!nodeAddress) {
81
+ // this.logger.debug(
82
+ // `Could not resolve peer ID ${peerId.toString()} to address`,
83
+ // );
84
+ return;
85
+ }
86
+ // Emit generic node disconnected event
87
+ this.emit(new NodeDisconnectedEvent({
88
+ source: this.address,
89
+ nodeAddress,
90
+ reason: 'peer_disconnected',
91
+ }));
92
+ // Check if this is a child node
93
+ if (this.isChild(nodeAddress)) {
94
+ this.logger.debug(`Child node disconnected: ${nodeAddress.toString()}`);
95
+ this.emit(new ChildLeftEvent({
96
+ source: this.address,
97
+ childAddress: nodeAddress,
98
+ parentAddress: this.address,
99
+ reason: 'peer_disconnected',
100
+ }));
101
+ // Optionally remove from hierarchy (auto-cleanup)
102
+ // this.hierarchyManager.removeChild(nodeAddress);
103
+ }
104
+ // Check if this is a parent node
105
+ if (this.isParent(nodeAddress)) {
106
+ this.logger.debug(`Parent node disconnected: ${nodeAddress.toString()}`);
107
+ this.emit(new ParentDisconnectedEvent({
108
+ source: this.address,
109
+ parentAddress: nodeAddress,
110
+ reason: 'peer_disconnected',
111
+ }));
112
+ }
113
+ }
114
+ /**
115
+ * Handle peer discovery event from libp2p
116
+ */
117
+ handlePeerDiscovery(evt) {
118
+ const peerInfo = evt.detail;
119
+ // this.logger.debug(`Peer discovered: ${peerInfo.id.toString()}`);
120
+ // Try to resolve peer ID to Olane address
121
+ const nodeAddress = this.peerIdToAddress(peerInfo.id.toString());
122
+ if (!nodeAddress) {
123
+ return;
124
+ }
125
+ this.emit(new NodeDiscoveredEvent({
126
+ source: this.address,
127
+ nodeAddress,
128
+ }));
129
+ }
130
+ /**
131
+ * Handle connection open event from libp2p
132
+ */
133
+ handleConnectionOpen(evt) {
134
+ // do nothing for now
135
+ }
136
+ /**
137
+ * Handle connection close event from libp2p
138
+ */
139
+ handleConnectionClose(evt) {
140
+ // do nothing for now
141
+ }
142
+ /**
143
+ * Try to resolve a libp2p peer ID to an Olane address
144
+ * Checks hierarchy manager for known peers
145
+ */
146
+ peerIdToAddress(peerId) {
147
+ // Check children
148
+ for (const child of this.hierarchyManager.children) {
149
+ const childTransports = child.transports;
150
+ for (const transport of childTransports) {
151
+ if (transport.toString().includes(peerId)) {
152
+ return child;
153
+ }
154
+ }
155
+ }
156
+ // Check parents
157
+ for (const parent of this.hierarchyManager.parents) {
158
+ const parentTransports = parent.transports;
159
+ for (const transport of parentTransports) {
160
+ if (transport.toString().includes(peerId)) {
161
+ return parent;
162
+ }
163
+ }
164
+ }
165
+ // Check leaders
166
+ for (const leader of this.hierarchyManager.leaders) {
167
+ const leaderTransports = leader.transports;
168
+ for (const transport of leaderTransports) {
169
+ if (transport.toString().includes(peerId)) {
170
+ return leader;
171
+ }
172
+ }
173
+ }
174
+ return null;
175
+ }
176
+ /**
177
+ * Check if an address is a direct child
178
+ */
179
+ isChild(address) {
180
+ return this.hierarchyManager.children.some((child) => child.toString() === address.toString());
181
+ }
182
+ /**
183
+ * Check if an address is a parent
184
+ */
185
+ isParent(address) {
186
+ return this.hierarchyManager.parents.some((parent) => parent.toString() === address.toString());
187
+ }
188
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"o-node.tool.d.ts","sourceRoot":"","sources":["../../src/o-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EAGR,QAAQ,EAET,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;;AAGrD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAkB;IACzC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAQhC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAW3B,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAiDnE,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAQ9B,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CAa5D"}
1
+ {"version":3,"file":"o-node.tool.d.ts","sourceRoot":"","sources":["../../src/o-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EAGR,QAAQ,EAIT,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;;AAIrD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAkB;IACzC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAUhC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAW3B,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCnE,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAQ9B,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CA2B5D"}
@@ -1,7 +1,8 @@
1
- import { CoreUtils, oAddress, oError, oErrorCodes, oRequest, } from '@olane/o-core';
1
+ import { CoreUtils, oRequest, ResponseBuilder, ChildJoinedEvent, } from '@olane/o-core';
2
2
  import { oTool } from '@olane/o-tool';
3
3
  import { oServerNode } from './nodes/server.node.js';
4
4
  import { oNodeTransport } from './router/o-node.transport.js';
5
+ import { oNodeAddress } from './router/o-node.address.js';
5
6
  /**
6
7
  * oTool is a mixin that extends the base class and implements the oTool interface
7
8
  * @param Base - The base class to extend
@@ -11,8 +12,10 @@ export class oNodeTool extends oTool(oServerNode) {
11
12
  async handleProtocol(address) {
12
13
  this.logger.debug('Handling protocol: ' + address.protocol);
13
14
  await this.p2pNode.handle(address.protocol, this.handleStream.bind(this), {
14
- maxInboundStreams: Infinity,
15
- maxOutboundStreams: Infinity,
15
+ maxInboundStreams: 10000,
16
+ maxOutboundStreams: process.env.MAX_OUTBOUND_STREAMS
17
+ ? parseInt(process.env.MAX_OUTBOUND_STREAMS)
18
+ : 1000,
16
19
  });
17
20
  }
18
21
  async initialize() {
@@ -24,6 +27,7 @@ export class oNodeTool extends oTool(oServerNode) {
24
27
  }
25
28
  }
26
29
  async handleStream(stream, connection) {
30
+ this.logger.debug('Handling connection: ', connection.id);
27
31
  // CRITICAL: Attach message listener immediately to prevent buffer overflow (libp2p v3)
28
32
  // Per libp2p migration guide: "If no message event handler is added, streams will
29
33
  // buffer incoming data until a pre-configured limit is reached, after which the stream will be reset."
@@ -34,26 +38,18 @@ export class oNodeTool extends oTool(oServerNode) {
34
38
  }
35
39
  const requestConfig = await CoreUtils.processStream(event);
36
40
  const request = new oRequest(requestConfig);
37
- let success = true;
38
- const result = await this.execute(request, stream).catch((error) => {
39
- this.logger.error('Error executing tool: ', request.toString(), error, typeof error);
40
- success = false;
41
- const responseError = error instanceof oError
42
- ? error
43
- : new oError(oErrorCodes.UNKNOWN, error.message);
44
- return {
45
- error: responseError.toJSON(),
46
- };
47
- });
48
- if (success) {
49
- this.metrics.successCount++;
41
+ // Use ResponseBuilder with automatic error handling and metrics tracking
42
+ const responseBuilder = ResponseBuilder.create().withMetrics(this.metrics);
43
+ let response;
44
+ try {
45
+ const result = await this.execute(request, stream);
46
+ response = await responseBuilder.build(request, result, null);
50
47
  }
51
- else {
52
- this.metrics.errorCount++;
48
+ catch (error) {
49
+ this.logger.error('Error executing tool: ', request.toString(), error, typeof error);
50
+ response = await responseBuilder.buildError(request, error);
53
51
  }
54
- // compose the response & add the expected connection + request fields
55
- const response = CoreUtils.buildResponse(request, result, result?.error);
56
- // add the request method to the response
52
+ // Send the response
57
53
  await CoreUtils.sendResponse(response, stream);
58
54
  };
59
55
  // Attach listener synchronously before any async operations
@@ -69,8 +65,17 @@ export class oNodeTool extends oTool(oServerNode) {
69
65
  async _tool_child_register(request) {
70
66
  this.logger.debug('Child register: ', request.params);
71
67
  const { address, transports } = request.params;
72
- const childAddress = new oAddress(address, transports.map((t) => new oNodeTransport(t)));
68
+ const childAddress = new oNodeAddress(address, transports.map((t) => new oNodeTransport(t)));
69
+ // Add child to hierarchy
73
70
  this.hierarchyManager.addChild(childAddress);
71
+ // Emit child joined event
72
+ if (this.notificationManager) {
73
+ this.notificationManager.emit(new ChildJoinedEvent({
74
+ source: this.address,
75
+ childAddress,
76
+ parentAddress: this.address,
77
+ }));
78
+ }
74
79
  return {
75
80
  message: `Child node registered with parent! ${childAddress.toString()}`,
76
81
  parentTransports: this.parentTransports.map((t) => t.toString()),
@@ -17,6 +17,7 @@ export declare class oNodeRouter extends oToolRouter {
17
17
  protected forward(address: oNodeAddress, request: oRouterRequest, node: oNode): Promise<any>;
18
18
  /**
19
19
  * Executes a request locally when routing to self.
20
+ * Now uses ResponseBuilder for consistency with useSelf() behavior.
20
21
  */
21
22
  private executeSelfRouting;
22
23
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"o-node.router.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAML,cAAc,EACd,aAAa,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK5C,qBAAa,WAAY,SAAQ,WAAW;IAC1C,OAAO,CAAC,aAAa,CAAqB;;IAO1C;;;;;;;;OAQG;cACa,OAAO,CACrB,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,KAAK,GACV,OAAO,CAAC,GAAG,CAAC;IA0Bf;;OAEG;YACW,kBAAkB;IAgBhC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAS5B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAUhC;;OAEG;YACW,eAAe;IA2B7B;;;OAGG;IACG,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC;IAyB3E;;;OAGG;IACH,UAAU,CAAC,qBAAqB,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO;CAGtE"}
1
+ {"version":3,"file":"o-node.router.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAML,cAAc,EAGd,aAAa,EACd,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAM5C,qBAAa,WAAY,SAAQ,WAAW;IAC1C,OAAO,CAAC,aAAa,CAAqB;;IAO1C;;;;;;;;OAQG;cACa,OAAO,CACrB,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,KAAK,GACV,OAAO,CAAC,GAAG,CAAC;IA6Bf;;;OAGG;YACW,kBAAkB;IA8DhC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAS5B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAWhC;;OAEG;YACW,eAAe;IAsC7B;;;OAGG;IACG,SAAS,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC;IAyB3E;;;OAGG;IACH,UAAU,CAAC,qBAAqB,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO;CAGtE"}
@@ -1,7 +1,7 @@
1
- import { oAddress, oError, oErrorCodes, oRequest, } from '@olane/o-core';
1
+ import { CoreUtils, oAddress, oError, oErrorCodes, oRequest, ResponseBuilder, } from '@olane/o-core';
2
2
  import { oToolRouter } from '@olane/o-tool';
3
- import { oNodeConnection } from '../connection/o-node-connection.js';
4
3
  import { oNodeRoutingPolicy } from './o-node.routing-policy.js';
4
+ import { oStreamRequest } from '../connection/o-stream.request.js';
5
5
  export class oNodeRouter extends oToolRouter {
6
6
  constructor() {
7
7
  super();
@@ -25,6 +25,9 @@ export class oNodeRouter extends oToolRouter {
25
25
  params: request.params,
26
26
  id: request.id,
27
27
  });
28
+ if (request.stream) {
29
+ nextHopRequest.stream = request.stream;
30
+ }
28
31
  // Handle self-routing: execute locally instead of dialing
29
32
  if (this.routingPolicy.isSelfAddress(address, node)) {
30
33
  return this.executeSelfRouting(request, node);
@@ -39,17 +42,55 @@ export class oNodeRouter extends oToolRouter {
39
42
  }
40
43
  /**
41
44
  * Executes a request locally when routing to self.
45
+ * Now uses ResponseBuilder for consistency with useSelf() behavior.
42
46
  */
43
47
  async executeSelfRouting(request, node) {
44
48
  const { payload } = request.params;
45
49
  const params = payload.params;
46
50
  const localRequest = new oRequest({
47
51
  method: payload.method,
48
- params: { ...params },
52
+ params: {
53
+ ...params,
54
+ _connectionId: request.params._connectionId,
55
+ _requestMethod: payload.method,
56
+ },
49
57
  id: request.id,
50
58
  });
51
- const result = await node.execute(localRequest);
52
- return result;
59
+ // Create ResponseBuilder with metrics tracking
60
+ const responseBuilder = ResponseBuilder.create().withMetrics(node.metrics);
61
+ // Handle streaming requests
62
+ const isStream = request.params._isStreaming;
63
+ if (isStream && request.stream) {
64
+ // For streaming, we need to handle the stream chunks
65
+ try {
66
+ const result = await node.execute(localRequest, request.stream);
67
+ const response = await responseBuilder.build(localRequest, result, null, {
68
+ isStream: true,
69
+ });
70
+ // Return unwrapped data for consistency with dialAndTransmit
71
+ return response.result.data;
72
+ }
73
+ catch (error) {
74
+ const errorResponse = await responseBuilder.buildError(localRequest, error, {
75
+ isStream: true,
76
+ });
77
+ // For errors, throw to match remote behavior
78
+ throw responseBuilder.normalizeError(error);
79
+ }
80
+ }
81
+ // Handle non-streaming requests with error handling
82
+ try {
83
+ const result = await node.execute(localRequest);
84
+ const response = await responseBuilder.build(localRequest, result, null);
85
+ // Return unwrapped data to match dialAndTransmit behavior
86
+ return response.result.data;
87
+ }
88
+ catch (error) {
89
+ // Build error response for metrics tracking
90
+ await responseBuilder.buildError(localRequest, error);
91
+ // Then throw the normalized error
92
+ throw responseBuilder.normalizeError(error);
93
+ }
53
94
  }
54
95
  /**
55
96
  * Checks if the next hop is the final destination address.
@@ -65,10 +106,11 @@ export class oNodeRouter extends oToolRouter {
65
106
  unwrapDestinationRequest(request) {
66
107
  const { payload } = request.params;
67
108
  const params = payload.params;
68
- return new oRequest({
109
+ return new oStreamRequest({
69
110
  method: payload.method,
70
111
  params: { ...params },
71
112
  id: request.id,
113
+ stream: request.stream,
72
114
  });
73
115
  }
74
116
  /**
@@ -76,13 +118,24 @@ export class oNodeRouter extends oToolRouter {
76
118
  */
77
119
  async dialAndTransmit(address, request, node) {
78
120
  try {
79
- const connection = await node.p2pNode.dial(address.libp2pTransports.map((t) => t.toMultiaddr()));
80
- const nodeConnection = new oNodeConnection({
81
- p2pConnection: connection,
121
+ const isStream = request.params._isStreaming ||
122
+ request.params.payload?.params?._isStreaming;
123
+ const nodeConnection = await node.connectionManager.connect({
82
124
  nextHopAddress: address,
83
125
  address: node.address,
84
126
  callerAddress: node.address,
127
+ isStream: isStream,
85
128
  });
129
+ if (isStream) {
130
+ const routeRequest = request;
131
+ if (!routeRequest.stream) {
132
+ throw new oError(oErrorCodes.INVALID_REQUEST, 'Stream is required');
133
+ }
134
+ nodeConnection.onChunk(async (response) => {
135
+ CoreUtils.sendStreamResponse(response, routeRequest.stream);
136
+ });
137
+ // allow this to continue as we will tell the transmitter to stream the response and we will intercept via the above listener
138
+ }
86
139
  const response = await nodeConnection.transmit(request);
87
140
  return response.result.data;
88
141
  }
@@ -1 +1 @@
1
- {"version":3,"file":"o-node.routing-policy.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.routing-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAG1C;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,cAAc;IACpD;;;;;;;;;OASG;IACH,iBAAiB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO;IAkB1D;;;;;;;;OAQG;IACH,0BAA0B,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,GACV,aAAa,GAAG,IAAI;CAuBxB"}
1
+ {"version":3,"file":"o-node.routing-policy.d.ts","sourceRoot":"","sources":["../../../src/router/o-node.routing-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAG1C;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,cAAc;IACpD;;;;;;;;;OASG;IACH,iBAAiB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO;IAuB1D;;;;;;;;OAQG;IACH,0BAA0B,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,KAAK,GACV,aAAa,GAAG,IAAI;CAmBxB"}
@@ -17,11 +17,16 @@ export class oNodeRoutingPolicy extends oRoutingPolicy {
17
17
  */
18
18
  isInternalAddress(address, node) {
19
19
  const nodeAddress = address;
20
+ // if we are trying to connect to a parent, it's internal
21
+ if (node.hierarchyManager.parents.some((p) => p.equals(address))) {
22
+ return true;
23
+ }
20
24
  if (nodeAddress.paths.indexOf(oAddress.leader().paths) !== -1 && // if the address has a leader
21
25
  nodeAddress.libp2pTransports?.length > 0) {
22
26
  // transports are provided, let's see if they match our known leaders
23
27
  const isLeaderRef = nodeAddress.toString() === oAddress.leader().toString();
24
- const isOurLeaderRef = node.hierarchyManager.leaders.some((l) => l.equals(nodeAddress));
28
+ const isOurLeaderRef = node.address.equals(nodeAddress) ||
29
+ node.hierarchyManager.leaders.some((l) => l.equals(nodeAddress));
25
30
  return isLeaderRef || isOurLeaderRef;
26
31
  }
27
32
  return true;
@@ -40,7 +45,7 @@ export class oNodeRoutingPolicy extends oRoutingPolicy {
40
45
  const isInternal = this.isInternalAddress(address, node);
41
46
  if (!isInternal) {
42
47
  // external address, so we need to route
43
- this.logger.debug('Address is external, routing...', nodeAddress.toString(), nodeAddress.libp2pTransports.map((t) => t.toString()));
48
+ this.logger.debug('Address is external, routing...', nodeAddress);
44
49
  // route to leader of external OS
45
50
  return {
46
51
  nextHopAddress: new oNodeAddress(oAddress.leader().toString(), nodeAddress.libp2pTransports),
@@ -1 +1 @@
1
- {"version":3,"file":"o-node.resolver.d.ts","sourceRoot":"","sources":["../../../../src/router/resolvers/o-node.resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAEhB,aAAa,EAEd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,qBAAa,aAAc,SAAQ,gBAAgB;IACrC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY;gBAArB,OAAO,EAAE,YAAY;IAIpD,IAAI,mBAAmB,IAAI,aAAa,EAAE,CAEzC;IAEK,OAAO,CAAC,YAAY,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAsC1E"}
1
+ {"version":3,"file":"o-node.resolver.d.ts","sourceRoot":"","sources":["../../../../src/router/resolvers/o-node.resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAEhB,aAAa,EAId,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,qBAAa,aAAc,SAAQ,gBAAgB;IACrC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY;gBAArB,OAAO,EAAE,YAAY;IAIpD,IAAI,mBAAmB,IAAI,aAAa,EAAE,CAEzC;IAEK,OAAO,CAAC,YAAY,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC;CA8C1E"}
@@ -1,4 +1,4 @@
1
- import { oAddressResolver, TransportType, } from '@olane/o-core';
1
+ import { oAddressResolver, oAddress, TransportType, oError, oErrorCodes, } from '@olane/o-core';
2
2
  import { oNodeAddress } from '../o-node.address.js';
3
3
  export class oNodeResolver extends oAddressResolver {
4
4
  constructor(address) {
@@ -32,6 +32,10 @@ export class oNodeResolver extends oAddressResolver {
32
32
  requestOverride: request,
33
33
  };
34
34
  }
35
+ // no child address, and we have already been to the leader, fail
36
+ if (address.toString().indexOf(oAddress.leader().toString()) > -1) {
37
+ throw new oError(oErrorCodes.NOT_FOUND, targetAddress.toString() + ' node not found.');
38
+ }
35
39
  return {
36
40
  nextHopAddress: address,
37
41
  targetAddress: targetAddress,
@@ -1 +1 @@
1
- {"version":3,"file":"o-node.search-resolver.d.ts","sourceRoot":"","sources":["../../../../src/router/resolvers/o-node.search-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,KAAK,EAEL,UAAU,EACV,cAAc,EAEd,aAAa,EAEd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,qBAAa,eAAgB,SAAQ,gBAAgB;IACvC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ;gBAAjB,OAAO,EAAE,QAAQ;IAIhD,IAAI,gBAAgB,IAAI,UAAU,EAAE,CAEnC;IAED;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,IAAI,QAAQ;IAIxC;;;;OAIG;IACH,SAAS,CAAC,eAAe,IAAI,MAAM;IAInC;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,CAAC,OAAO,EAAE,QAAQ,GAAG,GAAG;IAOnD;;;;;;OAMG;IACH,SAAS,CAAC,mBAAmB,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,GAAG,GAAG,EAAE;IASjE;;;;;OAKG;IACH,SAAS,CAAC,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI;IAIlD;;;;;OAKG;IACH,SAAS,CAAC,aAAa,CAAC,MAAM,EAAE,GAAG,GAAG,cAAc,EAAE;IAOtD;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,wBAAwB,CAChC,OAAO,EAAE,QAAQ,EACjB,gBAAgB,EAAE,cAAc,EAAE,EAClC,IAAI,EAAE,KAAK,GACV,cAAc,EAAE;IAgBnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAyCG;IACH,SAAS,CAAC,gBAAgB,CACxB,IAAI,EAAE,KAAK,EACX,qBAAqB,EAAE,QAAQ,EAC/B,YAAY,EAAE,GAAG,GAChB,QAAQ;IAsBL,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAoE/D"}
1
+ {"version":3,"file":"o-node.search-resolver.d.ts","sourceRoot":"","sources":["../../../../src/router/resolvers/o-node.search-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EACR,gBAAgB,EAChB,KAAK,EAEL,UAAU,EACV,cAAc,EAEd,aAAa,EAEd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,qBAAa,eAAgB,SAAQ,gBAAgB;IACvC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ;gBAAjB,OAAO,EAAE,QAAQ;IAIhD,IAAI,gBAAgB,IAAI,UAAU,EAAE,CAEnC;IAED;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,IAAI,QAAQ;IAIxC;;;;OAIG;IACH,SAAS,CAAC,eAAe,IAAI,MAAM;IAInC;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,CAAC,OAAO,EAAE,QAAQ,GAAG,GAAG;IAOnD;;;;;;OAMG;IACH,SAAS,CAAC,mBAAmB,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,GAAG,GAAG,EAAE;IASjE;;;;;OAKG;IACH,SAAS,CAAC,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI;IAIlD;;;;;OAKG;IACH,SAAS,CAAC,aAAa,CAAC,MAAM,EAAE,GAAG,GAAG,cAAc,EAAE;IAOtD;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,wBAAwB,CAChC,OAAO,EAAE,QAAQ,EACjB,gBAAgB,EAAE,cAAc,EAAE,EAClC,IAAI,EAAE,KAAK,GACV,cAAc,EAAE;IAgBnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAyCG;IACH,SAAS,CAAC,gBAAgB,CACxB,IAAI,EAAE,KAAK,EACX,qBAAqB,EAAE,QAAQ,EAC/B,YAAY,EAAE,GAAG,GAChB,QAAQ;IAeL,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAsG/D"}
@@ -1,4 +1,4 @@
1
- import { oAddress, oAddressResolver, oCustomTransport, RestrictedAddresses, } from '@olane/o-core';
1
+ import { NodeState, oAddress, oAddressResolver, oCustomTransport, RestrictedAddresses, } from '@olane/o-core';
2
2
  import { oNodeTransport } from '../o-node.transport.js';
3
3
  /**
4
4
  * Address resolver that searches a registry to find transports for addresses.
@@ -78,7 +78,7 @@ export class oSearchResolver extends oAddressResolver {
78
78
  * @returns The registry address to query
79
79
  */
80
80
  getRegistryAddress() {
81
- return new oAddress(RestrictedAddresses.REGISTRY);
81
+ return new oAddress('o://leader/registry');
82
82
  }
83
83
  /**
84
84
  * Returns the method name to call on the registry.
@@ -201,7 +201,6 @@ export class oSearchResolver extends oAddressResolver {
201
201
  determineNextHop(node, resolvedTargetAddress, searchResult) {
202
202
  // Determine next hop using standard hierarchy logic
203
203
  const nextHopAddress = oAddress.next(node.address, resolvedTargetAddress);
204
- this.logger.debug('determineNextHop with params', 'node.address: ' + node.address.toString(), 'resolvedTargetAddress: ' + resolvedTargetAddress.toString(), 'searchResult.address: ' + searchResult.address, 'next hop: ' + nextHopAddress.toString());
205
204
  // Map transports from search result
206
205
  const targetTransports = this.mapTransports(searchResult);
207
206
  // Set transports on the next hop based on routing logic
@@ -218,13 +217,40 @@ export class oSearchResolver extends oAddressResolver {
218
217
  requestOverride: resolveRequest,
219
218
  };
220
219
  }
221
- // Perform registry search
220
+ if (node.state !== NodeState.RUNNING) {
221
+ return {
222
+ nextHopAddress: address,
223
+ targetAddress: targetAddress,
224
+ requestOverride: resolveRequest,
225
+ };
226
+ }
227
+ // Perform registry search with error handling
222
228
  const searchParams = this.buildSearchParams(address);
223
229
  const registryAddress = this.getRegistryAddress();
224
- const searchResponse = await node.use(registryAddress, {
225
- method: this.getSearchMethod(),
226
- params: searchParams,
227
- });
230
+ let searchResponse;
231
+ try {
232
+ searchResponse = await node.use(registryAddress, {
233
+ method: this.getSearchMethod(),
234
+ params: searchParams,
235
+ });
236
+ }
237
+ catch (error) {
238
+ // Log the error but don't throw - allow fallback resolvers to handle it
239
+ const errorMessage = error instanceof Error ? error.message : String(error);
240
+ // Check if this is a circuit breaker error (fast-fail scenario)
241
+ if (errorMessage.includes('Circuit breaker is OPEN')) {
242
+ this.logger.warn(`Registry search blocked by circuit breaker for ${address.toString()}: ${errorMessage}`);
243
+ }
244
+ else {
245
+ this.logger.error(`Registry search failed for ${address.toString()}: ${errorMessage}`);
246
+ }
247
+ // Return original address without transports, letting next resolver in chain handle it
248
+ return {
249
+ nextHopAddress: address,
250
+ targetAddress: targetAddress,
251
+ requestOverride: resolveRequest,
252
+ };
253
+ }
228
254
  // Filter and select result
229
255
  const filteredResults = this.filterSearchResults(searchResponse.result.data, node);
230
256
  const selectedResult = this.selectResult(filteredResults);
@@ -240,7 +266,6 @@ export class oSearchResolver extends oAddressResolver {
240
266
  const extraParams = address
241
267
  .toString() // o://embeddings-text replace o://embeddings-text = ''
242
268
  .replace(address.toRootAddress().toString(), '');
243
- this.logger.debug('Extra params:', extraParams);
244
269
  // Check if selectedResult.address already contains the complete path
245
270
  // This happens when registry finds via staticAddress - the returned address
246
271
  // is the canonical hierarchical location, so we shouldn't append extraParams
@@ -0,0 +1,3 @@
1
+ export * from './stream.utils.js';
2
+ export * from './network.utils.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './stream.utils.js';
2
+ export * from './network.utils.js';
@@ -0,0 +1,6 @@
1
+ import { Stream } from '@olane/o-config';
2
+ import { oObject, oRequest } from '@olane/o-core';
3
+ export declare class StreamUtils extends oObject {
4
+ static processGenerator(request: oRequest, generator: AsyncGenerator<any>, stream: Stream): Promise<any>;
5
+ }
6
+ //# sourceMappingURL=stream.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.utils.d.ts","sourceRoot":"","sources":["../../../src/utils/stream.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAEL,OAAO,EACP,QAAQ,EAGT,MAAM,eAAe,CAAC;AAEvB,qBAAa,WAAY,SAAQ,OAAO;WAClB,gBAAgB,CAClC,OAAO,EAAE,QAAQ,EACjB,SAAS,EAAE,cAAc,CAAC,GAAG,CAAC,EAC9B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,GAAG,CAAC;CA6BhB"}
@@ -0,0 +1,31 @@
1
+ import { CoreUtils, oObject, ResponseBuilder, } from '@olane/o-core';
2
+ export class StreamUtils extends oObject {
3
+ static async processGenerator(request, generator, stream) {
4
+ const utils = new StreamUtils();
5
+ const responseBuilder = ResponseBuilder.create();
6
+ let aggregatedResult = '';
7
+ try {
8
+ // Send each chunk from the generator
9
+ // result should not be an oResponse, but rather a key value pair dict
10
+ for await (const result of generator) {
11
+ if (result.delta) {
12
+ aggregatedResult += result.delta;
13
+ }
14
+ const chunkResponse = await responseBuilder.buildChunk(request, result);
15
+ await CoreUtils.sendStreamResponse(chunkResponse, stream);
16
+ }
17
+ return {
18
+ message: aggregatedResult,
19
+ };
20
+ }
21
+ catch (error) {
22
+ // If error occurs during streaming, send error response
23
+ const errorResponse = await responseBuilder.buildError(request, error, {
24
+ isStream: true,
25
+ isLast: true,
26
+ });
27
+ await CoreUtils.sendStreamResponse(errorResponse, stream);
28
+ throw error;
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,15 @@
1
+ import { oNodeTool } from '../../src/o-node.tool.js';
2
+ /**
3
+ * Test-only extension of oNodeTool that adds streaming test methods.
4
+ * This class should only be used in test files and is not part of the production code.
5
+ */
6
+ export declare class TestNodeTool extends oNodeTool {
7
+ /**
8
+ * Test method that emits chunks for 10 seconds at 100ms intervals.
9
+ * Used for testing streaming functionality across hierarchical networks.
10
+ *
11
+ * @returns AsyncGenerator that yields 100 chunks over 10 seconds
12
+ */
13
+ _tool_test_stream(): AsyncGenerator<any>;
14
+ }
15
+ //# sourceMappingURL=test-node.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-node.tool.d.ts","sourceRoot":"","sources":["../../../test/helpers/test-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD;;;GAGG;AACH,qBAAa,YAAa,SAAQ,SAAS;IACzC;;;;;OAKG;IACI,iBAAiB,IAAI,cAAc,CAAC,GAAG,CAAC;CAehD"}