@olane/o-node 0.7.12-alpha.9 → 0.7.12

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 (74) 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 +7 -2
  6. package/dist/src/connection/o-node-connection.d.ts.map +1 -1
  7. package/dist/src/connection/o-node-connection.js +56 -32
  8. package/dist/src/connection/o-node-connection.manager.d.ts +20 -5
  9. package/dist/src/connection/o-node-connection.manager.d.ts.map +1 -1
  10. package/dist/src/connection/o-node-connection.manager.js +95 -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/connection/stream-handler.config.d.ts +46 -0
  15. package/dist/src/connection/stream-handler.config.d.ts.map +1 -0
  16. package/dist/src/connection/stream-handler.config.js +1 -0
  17. package/dist/src/connection/stream-handler.d.ts +98 -0
  18. package/dist/src/connection/stream-handler.d.ts.map +1 -0
  19. package/dist/src/connection/stream-handler.js +310 -0
  20. package/dist/src/index.d.ts +2 -1
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/index.js +2 -1
  23. package/dist/src/interfaces/i-heartbeatable-node.d.ts +49 -0
  24. package/dist/src/interfaces/i-heartbeatable-node.d.ts.map +1 -0
  25. package/dist/src/interfaces/i-heartbeatable-node.js +1 -0
  26. package/dist/src/interfaces/i-reconnectable-node.d.ts +5 -0
  27. package/dist/src/interfaces/i-reconnectable-node.d.ts.map +1 -1
  28. package/dist/src/interfaces/o-node.config.d.ts +16 -22
  29. package/dist/src/interfaces/o-node.config.d.ts.map +1 -1
  30. package/dist/src/managers/o-connection-heartbeat.manager.d.ts +17 -17
  31. package/dist/src/managers/o-connection-heartbeat.manager.d.ts.map +1 -1
  32. package/dist/src/managers/o-connection-heartbeat.manager.js +83 -78
  33. package/dist/src/managers/o-reconnection.manager.d.ts +12 -0
  34. package/dist/src/managers/o-reconnection.manager.d.ts.map +1 -1
  35. package/dist/src/managers/o-reconnection.manager.js +138 -22
  36. package/dist/src/o-node.d.ts +23 -12
  37. package/dist/src/o-node.d.ts.map +1 -1
  38. package/dist/src/o-node.js +227 -72
  39. package/dist/src/o-node.notification-manager.d.ts.map +1 -1
  40. package/dist/src/o-node.notification-manager.js +17 -12
  41. package/dist/src/o-node.tool.d.ts +1 -0
  42. package/dist/src/o-node.tool.d.ts.map +1 -1
  43. package/dist/src/o-node.tool.js +18 -34
  44. package/dist/src/router/o-node.router.d.ts +1 -0
  45. package/dist/src/router/o-node.router.d.ts.map +1 -1
  46. package/dist/src/router/o-node.router.js +62 -9
  47. package/dist/src/router/o-node.routing-policy.d.ts.map +1 -1
  48. package/dist/src/router/o-node.routing-policy.js +7 -2
  49. package/dist/src/router/resolvers/o-node.resolver.d.ts.map +1 -1
  50. package/dist/src/router/resolvers/o-node.resolver.js +5 -1
  51. package/dist/src/router/resolvers/o-node.search-resolver.d.ts.map +1 -1
  52. package/dist/src/router/resolvers/o-node.search-resolver.js +34 -9
  53. package/dist/src/utils/index.d.ts +3 -0
  54. package/dist/src/utils/index.d.ts.map +1 -0
  55. package/dist/src/utils/index.js +2 -0
  56. package/dist/src/utils/stream.utils.d.ts +6 -0
  57. package/dist/src/utils/stream.utils.d.ts.map +1 -0
  58. package/dist/src/utils/stream.utils.js +31 -0
  59. package/dist/test/helpers/test-node.tool.d.ts +15 -0
  60. package/dist/test/helpers/test-node.tool.d.ts.map +1 -0
  61. package/dist/test/helpers/test-node.tool.js +27 -0
  62. package/package.json +6 -6
  63. package/dist/src/router/resolvers/o-node.child-resolver.d.ts +0 -11
  64. package/dist/src/router/resolvers/o-node.child-resolver.d.ts.map +0 -1
  65. package/dist/src/router/resolvers/o-node.child-resolver.js +0 -58
  66. package/dist/src/utils/leader-request-wrapper.d.ts +0 -45
  67. package/dist/src/utils/leader-request-wrapper.d.ts.map +0 -1
  68. package/dist/src/utils/leader-request-wrapper.js +0 -89
  69. package/dist/test/o-node.spec.d.ts +0 -2
  70. package/dist/test/o-node.spec.d.ts.map +0 -1
  71. package/dist/test/o-node.spec.js +0 -20
  72. package/dist/test/search-resolver.spec.d.ts +0 -2
  73. package/dist/test/search-resolver.spec.d.ts.map +0 -1
  74. package/dist/test/search-resolver.spec.js +0 -693
@@ -1,4 +1,4 @@
1
- import { createNode, defaultLibp2pConfig, } from '@olane/o-config';
1
+ import { createNode, defaultLibp2pConfig, KEEP_ALIVE, } from '@olane/o-config';
2
2
  import { v4 as uuidv4 } from 'uuid';
3
3
  import { oNodeRouter } from './router/o-node.router.js';
4
4
  import { oNodeHierarchyManager } from './o-node.hierarchy-manager.js';
@@ -12,7 +12,6 @@ import { oLeaderResolverFallback } from './router/index.js';
12
12
  import { oNodeNotificationManager } from './o-node.notification-manager.js';
13
13
  import { oConnectionHeartbeatManager } from './managers/o-connection-heartbeat.manager.js';
14
14
  import { oReconnectionManager } from './managers/o-reconnection.manager.js';
15
- import { LeaderRequestWrapper } from './utils/leader-request-wrapper.js';
16
15
  export class oNode extends oToolBase {
17
16
  constructor(config) {
18
17
  super(config);
@@ -103,18 +102,54 @@ export class oNode extends oToolBase {
103
102
  peerId: this.peerId.toString(),
104
103
  },
105
104
  };
106
- await this.use(address, params);
105
+ this.use(address, params).catch((error) => {
106
+ this.logger.error('Failed to unregister from network:', error);
107
+ });
108
+ }
109
+ async setKeepAliveTag(address) {
110
+ if (!address || !address.libp2pTransports?.length) {
111
+ this.logger.warn('Address has no transports, skipping keep alive tag!', address);
112
+ return;
113
+ }
114
+ try {
115
+ const peers = await this.p2pNode.peerStore.all();
116
+ // find the peer that is already indexed rather than building the PeerId from the string value to avoid browser issues
117
+ const peer = peers.find((p) => p.id.toString() === address.libp2pTransports[0].toPeerId().toString());
118
+ if (!peer) {
119
+ this.logger.warn('Peer not found, skipping keep alive tag!', address);
120
+ return;
121
+ }
122
+ await this.p2pNode.peerStore.merge(peer.id, {
123
+ tags: {
124
+ [KEEP_ALIVE]: { value: 100 },
125
+ },
126
+ });
127
+ this.logger.debug('Set keep alive tag for peer:', peer.id.toString(), ' with address:', address.toString());
128
+ }
129
+ catch (error) {
130
+ this.logger.error('Failed to set keep alive tag:', error);
131
+ }
107
132
  }
108
133
  async registerParent() {
109
134
  if (this.type === NodeType.LEADER) {
110
135
  this.logger.debug('Skipping parent registration, node is leader');
111
136
  return;
112
137
  }
138
+ if (!this.parent?.libp2pTransports?.length) {
139
+ this.logger.debug('Parent has no transports, waiting for reconnection & leader ack');
140
+ if (this.parent?.toString() === oAddress.leader().toString()) {
141
+ this.parent.setTransports(this.leader?.libp2pTransports || []);
142
+ }
143
+ else {
144
+ this.logger.debug('Waiting for parent and reconnecting...');
145
+ await this.reconnectionManager?.waitForParentAndReconnect();
146
+ }
147
+ }
113
148
  // if no parent transports, register with the parent to get them
114
149
  // TODO: should we remove the transports check to make this more consistent?
115
- if (this.config.parent && this.config.parent.transports.length === 0) {
116
- this.logger.debug('Registering node with parent...', this.config.parent);
117
- const parentRegistration = await this.use(this.config.parent, {
150
+ if (this.config.parent) {
151
+ this.logger.debug('Registering node with parent...', this.config.parent?.toString());
152
+ await this.use(this.config.parent, {
118
153
  method: 'child_register',
119
154
  params: {
120
155
  address: this.address.toString(),
@@ -123,11 +158,24 @@ export class oNode extends oToolBase {
123
158
  _token: this.config.joinToken,
124
159
  },
125
160
  });
126
- const { parentTransports } = parentRegistration.result.data;
127
- // update the parent transports
128
- this.config.parent.setTransports(parentTransports.map((t) => new oNodeTransport(t)));
161
+ this.setKeepAliveTag(this.parent);
129
162
  }
130
163
  }
164
+ async registerLeader() {
165
+ const address = oAddress.registry();
166
+ const params = {
167
+ method: 'commit',
168
+ params: {
169
+ peerId: this.peerId.toString(),
170
+ address: this.address.toString(),
171
+ protocols: this.p2pNode.getProtocols(),
172
+ transports: this.transports,
173
+ staticAddress: this.staticAddress.toString(),
174
+ },
175
+ };
176
+ await this.use(address, params);
177
+ this.setKeepAliveTag(this.leader);
178
+ }
131
179
  async register() {
132
180
  if (this.type === NodeType.LEADER) {
133
181
  this.logger.debug('Skipping registration, node is leader');
@@ -148,18 +196,7 @@ export class oNode extends oToolBase {
148
196
  this.logger.debug('Registering node with leader...');
149
197
  }
150
198
  await this.registerParent();
151
- const address = oAddress.registry();
152
- const params = {
153
- method: 'commit',
154
- params: {
155
- peerId: this.peerId.toString(),
156
- address: this.address.toString(),
157
- protocols: this.p2pNode.getProtocols(),
158
- transports: this.transports,
159
- staticAddress: this.staticAddress.toString(),
160
- },
161
- };
162
- await this.use(address, params);
199
+ await this.registerLeader();
163
200
  this.logger.debug('Registration successful');
164
201
  }
165
202
  extractMethod(address) {
@@ -171,11 +208,6 @@ export class oNode extends oToolBase {
171
208
  if (this.connectionHeartbeatManager) {
172
209
  await this.connectionHeartbeatManager.start();
173
210
  }
174
- // await NetworkUtils.advertiseToNetwork(
175
- // this.address,
176
- // this.staticAddress,
177
- // this.p2pNode,
178
- // );
179
211
  }
180
212
  async validateJoinRequest(request) {
181
213
  return true;
@@ -225,12 +257,32 @@ export class oNode extends oToolBase {
225
257
  // ];
226
258
  // // let's make sure we only allow communication through the parent transports
227
259
  params.connectionGater = {
260
+ denyDialPeer: (peerId) => {
261
+ // we can call the leader
262
+ if (this.config.leader?.libp2pTransports.some((t) => t.toPeerId() === peerId.toString())) {
263
+ return false;
264
+ }
265
+ // we can call our parent
266
+ if (this.parentPeerId === peerId.toString()) {
267
+ return false;
268
+ }
269
+ // we can call our children
270
+ if (this.hierarchyManager.children.some((c) => c.libp2pTransports.some((t) => t.toPeerId() === peerId.toString()))) {
271
+ return false;
272
+ }
273
+ // check for standalone node
274
+ if (!this.config.parent && !this.config.leader) {
275
+ return false;
276
+ }
277
+ return true;
278
+ },
228
279
  // who can call us?
229
280
  denyInboundEncryptedConnection: (peerId, maConn) => {
230
281
  // deny all inbound connections unless they are from a parent transport
231
282
  if (this.parentPeerId === peerId.toString()) {
232
283
  return false;
233
284
  }
285
+ // allow connections from children (for ping)
234
286
  if (this.hierarchyManager.children.some((c) => c.libp2pTransports.some((t) => t.toPeerId() === peerId.toString()))) {
235
287
  return false;
236
288
  }
@@ -245,6 +297,13 @@ export class oNode extends oToolBase {
245
297
  ...(this.config.network?.connectionGater || {}),
246
298
  };
247
299
  }
300
+ params.connectionManager = {
301
+ ...(params.connectionManager || {}),
302
+ reconnectRetries: 20,
303
+ reconnectRetryInterval: 2000,
304
+ reconnectBackoffFactor: 1.2,
305
+ maxParallelReconnects: 10,
306
+ };
248
307
  // handle the address encapsulation
249
308
  if (this.config.leader &&
250
309
  !this.address.protocol.includes(this.config.leader.protocol)) {
@@ -258,17 +317,13 @@ export class oNode extends oToolBase {
258
317
  this.p2pNode = await createNode(params);
259
318
  return this.p2pNode;
260
319
  }
261
- async connect(nextHopAddress, targetAddress) {
320
+ async connect(config) {
262
321
  if (!this.connectionManager) {
263
322
  this.logger.error('Connection manager not initialized');
264
323
  throw new Error('Node is not ready to connect to other nodes');
265
324
  }
266
325
  const connection = await this.connectionManager
267
- .connect({
268
- address: targetAddress,
269
- nextHopAddress,
270
- callerAddress: this.address,
271
- })
326
+ .connect(config)
272
327
  .catch((error) => {
273
328
  // TODO: we need to handle this better and document
274
329
  if (error.message === 'Can not dial self') {
@@ -281,6 +336,28 @@ export class oNode extends oToolBase {
281
336
  }
282
337
  return connection;
283
338
  }
339
+ async initConnectionManager() {
340
+ this.connectionManager = new oNodeConnectionManager({
341
+ p2pNode: this.p2pNode,
342
+ defaultReadTimeoutMs: this.config.connectionTimeouts?.readTimeoutMs,
343
+ defaultDrainTimeoutMs: this.config.connectionTimeouts?.drainTimeoutMs,
344
+ runOnLimitedConnection: this.config.runOnLimitedConnection ?? false,
345
+ });
346
+ }
347
+ async hookInitializeFinished() { }
348
+ async hookStartFinished() {
349
+ // Initialize connection health monitor
350
+ this.connectionHeartbeatManager = new oConnectionHeartbeatManager(this, {
351
+ enabled: this.config.connectionHeartbeat?.enabled ?? true,
352
+ intervalMs: this.config.connectionHeartbeat?.intervalMs ?? 15000,
353
+ failureThreshold: this.config.connectionHeartbeat?.failureThreshold ?? 3,
354
+ checkChildren: this.config.connectionHeartbeat?.checkChildren ?? false,
355
+ checkParent: this.config.connectionHeartbeat?.checkParent ?? true,
356
+ checkLeader: true,
357
+ });
358
+ this.logger.info(`Connection heartbeat config: leader=${this.connectionHeartbeatManager.getConfig().checkLeader}, ` +
359
+ `parent=${this.connectionHeartbeatManager.getConfig().checkParent}`);
360
+ }
284
361
  async initialize() {
285
362
  this.logger.debug('Initializing node...');
286
363
  if (this.p2pNode && this.state !== NodeState.STOPPED) {
@@ -297,19 +374,7 @@ export class oNode extends oToolBase {
297
374
  this.address.setTransports(this.transports);
298
375
  this.peerId = this.p2pNode.peerId;
299
376
  // initialize connection manager
300
- this.connectionManager = new oNodeConnectionManager({
301
- p2pNode: this.p2pNode,
302
- });
303
- // Initialize leader request wrapper
304
- this.leaderRequestWrapper = new LeaderRequestWrapper({
305
- enabled: this.config.leaderRetry?.enabled ?? true,
306
- maxAttempts: this.config.leaderRetry?.maxAttempts ?? 20,
307
- baseDelayMs: this.config.leaderRetry?.baseDelayMs ?? 2000,
308
- maxDelayMs: this.config.leaderRetry?.maxDelayMs ?? 30000,
309
- timeoutMs: this.config.leaderRetry?.timeoutMs ?? 10000,
310
- });
311
- this.logger.info(`Leader retry config: enabled=${this.leaderRequestWrapper.getConfig().enabled}, ` +
312
- `maxAttempts=${this.leaderRequestWrapper.getConfig().maxAttempts}`);
377
+ await this.initConnectionManager();
313
378
  // initialize address resolution
314
379
  this.router.addResolver(new oMethodResolver(this.address));
315
380
  this.router.addResolver(new oNodeResolver(this.address));
@@ -318,38 +383,34 @@ export class oNode extends oToolBase {
318
383
  this.logger.debug('Adding leader resolver fallback...');
319
384
  this.router.addResolver(new oLeaderResolverFallback(this.address));
320
385
  }
321
- // Read ENABLE_LEADER_HEARTBEAT environment variable
322
- const enableLeaderHeartbeat = process.env.ENABLE_LEADER_HEARTBEAT === 'true';
323
- // Initialize connection heartbeat manager
324
- this.connectionHeartbeatManager = new oConnectionHeartbeatManager(this.p2pNode, this.hierarchyManager, this.notificationManager, this.address, {
325
- enabled: this.config.connectionHeartbeat?.enabled ?? true,
326
- intervalMs: this.config.connectionHeartbeat?.intervalMs ?? 15000,
327
- timeoutMs: this.config.connectionHeartbeat?.timeoutMs ?? 5000,
328
- failureThreshold: this.config.connectionHeartbeat?.failureThreshold ?? 3,
329
- checkChildren: this.config.connectionHeartbeat?.checkChildren ?? true,
330
- checkParent: this.config.connectionHeartbeat?.checkParent ?? true,
331
- checkLeader: this.config.connectionHeartbeat?.checkLeader ?? enableLeaderHeartbeat,
386
+ this.reconnectionManager = new oReconnectionManager(this, {
387
+ enabled: true,
388
+ maxAttempts: 10,
389
+ baseDelayMs: 5000,
390
+ maxDelayMs: 20000,
391
+ useLeaderFallback: true,
392
+ parentDiscoveryIntervalMs: 5000,
393
+ parentDiscoveryMaxDelayMs: 20000,
332
394
  });
333
- this.logger.info(`Connection heartbeat config: leader=${this.connectionHeartbeatManager.getConfig().checkLeader}, ` +
334
- `parent=${this.connectionHeartbeatManager.getConfig().checkParent}`);
335
- // Initialize reconnection manager
336
- if (this.config.reconnection?.enabled !== false) {
337
- this.reconnectionManager = new oReconnectionManager(this, {
338
- enabled: true,
339
- maxAttempts: this.config.reconnection?.maxAttempts ?? 10,
340
- baseDelayMs: this.config.reconnection?.baseDelayMs ?? 5000,
341
- maxDelayMs: this.config.reconnection?.maxDelayMs ?? 60000,
342
- useLeaderFallback: this.config.reconnection?.useLeaderFallback ?? true,
343
- });
344
- }
395
+ await this.hookInitializeFinished();
345
396
  }
346
397
  /**
347
398
  * Override use() to wrap leader/registry requests with retry logic
348
399
  */
349
- async use(address, data) {
350
- // Wrap leader/registry requests with retry logic
351
- return this.leaderRequestWrapper.execute(() => super.use(address, data), address, data?.method);
352
- }
400
+ // async use(
401
+ // address: oAddress,
402
+ // data?: {
403
+ // method?: string;
404
+ // params?: { [key: string]: any };
405
+ // id?: string;
406
+ // },
407
+ // options?: UseOptions,
408
+ // ): Promise<any> {
409
+ // // Wrap leader/registry requests with retry logic
410
+ // return super.use(address, data, options),
411
+ // address,
412
+ // data?.method,
413
+ // }
353
414
  async teardown() {
354
415
  // Stop heartbeat before parent teardown
355
416
  if (this.connectionHeartbeatManager) {
@@ -361,4 +422,98 @@ export class oNode extends oToolBase {
361
422
  await this.p2pNode.stop();
362
423
  }
363
424
  }
425
+ // IHeartbeatableNode interface methods
426
+ getLeaders() {
427
+ return [this.leader];
428
+ }
429
+ getParents() {
430
+ return this.hierarchyManager.getParents();
431
+ }
432
+ getChildren() {
433
+ return this.hierarchyManager.getChildren();
434
+ }
435
+ removeChild(childAddress) {
436
+ this.hierarchyManager.removeChild(childAddress);
437
+ }
438
+ /**
439
+ * Get the total number of active streams across all connections
440
+ * @returns Total count of active streams
441
+ */
442
+ getStreamCount() {
443
+ if (!this.p2pNode) {
444
+ return 0;
445
+ }
446
+ const connections = this.p2pNode.getConnections();
447
+ return connections.reduce((count, conn) => {
448
+ return count + (conn.streams?.length || 0);
449
+ }, 0);
450
+ }
451
+ /**
452
+ * Get libp2p metrics for this node
453
+ * Tool method that can be called remotely by monitoring systems
454
+ */
455
+ async _tool_get_libp2p_metrics(request) {
456
+ if (!this.p2pNode) {
457
+ return {
458
+ error: 'libp2p node not available',
459
+ available: false,
460
+ };
461
+ }
462
+ try {
463
+ // Get basic connection stats
464
+ const connections = this.p2pNode.getConnections();
465
+ const peers = await this.p2pNode.peerStore.all();
466
+ const inbound = connections.filter((c) => c.direction === 'inbound').length;
467
+ const outbound = connections.filter((c) => c.direction === 'outbound').length;
468
+ // Get DHT info if available
469
+ const services = this.p2pNode.services;
470
+ const dht = services?.dht;
471
+ const routingTable = dht?.routingTable;
472
+ const kBuckets = routingTable?.kb || null;
473
+ let routingTableSize = 0;
474
+ if (kBuckets) {
475
+ // Handle both array and object-like structures
476
+ if (Array.isArray(kBuckets)) {
477
+ for (const bucket of kBuckets) {
478
+ routingTableSize += bucket.peers?.length || 0;
479
+ }
480
+ }
481
+ else if (typeof kBuckets === 'object') {
482
+ // If it's an object, iterate over its values
483
+ for (const bucket of Object.values(kBuckets)) {
484
+ routingTableSize += bucket?.peers?.length || 0;
485
+ }
486
+ }
487
+ }
488
+ // Calculate total stream count across all connections
489
+ const streamCount = connections.reduce((count, conn) => {
490
+ return count + (conn.streams?.length || 0);
491
+ }, 0);
492
+ return {
493
+ available: true,
494
+ timestamp: Date.now(),
495
+ nodeAddress: this.address.toString(),
496
+ peerCount: peers.length,
497
+ connectionCount: connections.length,
498
+ inboundConnections: inbound,
499
+ outboundConnections: outbound,
500
+ streamCount,
501
+ dhtEnabled: !!dht,
502
+ dhtMode: dht?.clientMode ? 'client' : 'server',
503
+ dhtRoutingTableSize: routingTableSize,
504
+ protocols: Array.from(this.p2pNode.getProtocols()),
505
+ selfPeerId: this.p2pNode.peerId.toString(),
506
+ multiaddrs: this.p2pNode
507
+ .getMultiaddrs()
508
+ .map((ma) => ma.toString()),
509
+ };
510
+ }
511
+ catch (error) {
512
+ return {
513
+ error: `Failed to collect libp2p metrics: ${error.message}`,
514
+ available: false,
515
+ nodeAddress: this.address.toString(),
516
+ };
517
+ }
518
+ }
364
519
  }
@@ -1 +1 @@
1
- {"version":3,"file":"o-node.notification-manager.d.ts","sourceRoot":"","sources":["../../src/o-node.notification-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EACL,oBAAoB,EAQrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAEtE;;;GAGG;AACH,qBAAa,wBAAyB,SAAQ,oBAAoB;IAE9D,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,gBAAgB;IACxB,OAAO,CAAC,OAAO;gBAFP,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,qBAAqB,EACvC,OAAO,EAAE,YAAY;IAK/B;;OAEG;IACH,SAAS,CAAC,cAAc,IAAI,IAAI;IAiBhC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAgDzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAkD5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAmB3B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAK5B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAK7B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAkCvB;;OAEG;IACH,OAAO,CAAC,OAAO;IAMf;;OAEG;IACH,OAAO,CAAC,QAAQ;CAKjB"}
1
+ {"version":3,"file":"o-node.notification-manager.d.ts","sourceRoot":"","sources":["../../src/o-node.notification-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EACL,oBAAoB,EAQrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAEtE;;;GAGG;AACH,qBAAa,wBAAyB,SAAQ,oBAAoB;IAE9D,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,gBAAgB;IACxB,OAAO,CAAC,OAAO;gBAFP,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,qBAAqB,EACvC,OAAO,EAAE,YAAY;IAK/B;;OAEG;IACH,SAAS,CAAC,cAAc,IAAI,IAAI;IAgChC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkDzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAoD5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAmB3B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAI5B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAI7B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAkCvB;;OAEG;IACH,OAAO,CAAC,OAAO;IAMf;;OAEG;IACH,OAAO,CAAC,QAAQ;CAKjB"}
@@ -17,7 +17,10 @@ export class oNodeNotificationManager extends oNotificationManager {
17
17
  this.logger.debug('Setting up libp2p event listeners...');
18
18
  // Peer connection events
19
19
  this.p2pNode.addEventListener('peer:connect', this.handlePeerConnect.bind(this));
20
- this.p2pNode.addEventListener('peer:disconnect', this.handlePeerDisconnect.bind(this));
20
+ // this.p2pNode.addEventListener(
21
+ // 'peer:disconnect',
22
+ // this.handlePeerDisconnect.bind(this),
23
+ // );
21
24
  // Peer discovery events
22
25
  this.p2pNode.addEventListener('peer:discovery', this.handlePeerDiscovery.bind(this));
23
26
  // Connection events
@@ -30,11 +33,13 @@ export class oNodeNotificationManager extends oNotificationManager {
30
33
  */
31
34
  handlePeerConnect(evt) {
32
35
  const peerId = evt.detail;
33
- this.logger.debug(`Peer connected: ${peerId.toString()}`);
36
+ // this.logger.debug(`Peer connected: ${peerId.toString()}`);
34
37
  // Try to resolve peer ID to Olane address
35
38
  const nodeAddress = this.peerIdToAddress(peerId.toString());
36
39
  if (!nodeAddress) {
37
- this.logger.debug(`Could not resolve peer ID ${peerId.toString()} to address`);
40
+ // this.logger.debug(
41
+ // `Could not resolve peer ID ${peerId.toString()} to address`,
42
+ // );
38
43
  return;
39
44
  }
40
45
  // Emit generic node connected event
@@ -48,7 +53,7 @@ export class oNodeNotificationManager extends oNotificationManager {
48
53
  }));
49
54
  // Check if this is a child node
50
55
  if (this.isChild(nodeAddress)) {
51
- this.logger.debug(`Child node connected: ${nodeAddress.toString()}`);
56
+ // this.logger.debug(`Child node connected: ${nodeAddress.toString()}`);
52
57
  this.emit(new ChildJoinedEvent({
53
58
  source: this.address,
54
59
  childAddress: nodeAddress,
@@ -57,7 +62,7 @@ export class oNodeNotificationManager extends oNotificationManager {
57
62
  }
58
63
  // Check if this is a parent node
59
64
  if (this.isParent(nodeAddress)) {
60
- this.logger.debug(`Parent node connected: ${nodeAddress.toString()}`);
65
+ // this.logger.debug(`Parent node connected: ${nodeAddress.toString()}`);
61
66
  this.emit(new ParentConnectedEvent({
62
67
  source: this.address,
63
68
  parentAddress: nodeAddress,
@@ -69,11 +74,13 @@ export class oNodeNotificationManager extends oNotificationManager {
69
74
  */
70
75
  handlePeerDisconnect(evt) {
71
76
  const peerId = evt.detail;
72
- this.logger.debug(`Peer disconnected: ${peerId.toString()}`);
77
+ // this.logger.debug(`Peer disconnected: ${peerId.toString()}`);
73
78
  // Try to resolve peer ID to Olane address
74
79
  const nodeAddress = this.peerIdToAddress(peerId.toString());
75
80
  if (!nodeAddress) {
76
- this.logger.debug(`Could not resolve peer ID ${peerId.toString()} to address`);
81
+ // this.logger.debug(
82
+ // `Could not resolve peer ID ${peerId.toString()} to address`,
83
+ // );
77
84
  return;
78
85
  }
79
86
  // Emit generic node disconnected event
@@ -109,7 +116,7 @@ export class oNodeNotificationManager extends oNotificationManager {
109
116
  */
110
117
  handlePeerDiscovery(evt) {
111
118
  const peerInfo = evt.detail;
112
- this.logger.debug(`Peer discovered: ${peerInfo.id.toString()}`);
119
+ // this.logger.debug(`Peer discovered: ${peerInfo.id.toString()}`);
113
120
  // Try to resolve peer ID to Olane address
114
121
  const nodeAddress = this.peerIdToAddress(peerInfo.id.toString());
115
122
  if (!nodeAddress) {
@@ -124,15 +131,13 @@ export class oNodeNotificationManager extends oNotificationManager {
124
131
  * Handle connection open event from libp2p
125
132
  */
126
133
  handleConnectionOpen(evt) {
127
- const remotePeer = evt.detail.remotePeer;
128
- this.logger.debug(`Connection opened to: ${remotePeer.toString()}`);
134
+ // do nothing for now
129
135
  }
130
136
  /**
131
137
  * Handle connection close event from libp2p
132
138
  */
133
139
  handleConnectionClose(evt) {
134
- const remotePeer = evt.detail.remotePeer;
135
- this.logger.debug(`Connection closed to: ${remotePeer.toString()}`);
140
+ // do nothing for now
136
141
  }
137
142
  /**
138
143
  * Try to resolve a libp2p peer ID to an Olane address
@@ -8,6 +8,7 @@ declare const oNodeTool_base: typeof oServerNode;
8
8
  * @returns A new class that extends the base class and implements the oTool interface
9
9
  */
10
10
  export declare class oNodeTool extends oNodeTool_base {
11
+ private streamHandler;
11
12
  handleProtocol(address: oAddress): Promise<void>;
12
13
  initialize(): Promise<void>;
13
14
  handleStream(stream: Stream, connection: Connection): Promise<void>;
@@ -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,EAGT,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;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;CA2B5D"}
1
+ {"version":3,"file":"o-node.tool.d.ts","sourceRoot":"","sources":["../../src/o-node.tool.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,QAAQ,EAET,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;;AAKrD;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,cAAkB;IAC/C,OAAO,CAAC,aAAa,CAAiB;IAEhC,cAAc,CAAC,OAAO,EAAE,QAAQ;IAUhC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BnE,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC;IAQ9B,oBAAoB,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;CA2B5D"}
@@ -1,8 +1,9 @@
1
- import { CoreUtils, oError, oErrorCodes, oRequest, ChildJoinedEvent, } from '@olane/o-core';
1
+ import { 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
5
  import { oNodeAddress } from './router/o-node.address.js';
6
+ import { StreamHandler } from './connection/stream-handler.js';
6
7
  /**
7
8
  * oTool is a mixin that extends the base class and implements the oTool interface
8
9
  * @param Base - The base class to extend
@@ -12,12 +13,15 @@ export class oNodeTool extends oTool(oServerNode) {
12
13
  async handleProtocol(address) {
13
14
  this.logger.debug('Handling protocol: ' + address.protocol);
14
15
  await this.p2pNode.handle(address.protocol, this.handleStream.bind(this), {
15
- maxInboundStreams: Infinity,
16
- maxOutboundStreams: Infinity,
16
+ maxInboundStreams: 10000,
17
+ maxOutboundStreams: process.env.MAX_OUTBOUND_STREAMS
18
+ ? parseInt(process.env.MAX_OUTBOUND_STREAMS)
19
+ : 1000,
17
20
  });
18
21
  }
19
22
  async initialize() {
20
23
  await super.initialize();
24
+ this.streamHandler = new StreamHandler(this.logger);
21
25
  await this.handleProtocol(this.address);
22
26
  if (this.staticAddress &&
23
27
  this.staticAddress?.toString() !== this.address.toString()) {
@@ -25,40 +29,20 @@ export class oNodeTool extends oTool(oServerNode) {
25
29
  }
26
30
  }
27
31
  async handleStream(stream, connection) {
28
- // CRITICAL: Attach message listener immediately to prevent buffer overflow (libp2p v3)
29
- // Per libp2p migration guide: "If no message event handler is added, streams will
30
- // buffer incoming data until a pre-configured limit is reached, after which the stream will be reset."
31
- const messageHandler = async (event) => {
32
- if (!event.data) {
33
- this.logger.warn('Malformed event data');
34
- return;
32
+ this.logger.debug('Handling connection: ', connection.id);
33
+ // Use StreamHandler for consistent stream handling
34
+ // This follows libp2p v3 best practices for immediate message listener attachment
35
+ await this.streamHandler.handleIncomingStream(stream, connection, async (request, stream) => {
36
+ try {
37
+ const result = await this.execute(request, stream);
38
+ // Return the raw result - StreamHandler will build and send the response
39
+ return result;
35
40
  }
36
- const requestConfig = await CoreUtils.processStream(event);
37
- const request = new oRequest(requestConfig);
38
- let success = true;
39
- const result = await this.execute(request, stream).catch((error) => {
41
+ catch (error) {
40
42
  this.logger.error('Error executing tool: ', request.toString(), error, typeof error);
41
- success = false;
42
- const responseError = error instanceof oError
43
- ? error
44
- : new oError(oErrorCodes.UNKNOWN, error.message);
45
- return {
46
- error: responseError.toJSON(),
47
- };
48
- });
49
- if (success) {
50
- this.metrics.successCount++;
43
+ throw error; // StreamHandler will handle error response building
51
44
  }
52
- else {
53
- this.metrics.errorCount++;
54
- }
55
- // compose the response & add the expected connection + request fields
56
- const response = CoreUtils.buildResponse(request, result, result?.error);
57
- // add the request method to the response
58
- await CoreUtils.sendResponse(response, stream);
59
- };
60
- // Attach listener synchronously before any async operations
61
- stream.addEventListener('message', messageHandler);
45
+ });
62
46
  }
63
47
  async _tool_identify() {
64
48
  return {
@@ -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"}