@olane/o-leader 0.7.10 → 0.7.11

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=address-resolution-integration.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address-resolution-integration.spec.d.ts","sourceRoot":"","sources":["../../test/address-resolution-integration.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,704 @@
1
+ import { expect } from 'chai';
2
+ import { oLeaderNode } from '../src/leader.node.js';
3
+ import { oNode } from '@olane/o-node';
4
+ import { oNodeAddress } from '@olane/o-node';
5
+ import { oAddress, RestrictedAddresses, NodeState } from '@olane/o-core';
6
+ describe('Address Resolution Integration Tests', () => {
7
+ describe('Leader + Registry + Resolver Integration', () => {
8
+ let leader;
9
+ beforeEach(async () => {
10
+ leader = new oLeaderNode({
11
+ name: 'test-leader',
12
+ parent: null,
13
+ leader: null,
14
+ network: {
15
+ listeners: [],
16
+ },
17
+ });
18
+ await leader.start();
19
+ });
20
+ afterEach(async () => {
21
+ if (leader && leader.state === NodeState.RUNNING) {
22
+ await leader.stop();
23
+ }
24
+ });
25
+ it('should start leader with registry as child', async () => {
26
+ expect(leader.state).to.equal(NodeState.RUNNING);
27
+ expect(leader.address.value).to.equal(RestrictedAddresses.LEADER);
28
+ // Verify registry is a child
29
+ const registryChild = leader.hierarchyManager.getChild(new oAddress(RestrictedAddresses.REGISTRY));
30
+ expect(registryChild).to.not.be.null;
31
+ expect(registryChild?.value).to.equal(RestrictedAddresses.REGISTRY);
32
+ });
33
+ it('should be able to register a node in the registry', async () => {
34
+ const registrationParams = {
35
+ _connectionId: 'test-connection',
36
+ _requestMethod: 'test-method',
37
+ peerId: 'peer-embeddings',
38
+ address: 'o://leader/services/embeddings',
39
+ staticAddress: 'o://embeddings-text',
40
+ protocols: ['/embeddings/1.0.0'],
41
+ transports: ['/ip4/127.0.0.1/tcp/6001'],
42
+ };
43
+ const result = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
44
+ method: 'commit',
45
+ params: registrationParams,
46
+ });
47
+ expect(result.result.success).to.be.true;
48
+ });
49
+ it('should be able to search for registered nodes', async () => {
50
+ // Register a node
51
+ const registrationParams = {
52
+ _connectionId: 'test-connection',
53
+ _requestMethod: 'test-method',
54
+ peerId: 'peer-search-test',
55
+ address: 'o://leader/services/search-test',
56
+ staticAddress: 'o://search-test',
57
+ protocols: ['/test/1.0.0'],
58
+ transports: ['/ip4/127.0.0.1/tcp/7001'],
59
+ };
60
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
61
+ method: 'commit',
62
+ params: registrationParams,
63
+ });
64
+ // Search for the node
65
+ const searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
66
+ method: 'search',
67
+ params: { staticAddress: 'o://search-test' },
68
+ });
69
+ expect(searchResult.result.data).to.be.an('array');
70
+ expect(searchResult.result.data).to.have.lengthOf(1);
71
+ expect(searchResult.result.data[0].peerId).to.equal('peer-search-test');
72
+ });
73
+ it('should resolve address using search resolver', async () => {
74
+ // Register a service
75
+ const registrationParams = {
76
+ _connectionId: 'test-connection',
77
+ _requestMethod: 'test-method',
78
+ peerId: 'peer-embeddings',
79
+ address: 'o://leader/services/embeddings',
80
+ staticAddress: 'o://embeddings-text',
81
+ protocols: ['/embeddings/1.0.0'],
82
+ transports: ['/ip4/127.0.0.1/tcp/6001'],
83
+ };
84
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
85
+ method: 'commit',
86
+ params: registrationParams,
87
+ });
88
+ // Try to use the service by static address
89
+ // The resolver should find it in the registry
90
+ try {
91
+ const result = await leader.use(new oAddress('o://embeddings-text'), {
92
+ method: 'ping', // Some method that may not exist, but routing should work
93
+ params: {},
94
+ });
95
+ // If we get here without error, routing worked
96
+ // The actual method call may fail, but that's expected
97
+ }
98
+ catch (error) {
99
+ // Expected - the service doesn't actually exist, but we should have resolved the address
100
+ // Check that the error is about the method, not about routing
101
+ expect(error.message).to.not.include('No route');
102
+ }
103
+ });
104
+ it('should handle multiple services with same protocol', async () => {
105
+ // Register two embedding services
106
+ const service1 = {
107
+ _connectionId: 'test-connection',
108
+ _requestMethod: 'test-method',
109
+ peerId: 'peer-embeddings-1',
110
+ address: 'o://leader/services/embeddings-text',
111
+ staticAddress: 'o://embeddings-text',
112
+ protocols: ['/embeddings/1.0.0'],
113
+ transports: ['/ip4/127.0.0.1/tcp/6001'],
114
+ registeredAt: 1000,
115
+ };
116
+ const service2 = {
117
+ _connectionId: 'test-connection',
118
+ _requestMethod: 'test-method',
119
+ peerId: 'peer-embeddings-2',
120
+ address: 'o://leader/services/embeddings-image',
121
+ staticAddress: 'o://embeddings-image',
122
+ protocols: ['/embeddings/1.0.0'],
123
+ transports: ['/ip4/127.0.0.1/tcp/6002'],
124
+ registeredAt: 2000, // More recent
125
+ };
126
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
127
+ method: 'commit',
128
+ params: service1,
129
+ });
130
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
131
+ method: 'commit',
132
+ params: service2,
133
+ });
134
+ // Search by protocol - should return both, sorted by registeredAt
135
+ const searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
136
+ method: 'search',
137
+ params: { protocols: ['/embeddings/1.0.0'] },
138
+ });
139
+ expect(searchResult.result.data).to.have.lengthOf(2);
140
+ expect(searchResult.result.data[0].peerId).to.equal('peer-embeddings-2'); // Most recent
141
+ expect(searchResult.result.data[1].peerId).to.equal('peer-embeddings-1');
142
+ });
143
+ it('should handle service removal', async () => {
144
+ const registrationParams = {
145
+ _connectionId: 'test-connection',
146
+ _requestMethod: 'test-method',
147
+ peerId: 'peer-temporary',
148
+ address: 'o://leader/services/temporary',
149
+ staticAddress: 'o://temporary',
150
+ protocols: ['/temp/1.0.0'],
151
+ transports: ['/memory'],
152
+ };
153
+ // Register
154
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
155
+ method: 'commit',
156
+ params: registrationParams,
157
+ });
158
+ // Verify it exists
159
+ let searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
160
+ method: 'search',
161
+ params: { staticAddress: 'o://temporary' },
162
+ });
163
+ expect(searchResult.result.data).to.have.lengthOf(1);
164
+ // Remove
165
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
166
+ method: 'remove',
167
+ params: { peerId: 'peer-temporary' },
168
+ });
169
+ // Verify it's gone
170
+ searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
171
+ method: 'search',
172
+ params: { staticAddress: 'o://temporary' },
173
+ });
174
+ expect(searchResult.result.data).to.have.lengthOf(0);
175
+ });
176
+ it('should list all registered nodes', async () => {
177
+ // Register multiple nodes
178
+ const nodes = [
179
+ {
180
+ _connectionId: 'test-connection',
181
+ _requestMethod: 'test-method',
182
+ peerId: 'peer-1',
183
+ address: 'o://node-1',
184
+ protocols: [],
185
+ transports: ['/memory'],
186
+ },
187
+ {
188
+ _connectionId: 'test-connection',
189
+ _requestMethod: 'test-method',
190
+ peerId: 'peer-2',
191
+ address: 'o://node-2',
192
+ protocols: [],
193
+ transports: ['/memory'],
194
+ },
195
+ {
196
+ _connectionId: 'test-connection',
197
+ _requestMethod: 'test-method',
198
+ peerId: 'peer-3',
199
+ address: 'o://node-3',
200
+ protocols: [],
201
+ transports: ['/memory'],
202
+ },
203
+ ];
204
+ for (const node of nodes) {
205
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
206
+ method: 'commit',
207
+ params: node,
208
+ });
209
+ }
210
+ // Get all nodes
211
+ const result = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
212
+ method: 'find_all',
213
+ params: {},
214
+ });
215
+ expect(result.result.data).to.be.an('array');
216
+ expect(result.result.data.length).to.be.at.least(3); // At least our 3 nodes
217
+ const peerIds = result.result.data.map((n) => n.peerId);
218
+ expect(peerIds).to.include('peer-1');
219
+ expect(peerIds).to.include('peer-2');
220
+ expect(peerIds).to.include('peer-3');
221
+ });
222
+ });
223
+ describe('Multi-node scenarios', () => {
224
+ let leader;
225
+ let childNode;
226
+ beforeEach(async () => {
227
+ leader = new oLeaderNode({
228
+ name: 'test-leader',
229
+ parent: null,
230
+ leader: null,
231
+ network: {
232
+ listeners: [],
233
+ },
234
+ });
235
+ await leader.start();
236
+ });
237
+ afterEach(async () => {
238
+ if (childNode && childNode.state === NodeState.RUNNING) {
239
+ await childNode.stop();
240
+ }
241
+ if (leader && leader.state === NodeState.RUNNING) {
242
+ await leader.stop();
243
+ }
244
+ });
245
+ it('should handle hierarchical node registration', async () => {
246
+ // Create child node
247
+ childNode = new oNode({
248
+ address: new oNodeAddress('o://leader/services'),
249
+ parent: leader.address,
250
+ leader: leader.address,
251
+ network: {
252
+ listeners: [],
253
+ },
254
+ });
255
+ await childNode.start();
256
+ // Register the child in the registry
257
+ const registrationParams = {
258
+ _connectionId: 'test-connection',
259
+ _requestMethod: 'test-method',
260
+ peerId: 'peer-services',
261
+ address: 'o://leader/services',
262
+ protocols: ['/services/1.0.0'],
263
+ transports: ['/memory'],
264
+ };
265
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
266
+ method: 'commit',
267
+ params: registrationParams,
268
+ });
269
+ // Search for it
270
+ const searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
271
+ method: 'search',
272
+ params: { address: 'o://leader/services' },
273
+ });
274
+ expect(searchResult.result.data).to.have.lengthOf(1);
275
+ expect(searchResult.result.data[0].address).to.equal('o://leader/services');
276
+ });
277
+ it('should support service discovery by protocol', async () => {
278
+ // Register services with different protocols
279
+ const services = [
280
+ {
281
+ _connectionId: 'test-connection',
282
+ _requestMethod: 'test-method',
283
+ peerId: 'peer-embeddings',
284
+ address: 'o://leader/ai/embeddings',
285
+ protocols: ['/ai/embeddings/1.0.0', '/text/1.0.0'],
286
+ transports: ['/memory'],
287
+ },
288
+ {
289
+ _connectionId: 'test-connection',
290
+ _requestMethod: 'test-method',
291
+ peerId: 'peer-chat',
292
+ address: 'o://leader/ai/chat',
293
+ protocols: ['/ai/chat/1.0.0', '/text/1.0.0'],
294
+ transports: ['/memory'],
295
+ },
296
+ {
297
+ _connectionId: 'test-connection',
298
+ _requestMethod: 'test-method',
299
+ peerId: 'peer-vision',
300
+ address: 'o://leader/ai/vision',
301
+ protocols: ['/ai/vision/1.0.0', '/image/1.0.0'],
302
+ transports: ['/memory'],
303
+ },
304
+ ];
305
+ for (const service of services) {
306
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
307
+ method: 'commit',
308
+ params: service,
309
+ });
310
+ }
311
+ // Find all text-based services
312
+ const textServices = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
313
+ method: 'search',
314
+ params: { protocols: ['/text/1.0.0'] },
315
+ });
316
+ expect(textServices.result.data).to.have.lengthOf(2);
317
+ const peerIds = textServices.result.data.map((s) => s.peerId);
318
+ expect(peerIds).to.include.members(['peer-embeddings', 'peer-chat']);
319
+ // Find embeddings service specifically
320
+ const embeddingsService = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
321
+ method: 'search',
322
+ params: { protocols: ['/ai/embeddings/1.0.0'] },
323
+ });
324
+ expect(embeddingsService.result.data).to.have.lengthOf(1);
325
+ expect(embeddingsService.result.data[0].peerId).to.equal('peer-embeddings');
326
+ });
327
+ it('should handle service updates on re-registration', async () => {
328
+ const initialRegistration = {
329
+ _connectionId: 'test-connection',
330
+ _requestMethod: 'test-method',
331
+ peerId: 'peer-dynamic',
332
+ address: 'o://leader/services/dynamic',
333
+ protocols: ['/service/1.0.0'],
334
+ transports: ['/ip4/127.0.0.1/tcp/4001'],
335
+ registeredAt: 1000,
336
+ };
337
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
338
+ method: 'commit',
339
+ params: initialRegistration,
340
+ });
341
+ // Simulate service update (new transport)
342
+ const updatedRegistration = {
343
+ _connectionId: 'test-connection',
344
+ _requestMethod: 'test-method',
345
+ peerId: 'peer-dynamic',
346
+ address: 'o://leader/services/dynamic',
347
+ protocols: ['/service/1.0.0', '/service/2.0.0'], // Added new protocol
348
+ transports: ['/ip4/192.168.1.1/tcp/4001'], // New transport
349
+ registeredAt: 2000,
350
+ };
351
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
352
+ method: 'commit',
353
+ params: updatedRegistration,
354
+ });
355
+ // Verify update
356
+ const searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
357
+ method: 'search',
358
+ params: { address: 'o://leader/services/dynamic' },
359
+ });
360
+ expect(searchResult.result.data).to.have.lengthOf(1);
361
+ expect(searchResult.result.data[0].protocols).to.include('/service/2.0.0');
362
+ expect(searchResult.result.data[0].transports[0]).to.equal('/ip4/192.168.1.1/tcp/4001');
363
+ expect(searchResult.result.data[0].registeredAt).to.equal(2000);
364
+ });
365
+ });
366
+ describe('Real-world routing scenarios', () => {
367
+ let leader;
368
+ beforeEach(async () => {
369
+ leader = new oLeaderNode({
370
+ name: 'test-leader',
371
+ parent: null,
372
+ leader: null,
373
+ network: {
374
+ listeners: [],
375
+ },
376
+ });
377
+ await leader.start();
378
+ });
379
+ afterEach(async () => {
380
+ if (leader && leader.state === NodeState.RUNNING) {
381
+ await leader.stop();
382
+ }
383
+ });
384
+ it('should route to static address via registry lookup', async () => {
385
+ // Register service with static address
386
+ const registrationParams = {
387
+ _connectionId: 'test-connection',
388
+ _requestMethod: 'test-method',
389
+ peerId: 'peer-static-service',
390
+ address: 'o://leader/services/my-service',
391
+ staticAddress: 'o://my-service',
392
+ protocols: ['/service/1.0.0'],
393
+ transports: ['/memory'],
394
+ };
395
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
396
+ method: 'commit',
397
+ params: registrationParams,
398
+ });
399
+ // Verify static address search works
400
+ const searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
401
+ method: 'search',
402
+ params: { staticAddress: 'o://my-service' },
403
+ });
404
+ expect(searchResult.result.data).to.have.lengthOf(1);
405
+ expect(searchResult.result.data[0].address).to.equal('o://leader/services/my-service');
406
+ expect(searchResult.result.data[0].staticAddress).to.equal('o://my-service');
407
+ });
408
+ it('should handle hierarchical routing through services', async () => {
409
+ // Register nested services
410
+ const services = [
411
+ {
412
+ _connectionId: 'test-connection',
413
+ _requestMethod: 'test-method',
414
+ peerId: 'peer-services-parent',
415
+ address: 'o://leader/services',
416
+ protocols: ['/services/1.0.0'],
417
+ transports: ['/memory'],
418
+ },
419
+ {
420
+ _connectionId: 'test-connection',
421
+ _requestMethod: 'test-method',
422
+ peerId: 'peer-embeddings-child',
423
+ address: 'o://leader/services/embeddings',
424
+ protocols: ['/embeddings/1.0.0'],
425
+ transports: ['/memory'],
426
+ },
427
+ {
428
+ _connectionId: 'test-connection',
429
+ _requestMethod: 'test-method',
430
+ peerId: 'peer-embeddings-text-leaf',
431
+ address: 'o://leader/services/embeddings/text',
432
+ staticAddress: 'o://embeddings-text',
433
+ protocols: ['/embeddings/text/1.0.0'],
434
+ transports: ['/memory'],
435
+ },
436
+ ];
437
+ for (const service of services) {
438
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
439
+ method: 'commit',
440
+ params: service,
441
+ });
442
+ }
443
+ // Verify all are registered
444
+ const allServices = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
445
+ method: 'find_all',
446
+ params: {},
447
+ });
448
+ const peerIds = allServices.result.data.map((s) => s.peerId);
449
+ expect(peerIds).to.include.members([
450
+ 'peer-services-parent',
451
+ 'peer-embeddings-child',
452
+ 'peer-embeddings-text-leaf',
453
+ ]);
454
+ // Test routing through hierarchy using oAddress.next()
455
+ const currentAddress = new oAddress('o://leader');
456
+ const targetAddress = new oAddress('o://leader/services/embeddings/text');
457
+ // First hop: leader -> leader/services
458
+ const firstHop = oAddress.next(currentAddress, targetAddress);
459
+ expect(firstHop.toString()).to.equal('o://leader/services');
460
+ // Second hop: leader/services -> leader/services/embeddings
461
+ const secondHop = oAddress.next(firstHop, targetAddress);
462
+ expect(secondHop.toString()).to.equal('o://leader/services/embeddings');
463
+ // Third hop: leader/services/embeddings -> leader/services/embeddings/text
464
+ const thirdHop = oAddress.next(secondHop, targetAddress);
465
+ expect(thirdHop.toString()).to.equal('o://leader/services/embeddings/text');
466
+ // Fourth hop: at destination
467
+ const fourthHop = oAddress.next(thirdHop, targetAddress);
468
+ expect(fourthHop.toString()).to.equal('o://leader/services/embeddings/text');
469
+ });
470
+ it('should support searching with combined parameters', async () => {
471
+ // Register service with multiple attributes
472
+ const registrationParams = {
473
+ _connectionId: 'test-connection',
474
+ _requestMethod: 'test-method',
475
+ peerId: 'peer-complex',
476
+ address: 'o://leader/ai/embeddings',
477
+ staticAddress: 'o://embeddings-text',
478
+ protocols: ['/embeddings/1.0.0', '/text/1.0.0', '/ai/1.0.0'],
479
+ transports: ['/ip4/127.0.0.1/tcp/6001'],
480
+ };
481
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
482
+ method: 'commit',
483
+ params: registrationParams,
484
+ });
485
+ // Search by static address
486
+ const byStatic = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
487
+ method: 'search',
488
+ params: { staticAddress: 'o://embeddings-text' },
489
+ });
490
+ expect(byStatic.result.data).to.have.lengthOf(1);
491
+ // Search by exact address
492
+ const byAddress = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
493
+ method: 'search',
494
+ params: { address: 'o://leader/ai/embeddings' },
495
+ });
496
+ expect(byAddress.result.data).to.have.lengthOf(1);
497
+ // Search by multiple protocols (AND logic)
498
+ const byProtocols = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
499
+ method: 'search',
500
+ params: { protocols: ['/embeddings/1.0.0', '/text/1.0.0'] },
501
+ });
502
+ expect(byProtocols.result.data).to.have.lengthOf(1);
503
+ // Search by single protocol
504
+ const bySingleProtocol = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
505
+ method: 'search',
506
+ params: { protocols: ['/ai/1.0.0'] },
507
+ });
508
+ expect(bySingleProtocol.result.data).to.have.lengthOf(1);
509
+ });
510
+ it('should handle registry preventing infinite loops', async () => {
511
+ // Try to register registry itself (should work, but search should filter it)
512
+ const registryRegistration = {
513
+ _connectionId: 'test-connection',
514
+ _requestMethod: 'test-method',
515
+ peerId: 'peer-registry',
516
+ address: RestrictedAddresses.REGISTRY,
517
+ staticAddress: RestrictedAddresses.REGISTRY,
518
+ protocols: ['/registry/1.0.0'],
519
+ transports: ['/memory'],
520
+ };
521
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
522
+ method: 'commit',
523
+ params: registryRegistration,
524
+ });
525
+ // Verify it's in the registry
526
+ const allNodes = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
527
+ method: 'find_all',
528
+ params: {},
529
+ });
530
+ const registryNode = allNodes.result.data.find((n) => n.peerId === 'peer-registry');
531
+ expect(registryNode).to.exist;
532
+ // But search resolver should filter it out to prevent loops
533
+ // This is tested in the resolver unit tests
534
+ });
535
+ it('should handle empty registry gracefully', async () => {
536
+ // Search empty registry
537
+ const searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
538
+ method: 'search',
539
+ params: { staticAddress: 'o://nonexistent' },
540
+ });
541
+ expect(searchResult.result.data).to.be.an('array');
542
+ expect(searchResult.result.data).to.have.lengthOf(0);
543
+ });
544
+ it('should handle multiple transports per service', async () => {
545
+ const registrationParams = {
546
+ _connectionId: 'test-connection',
547
+ _requestMethod: 'test-method',
548
+ peerId: 'peer-multi-transport',
549
+ address: 'o://leader/services/multi',
550
+ staticAddress: 'o://multi-transport',
551
+ protocols: ['/multi/1.0.0'],
552
+ transports: [
553
+ '/ip4/127.0.0.1/tcp/4001',
554
+ '/ip4/192.168.1.1/tcp/4001',
555
+ '/ip6/::1/tcp/4001',
556
+ ],
557
+ };
558
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
559
+ method: 'commit',
560
+ params: registrationParams,
561
+ });
562
+ const searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
563
+ method: 'search',
564
+ params: { staticAddress: 'o://multi-transport' },
565
+ });
566
+ expect(searchResult.result.data[0].transports).to.have.lengthOf(3);
567
+ expect(searchResult.result.data[0].transports).to.include('/ip4/127.0.0.1/tcp/4001');
568
+ expect(searchResult.result.data[0].transports).to.include('/ip4/192.168.1.1/tcp/4001');
569
+ expect(searchResult.result.data[0].transports).to.include('/ip6/::1/tcp/4001');
570
+ });
571
+ });
572
+ describe('Edge cases and error handling', () => {
573
+ let leader;
574
+ beforeEach(async () => {
575
+ leader = new oLeaderNode({
576
+ name: 'test-leader',
577
+ parent: null,
578
+ leader: null,
579
+ network: {
580
+ listeners: [],
581
+ },
582
+ });
583
+ await leader.start();
584
+ });
585
+ afterEach(async () => {
586
+ if (leader && leader.state === NodeState.RUNNING) {
587
+ await leader.stop();
588
+ }
589
+ });
590
+ it('should handle service with long address path', async () => {
591
+ const registrationParams = {
592
+ _connectionId: 'test-connection',
593
+ _requestMethod: 'test-method',
594
+ peerId: 'peer-deep',
595
+ address: 'o://leader/a/b/c/d/e/f/g/h/i/j/service',
596
+ protocols: ['/deep/1.0.0'],
597
+ transports: ['/memory'],
598
+ };
599
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
600
+ method: 'commit',
601
+ params: registrationParams,
602
+ });
603
+ const searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
604
+ method: 'search',
605
+ params: { address: 'o://leader/a/b/c/d/e/f/g/h/i/j/service' },
606
+ });
607
+ expect(searchResult.result.data).to.have.lengthOf(1);
608
+ });
609
+ it('should handle service with special characters', async () => {
610
+ const registrationParams = {
611
+ _connectionId: 'test-connection',
612
+ _requestMethod: 'test-method',
613
+ peerId: 'peer-special-chars',
614
+ address: 'o://leader/my-service_v2.0',
615
+ staticAddress: 'o://my-service_v2.0',
616
+ protocols: ['/service/2.0.0'],
617
+ transports: ['/memory'],
618
+ };
619
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
620
+ method: 'commit',
621
+ params: registrationParams,
622
+ });
623
+ const searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
624
+ method: 'search',
625
+ params: { staticAddress: 'o://my-service_v2.0' },
626
+ });
627
+ expect(searchResult.result.data).to.have.lengthOf(1);
628
+ expect(searchResult.result.data[0].address).to.equal('o://leader/my-service_v2.0');
629
+ });
630
+ it('should handle service with empty protocols array', async () => {
631
+ const registrationParams = {
632
+ _connectionId: 'test-connection',
633
+ _requestMethod: 'test-method',
634
+ peerId: 'peer-no-protocols',
635
+ address: 'o://leader/services/no-protocols',
636
+ protocols: [],
637
+ transports: ['/memory'],
638
+ };
639
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
640
+ method: 'commit',
641
+ params: registrationParams,
642
+ });
643
+ const searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
644
+ method: 'search',
645
+ params: { address: 'o://leader/services/no-protocols' },
646
+ });
647
+ expect(searchResult.result.data).to.have.lengthOf(1);
648
+ expect(searchResult.result.data[0].protocols).to.be.an('array')
649
+ .that.is.empty;
650
+ });
651
+ it('should handle concurrent registrations', async () => {
652
+ const registrations = [];
653
+ for (let i = 0; i < 10; i++) {
654
+ registrations.push({
655
+ _connectionId: 'test-connection',
656
+ _requestMethod: 'test-method',
657
+ peerId: `peer-concurrent-${i}`,
658
+ address: `o://leader/services/concurrent-${i}`,
659
+ protocols: ['/concurrent/1.0.0'],
660
+ transports: ['/memory'],
661
+ registeredAt: Date.now() + i,
662
+ });
663
+ }
664
+ // Register concurrently
665
+ const promises = registrations.map((params) => leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
666
+ method: 'commit',
667
+ params,
668
+ }));
669
+ await Promise.all(promises);
670
+ // Verify all were registered
671
+ const searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
672
+ method: 'search',
673
+ params: { protocols: ['/concurrent/1.0.0'] },
674
+ });
675
+ expect(searchResult.result.data).to.have.lengthOf(10);
676
+ });
677
+ it('should maintain sort order after multiple operations', async () => {
678
+ const nodes = [
679
+ { peerId: 'peer-1', registeredAt: 1000 },
680
+ { peerId: 'peer-2', registeredAt: 2000 },
681
+ { peerId: 'peer-3', registeredAt: 3000 },
682
+ ];
683
+ for (const node of nodes) {
684
+ await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
685
+ method: 'commit',
686
+ params: {
687
+ ...node,
688
+ address: `o://${node.peerId}`,
689
+ protocols: ['/test/1.0.0'],
690
+ transports: ['/memory'],
691
+ },
692
+ });
693
+ }
694
+ const searchResult = await leader.use(new oAddress(RestrictedAddresses.REGISTRY), {
695
+ method: 'search',
696
+ params: { protocols: ['/test/1.0.0'] },
697
+ });
698
+ // Should be sorted by registeredAt descending (most recent first)
699
+ expect(searchResult.result.data[0].peerId).to.equal('peer-3');
700
+ expect(searchResult.result.data[1].peerId).to.equal('peer-2');
701
+ expect(searchResult.result.data[2].peerId).to.equal('peer-1');
702
+ });
703
+ });
704
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=registry-memory.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-memory.spec.d.ts","sourceRoot":"","sources":["../../test/registry-memory.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,528 @@
1
+ import { expect } from 'chai';
2
+ import { RegistryMemoryTool } from '../src/registry/registry-memory.tool.js';
3
+ import { oAddress } from '@olane/o-core';
4
+ describe('RegistryMemoryTool', () => {
5
+ let registry;
6
+ beforeEach(() => {
7
+ registry = new RegistryMemoryTool({
8
+ name: 'test-registry',
9
+ parent: oAddress.leader(),
10
+ leader: oAddress.leader(),
11
+ network: {
12
+ listeners: [],
13
+ },
14
+ });
15
+ });
16
+ describe('_tool_commit()', () => {
17
+ it('should register a node with all fields', async () => {
18
+ const params = {
19
+ _connectionId: 'test-connection',
20
+ _requestMethod: 'test-method',
21
+ peerId: 'peer-123',
22
+ address: 'o://leader/services/embeddings',
23
+ protocols: ['/embeddings/1.0.0', '/text/1.0.0'],
24
+ transports: ['/ip4/127.0.0.1/tcp/4001'],
25
+ staticAddress: 'o://embeddings-text',
26
+ registeredAt: Date.now(),
27
+ };
28
+ const request = {
29
+ params,
30
+ };
31
+ const result = await registry._tool_commit(request);
32
+ expect(result.success).to.be.true;
33
+ // Verify node is in registry
34
+ const allNodes = await registry._tool_find_all({});
35
+ expect(allNodes).to.have.lengthOf(1);
36
+ expect(allNodes[0].peerId).to.equal('peer-123');
37
+ expect(allNodes[0].address).to.equal('o://leader/services/embeddings');
38
+ });
39
+ it('should register a node with minimal fields', async () => {
40
+ const params = {
41
+ _connectionId: 'test-connection',
42
+ _requestMethod: 'test-method',
43
+ peerId: 'peer-456',
44
+ address: 'o://worker',
45
+ protocols: [],
46
+ transports: ['/memory'],
47
+ };
48
+ const request = {
49
+ params,
50
+ };
51
+ const result = await registry._tool_commit(request);
52
+ expect(result.success).to.be.true;
53
+ const allNodes = await registry._tool_find_all({});
54
+ expect(allNodes).to.have.lengthOf(1);
55
+ expect(allNodes[0].peerId).to.equal('peer-456');
56
+ });
57
+ it('should update an existing node on re-registration', async () => {
58
+ const initialParams = {
59
+ _connectionId: 'test-connection',
60
+ _requestMethod: 'test-method',
61
+ peerId: 'peer-789',
62
+ address: 'o://service-v1',
63
+ protocols: ['/service/1.0.0'],
64
+ transports: ['/ip4/127.0.0.1/tcp/4001'],
65
+ };
66
+ await registry._tool_commit({
67
+ params: initialParams,
68
+ });
69
+ // Re-register with updated info
70
+ const updatedParams = {
71
+ _connectionId: 'test-connection',
72
+ _requestMethod: 'test-method',
73
+ peerId: 'peer-789',
74
+ address: 'o://service-v2',
75
+ protocols: ['/service/2.0.0'],
76
+ transports: ['/ip4/127.0.0.1/tcp/4002'],
77
+ };
78
+ await registry._tool_commit({
79
+ params: updatedParams,
80
+ });
81
+ const allNodes = await registry._tool_find_all({});
82
+ expect(allNodes).to.have.lengthOf(1);
83
+ expect(allNodes[0].address).to.equal('o://service-v2');
84
+ expect(allNodes[0].protocols).to.deep.equal(['/service/2.0.0']);
85
+ });
86
+ it('should register multiple nodes with same protocols', async () => {
87
+ const node1 = {
88
+ _connectionId: 'test-connection',
89
+ _requestMethod: 'test-method',
90
+ peerId: 'peer-001',
91
+ address: 'o://embeddings-node-1',
92
+ protocols: ['/embeddings/1.0.0'],
93
+ transports: ['/ip4/127.0.0.1/tcp/5001'],
94
+ };
95
+ const node2 = {
96
+ _connectionId: 'test-connection',
97
+ _requestMethod: 'test-method',
98
+ peerId: 'peer-002',
99
+ address: 'o://embeddings-node-2',
100
+ protocols: ['/embeddings/1.0.0'],
101
+ transports: ['/ip4/127.0.0.1/tcp/5002'],
102
+ };
103
+ await registry._tool_commit({ params: node1 });
104
+ await registry._tool_commit({ params: node2 });
105
+ const allNodes = await registry._tool_find_all({});
106
+ expect(allNodes).to.have.lengthOf(2);
107
+ });
108
+ it('should set registeredAt timestamp if not provided', async () => {
109
+ const params = {
110
+ _connectionId: 'test-connection',
111
+ _requestMethod: 'test-method',
112
+ peerId: 'peer-timestamp',
113
+ address: 'o://test',
114
+ protocols: [],
115
+ transports: ['/memory'],
116
+ };
117
+ const beforeTime = Date.now();
118
+ await registry._tool_commit({ params });
119
+ const afterTime = Date.now();
120
+ const allNodes = await registry._tool_find_all({});
121
+ const registeredNode = allNodes[0];
122
+ expect(registeredNode.registeredAt).to.be.a('number');
123
+ expect(registeredNode.registeredAt).to.be.at.least(beforeTime);
124
+ expect(registeredNode.registeredAt).to.be.at.most(afterTime);
125
+ });
126
+ it('should preserve custom registeredAt timestamp', async () => {
127
+ const customTimestamp = 1234567890;
128
+ const params = {
129
+ _connectionId: 'test-connection',
130
+ _requestMethod: 'test-method',
131
+ peerId: 'peer-custom-time',
132
+ address: 'o://test',
133
+ protocols: [],
134
+ transports: ['/memory'],
135
+ registeredAt: customTimestamp,
136
+ };
137
+ await registry._tool_commit({ params });
138
+ const allNodes = await registry._tool_find_all({});
139
+ expect(allNodes[0].registeredAt).to.equal(customTimestamp);
140
+ });
141
+ it('should update protocol mapping on registration', async () => {
142
+ const params = {
143
+ _connectionId: 'test-connection',
144
+ _requestMethod: 'test-method',
145
+ peerId: 'peer-protocol-map',
146
+ address: 'o://test',
147
+ protocols: ['/embeddings/1.0.0', '/chat/1.0.0'],
148
+ transports: ['/memory'],
149
+ };
150
+ await registry._tool_commit({ params });
151
+ // Search by protocol to verify mapping
152
+ const searchResult = await registry._tool_search({
153
+ params: { protocols: ['/embeddings/1.0.0'] },
154
+ });
155
+ expect(searchResult).to.have.lengthOf(1);
156
+ expect(searchResult[0].peerId).to.equal('peer-protocol-map');
157
+ });
158
+ });
159
+ describe('_tool_search()', () => {
160
+ beforeEach(async () => {
161
+ // Setup test data
162
+ const nodes = [
163
+ {
164
+ _connectionId: 'test-connection',
165
+ _requestMethod: 'test-method',
166
+ peerId: 'peer-embeddings-1',
167
+ address: 'o://leader/services/embeddings-text',
168
+ staticAddress: 'o://embeddings-text',
169
+ protocols: ['/embeddings/1.0.0', '/text/1.0.0'],
170
+ transports: ['/ip4/127.0.0.1/tcp/6001'],
171
+ registeredAt: 1000,
172
+ },
173
+ {
174
+ _connectionId: 'test-connection',
175
+ _requestMethod: 'test-method',
176
+ peerId: 'peer-embeddings-2',
177
+ address: 'o://leader/services/embeddings-image',
178
+ staticAddress: 'o://embeddings-image',
179
+ protocols: ['/embeddings/1.0.0', '/image/1.0.0'],
180
+ transports: ['/ip4/127.0.0.1/tcp/6002'],
181
+ registeredAt: 2000,
182
+ },
183
+ {
184
+ _connectionId: 'test-connection',
185
+ _requestMethod: 'test-method',
186
+ peerId: 'peer-chat',
187
+ address: 'o://leader/services/chat',
188
+ staticAddress: 'o://chat',
189
+ protocols: ['/chat/1.0.0'],
190
+ transports: ['/ip4/127.0.0.1/tcp/6003'],
191
+ registeredAt: 3000,
192
+ },
193
+ ];
194
+ for (const node of nodes) {
195
+ await registry._tool_commit({ params: node });
196
+ }
197
+ });
198
+ it('should search by staticAddress (exact match)', async () => {
199
+ const result = await registry._tool_search({
200
+ params: { staticAddress: 'o://embeddings-text' },
201
+ });
202
+ expect(result).to.have.lengthOf(1);
203
+ expect(result[0].peerId).to.equal('peer-embeddings-1');
204
+ expect(result[0].staticAddress).to.equal('o://embeddings-text');
205
+ });
206
+ it('should search by address (exact match)', async () => {
207
+ const result = await registry._tool_search({
208
+ params: { address: 'o://leader/services/chat' },
209
+ });
210
+ expect(result).to.have.lengthOf(1);
211
+ expect(result[0].peerId).to.equal('peer-chat');
212
+ });
213
+ it('should search by single protocol', async () => {
214
+ const result = await registry._tool_search({
215
+ params: { protocols: ['/chat/1.0.0'] },
216
+ });
217
+ expect(result).to.have.lengthOf(1);
218
+ expect(result[0].peerId).to.equal('peer-chat');
219
+ });
220
+ it('should search by multiple protocols (AND logic)', async () => {
221
+ const result = await registry._tool_search({
222
+ params: { protocols: ['/embeddings/1.0.0', '/text/1.0.0'] },
223
+ });
224
+ expect(result).to.have.lengthOf(1);
225
+ expect(result[0].peerId).to.equal('peer-embeddings-1');
226
+ });
227
+ it('should return multiple nodes matching protocol', async () => {
228
+ const result = await registry._tool_search({
229
+ params: { protocols: ['/embeddings/1.0.0'] },
230
+ });
231
+ expect(result).to.have.lengthOf(2);
232
+ expect(result[0].peerId).to.equal('peer-embeddings-2'); // More recent
233
+ expect(result[1].peerId).to.equal('peer-embeddings-1');
234
+ });
235
+ it('should return empty array when no results found', async () => {
236
+ const result = await registry._tool_search({
237
+ params: { staticAddress: 'o://nonexistent' },
238
+ });
239
+ expect(result).to.be.an('array');
240
+ expect(result).to.have.lengthOf(0);
241
+ });
242
+ it('should return empty array for non-matching protocol', async () => {
243
+ const result = await registry._tool_search({
244
+ params: { protocols: ['/nonexistent/1.0.0'] },
245
+ });
246
+ expect(result).to.have.lengthOf(0);
247
+ });
248
+ it('should sort results by registeredAt (most recent first)', async () => {
249
+ const result = await registry._tool_search({
250
+ params: { protocols: ['/embeddings/1.0.0'] },
251
+ });
252
+ expect(result).to.have.lengthOf(2);
253
+ expect(result[0].registeredAt).to.equal(2000);
254
+ expect(result[1].registeredAt).to.equal(1000);
255
+ expect(result[0].registeredAt).to.be.greaterThan(result[1].registeredAt);
256
+ });
257
+ it('should handle combined search parameters', async () => {
258
+ const result = await registry._tool_search({
259
+ params: {
260
+ staticAddress: 'o://embeddings-text',
261
+ protocols: ['/embeddings/1.0.0'],
262
+ },
263
+ });
264
+ // Should return the node that matches staticAddress
265
+ // Note: Current implementation concatenates results, may have duplicates
266
+ expect(result.length).to.be.at.least(1);
267
+ const textEmbedding = result.find((r) => r.peerId === 'peer-embeddings-1');
268
+ expect(textEmbedding).to.exist;
269
+ });
270
+ it('should handle search with no parameters', async () => {
271
+ const result = await registry._tool_search({
272
+ params: {},
273
+ });
274
+ // Empty search should return empty array
275
+ expect(result).to.be.an('array');
276
+ expect(result).to.have.lengthOf(0);
277
+ });
278
+ it('should handle partial protocol match failure', async () => {
279
+ // Search for both protocols when node only has one
280
+ const result = await registry._tool_search({
281
+ params: { protocols: ['/chat/1.0.0', '/embeddings/1.0.0'] },
282
+ });
283
+ // AND logic: node must have ALL protocols
284
+ expect(result).to.have.lengthOf(0);
285
+ });
286
+ it('should handle nodes without registeredAt timestamp', async () => {
287
+ const params = {
288
+ _connectionId: 'test-connection',
289
+ _requestMethod: 'test-method',
290
+ peerId: 'peer-no-timestamp',
291
+ address: 'o://test-no-timestamp',
292
+ protocols: ['/test/1.0.0'],
293
+ transports: ['/memory'],
294
+ // No registeredAt
295
+ };
296
+ await registry._tool_commit({ params });
297
+ const result = await registry._tool_search({
298
+ params: { protocols: ['/test/1.0.0'] },
299
+ });
300
+ expect(result).to.have.lengthOf(1);
301
+ expect(result[0].peerId).to.equal('peer-no-timestamp');
302
+ });
303
+ });
304
+ describe('_tool_find_all()', () => {
305
+ it('should return empty array for empty registry', async () => {
306
+ const result = await registry._tool_find_all({});
307
+ expect(result).to.be.an('array');
308
+ expect(result).to.have.lengthOf(0);
309
+ });
310
+ it('should return single node', async () => {
311
+ const params = {
312
+ _connectionId: 'test-connection',
313
+ _requestMethod: 'test-method',
314
+ peerId: 'single-peer',
315
+ address: 'o://single',
316
+ protocols: [],
317
+ transports: ['/memory'],
318
+ };
319
+ await registry._tool_commit({ params });
320
+ const result = await registry._tool_find_all({});
321
+ expect(result).to.have.lengthOf(1);
322
+ expect(result[0].peerId).to.equal('single-peer');
323
+ });
324
+ it('should return all registered nodes', async () => {
325
+ const nodes = [
326
+ {
327
+ peerId: 'peer-1',
328
+ address: 'o://node-1',
329
+ protocols: [],
330
+ transports: ['/memory'],
331
+ },
332
+ {
333
+ peerId: 'peer-2',
334
+ address: 'o://node-2',
335
+ protocols: [],
336
+ transports: ['/memory'],
337
+ },
338
+ {
339
+ peerId: 'peer-3',
340
+ address: 'o://node-3',
341
+ protocols: [],
342
+ transports: ['/memory'],
343
+ },
344
+ ];
345
+ for (const node of nodes) {
346
+ await registry._tool_commit({
347
+ params: node,
348
+ });
349
+ }
350
+ const result = await registry._tool_find_all({});
351
+ expect(result).to.have.lengthOf(3);
352
+ const peerIds = result.map((r) => r.peerId);
353
+ expect(peerIds).to.include.members(['peer-1', 'peer-2', 'peer-3']);
354
+ });
355
+ it('should return nodes with all their properties', async () => {
356
+ const params = {
357
+ _connectionId: 'test-connection',
358
+ _requestMethod: 'test-method',
359
+ peerId: 'full-peer',
360
+ address: 'o://full-node',
361
+ staticAddress: 'o://static',
362
+ protocols: ['/protocol/1.0.0'],
363
+ transports: ['/ip4/127.0.0.1/tcp/4001'],
364
+ ttl: 3600,
365
+ registeredAt: 12345,
366
+ };
367
+ await registry._tool_commit({ params });
368
+ const result = await registry._tool_find_all({});
369
+ expect(result[0]).to.deep.include({
370
+ peerId: 'full-peer',
371
+ address: 'o://full-node',
372
+ staticAddress: 'o://static',
373
+ ttl: 3600,
374
+ registeredAt: 12345,
375
+ });
376
+ expect(result[0].protocols).to.deep.equal(['/protocol/1.0.0']);
377
+ expect(result[0].transports).to.deep.equal(['/ip4/127.0.0.1/tcp/4001']);
378
+ });
379
+ });
380
+ describe('_tool_remove()', () => {
381
+ it('should remove an existing node', async () => {
382
+ const params = {
383
+ _connectionId: 'test-connection',
384
+ _requestMethod: 'test-method',
385
+ peerId: 'peer-to-remove',
386
+ address: 'o://removable',
387
+ protocols: ['/test/1.0.0'],
388
+ transports: ['/memory'],
389
+ };
390
+ await registry._tool_commit({ params });
391
+ let allNodes = await registry._tool_find_all({});
392
+ expect(allNodes).to.have.lengthOf(1);
393
+ const result = await registry._tool_remove({
394
+ params,
395
+ });
396
+ expect(result.success).to.be.true;
397
+ allNodes = await registry._tool_find_all({});
398
+ expect(allNodes).to.have.lengthOf(0);
399
+ });
400
+ it('should handle removing non-existent node', async () => {
401
+ const params = {
402
+ _connectionId: 'test-connection',
403
+ _requestMethod: 'test-method',
404
+ peerId: 'nonexistent-peer',
405
+ address: 'o://nonexistent',
406
+ protocols: [],
407
+ transports: [],
408
+ };
409
+ const result = await registry._tool_remove({
410
+ params,
411
+ });
412
+ expect(result.success).to.be.true; // Should succeed silently
413
+ });
414
+ it('should remove correct node when multiple exist', async () => {
415
+ const nodes = [
416
+ {
417
+ peerId: 'peer-1',
418
+ address: 'o://node-1',
419
+ protocols: [],
420
+ transports: ['/memory'],
421
+ },
422
+ {
423
+ peerId: 'peer-2',
424
+ address: 'o://node-2',
425
+ protocols: [],
426
+ transports: ['/memory'],
427
+ },
428
+ {
429
+ peerId: 'peer-3',
430
+ address: 'o://node-3',
431
+ protocols: [],
432
+ transports: ['/memory'],
433
+ },
434
+ ];
435
+ for (const node of nodes) {
436
+ await registry._tool_commit({
437
+ params: node,
438
+ });
439
+ }
440
+ await registry._tool_remove({
441
+ params: { peerId: 'peer-2' },
442
+ });
443
+ const allNodes = await registry._tool_find_all({});
444
+ expect(allNodes).to.have.lengthOf(2);
445
+ const peerIds = allNodes.map((r) => r.peerId);
446
+ expect(peerIds).to.not.include('peer-2');
447
+ expect(peerIds).to.include.members(['peer-1', 'peer-3']);
448
+ });
449
+ it('should not affect search results after removal', async () => {
450
+ const params = {
451
+ _connectionId: 'test-connection',
452
+ _requestMethod: 'test-method',
453
+ peerId: 'peer-search-remove',
454
+ address: 'o://test',
455
+ staticAddress: 'o://test-static',
456
+ protocols: ['/test/1.0.0'],
457
+ transports: ['/memory'],
458
+ };
459
+ await registry._tool_commit({ params });
460
+ let searchResult = await registry._tool_search({
461
+ params: { staticAddress: 'o://test-static' },
462
+ });
463
+ expect(searchResult).to.have.lengthOf(1);
464
+ await registry._tool_remove({ params });
465
+ searchResult = await registry._tool_search({
466
+ params: { staticAddress: 'o://test-static' },
467
+ });
468
+ expect(searchResult).to.have.lengthOf(0);
469
+ });
470
+ });
471
+ describe('Edge cases and error scenarios', () => {
472
+ it('should handle registry with large number of nodes', async () => {
473
+ const nodeCount = 100;
474
+ for (let i = 0; i < nodeCount; i++) {
475
+ const params = {
476
+ _connectionId: 'test-connection',
477
+ _requestMethod: 'test-method',
478
+ peerId: `peer-${i}`,
479
+ address: `o://node-${i}`,
480
+ protocols: ['/test/1.0.0'],
481
+ transports: ['/memory'],
482
+ registeredAt: i,
483
+ };
484
+ await registry._tool_commit({ params });
485
+ }
486
+ const allNodes = await registry._tool_find_all({});
487
+ expect(allNodes).to.have.lengthOf(nodeCount);
488
+ // Verify sorting still works
489
+ const searchResult = await registry._tool_search({
490
+ params: { protocols: ['/test/1.0.0'] },
491
+ });
492
+ expect(searchResult[0].registeredAt).to.equal(nodeCount - 1); // Most recent
493
+ });
494
+ it('should handle nodes with empty protocols array', async () => {
495
+ const params = {
496
+ _connectionId: 'test-connection',
497
+ _requestMethod: 'test-method',
498
+ peerId: 'peer-no-protocols',
499
+ address: 'o://no-protocols',
500
+ protocols: [],
501
+ transports: ['/memory'],
502
+ };
503
+ await registry._tool_commit({ params });
504
+ const result = await registry._tool_search({
505
+ params: { address: 'o://no-protocols' },
506
+ });
507
+ expect(result).to.have.lengthOf(1);
508
+ expect(result[0].protocols).to.be.an('array').that.is.empty;
509
+ });
510
+ it('should handle special characters in addresses', async () => {
511
+ const params = {
512
+ _connectionId: 'test-connection',
513
+ _requestMethod: 'test-method',
514
+ peerId: 'peer-special',
515
+ address: 'o://service-name_v2.0',
516
+ staticAddress: 'o://service-name_v2.0',
517
+ protocols: [],
518
+ transports: ['/memory'],
519
+ };
520
+ await registry._tool_commit({ params });
521
+ const result = await registry._tool_search({
522
+ params: { staticAddress: 'o://service-name_v2.0' },
523
+ });
524
+ expect(result).to.have.lengthOf(1);
525
+ expect(result[0].address).to.equal('o://service-name_v2.0');
526
+ });
527
+ });
528
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olane/o-leader",
3
- "version": "0.7.10",
3
+ "version": "0.7.11",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -55,12 +55,12 @@
55
55
  "typescript": "5.4.5"
56
56
  },
57
57
  "peerDependencies": {
58
- "@olane/o-config": "^0.7.9",
59
- "@olane/o-core": "^0.7.9",
60
- "@olane/o-gateway-olane": "^0.7.9",
61
- "@olane/o-lane": "^0.7.9",
62
- "@olane/o-protocol": "^0.7.9",
63
- "@olane/o-tool": "^0.7.9"
58
+ "@olane/o-config": "^0.7.11",
59
+ "@olane/o-core": "^0.7.11",
60
+ "@olane/o-gateway-olane": "^0.7.11",
61
+ "@olane/o-lane": "^0.7.11",
62
+ "@olane/o-protocol": "^0.7.11",
63
+ "@olane/o-tool": "^0.7.11"
64
64
  },
65
65
  "dependencies": {
66
66
  "debug": "^4.4.1",