holosphere 2.0.0-alpha0 → 2.0.0-alpha2

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 (88) hide show
  1. package/LICENSE +162 -38
  2. package/dist/cjs/holosphere.cjs +2 -0
  3. package/dist/cjs/holosphere.cjs.map +1 -0
  4. package/dist/esm/holosphere.js +56 -0
  5. package/dist/esm/holosphere.js.map +1 -0
  6. package/dist/index-CDfIuXew.js +15974 -0
  7. package/dist/index-CDfIuXew.js.map +1 -0
  8. package/dist/index-ifOgtDvd.cjs +3 -0
  9. package/dist/index-ifOgtDvd.cjs.map +1 -0
  10. package/dist/indexeddb-storage-CMW4qRQS.js +96 -0
  11. package/dist/indexeddb-storage-CMW4qRQS.js.map +1 -0
  12. package/dist/indexeddb-storage-DLZOgetM.cjs +2 -0
  13. package/dist/indexeddb-storage-DLZOgetM.cjs.map +1 -0
  14. package/dist/memory-storage-DQzcAZlf.js +47 -0
  15. package/dist/memory-storage-DQzcAZlf.js.map +1 -0
  16. package/dist/memory-storage-DmePEP2q.cjs +2 -0
  17. package/dist/memory-storage-DmePEP2q.cjs.map +1 -0
  18. package/dist/secp256k1-CP0ZkpAx.cjs +13 -0
  19. package/dist/secp256k1-CP0ZkpAx.cjs.map +1 -0
  20. package/dist/secp256k1-vOXp40Fx.js +2281 -0
  21. package/dist/secp256k1-vOXp40Fx.js.map +1 -0
  22. package/docs/FOSDEM_PROPOSAL.md +388 -0
  23. package/docs/LOCALFIRST.md +266 -0
  24. package/docs/contracts/api-interface.md +793 -0
  25. package/docs/data-model.md +476 -0
  26. package/docs/gun-async-usage.md +338 -0
  27. package/docs/plan.md +349 -0
  28. package/docs/quickstart.md +674 -0
  29. package/docs/research.md +362 -0
  30. package/docs/spec.md +244 -0
  31. package/docs/storage-backends.md +326 -0
  32. package/docs/tasks.md +947 -0
  33. package/package.json +1 -1
  34. package/tests/unit/ai/aggregation.test.js +0 -295
  35. package/tests/unit/ai/breakdown.test.js +0 -446
  36. package/tests/unit/ai/classifier.test.js +0 -294
  37. package/tests/unit/ai/council.test.js +0 -262
  38. package/tests/unit/ai/embeddings.test.js +0 -384
  39. package/tests/unit/ai/federation-ai.test.js +0 -344
  40. package/tests/unit/ai/h3-ai.test.js +0 -458
  41. package/tests/unit/ai/index.test.js +0 -304
  42. package/tests/unit/ai/json-ops.test.js +0 -307
  43. package/tests/unit/ai/llm-service.test.js +0 -390
  44. package/tests/unit/ai/nl-query.test.js +0 -383
  45. package/tests/unit/ai/relationships.test.js +0 -311
  46. package/tests/unit/ai/schema-extractor.test.js +0 -384
  47. package/tests/unit/ai/spatial.test.js +0 -279
  48. package/tests/unit/ai/tts.test.js +0 -279
  49. package/tests/unit/content.test.js +0 -332
  50. package/tests/unit/contract/core.test.js +0 -88
  51. package/tests/unit/contract/crypto.test.js +0 -198
  52. package/tests/unit/contract/data.test.js +0 -223
  53. package/tests/unit/contract/federation.test.js +0 -181
  54. package/tests/unit/contract/hierarchical.test.js +0 -113
  55. package/tests/unit/contract/schema.test.js +0 -114
  56. package/tests/unit/contract/social.test.js +0 -217
  57. package/tests/unit/contract/spatial.test.js +0 -110
  58. package/tests/unit/contract/subscriptions.test.js +0 -128
  59. package/tests/unit/contract/utils.test.js +0 -159
  60. package/tests/unit/core.test.js +0 -152
  61. package/tests/unit/crypto.test.js +0 -328
  62. package/tests/unit/federation.test.js +0 -234
  63. package/tests/unit/gun-async.test.js +0 -252
  64. package/tests/unit/hierarchical.test.js +0 -399
  65. package/tests/unit/integration/scenario-01-geographic-storage.test.js +0 -74
  66. package/tests/unit/integration/scenario-02-federation.test.js +0 -76
  67. package/tests/unit/integration/scenario-03-subscriptions.test.js +0 -102
  68. package/tests/unit/integration/scenario-04-validation.test.js +0 -129
  69. package/tests/unit/integration/scenario-05-hierarchy.test.js +0 -125
  70. package/tests/unit/integration/scenario-06-social.test.js +0 -135
  71. package/tests/unit/integration/scenario-07-persistence.test.js +0 -130
  72. package/tests/unit/integration/scenario-08-authorization.test.js +0 -161
  73. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +0 -139
  74. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +0 -357
  75. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +0 -410
  76. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +0 -719
  77. package/tests/unit/performance/benchmark.test.js +0 -85
  78. package/tests/unit/schema.test.js +0 -213
  79. package/tests/unit/spatial.test.js +0 -158
  80. package/tests/unit/storage.test.js +0 -195
  81. package/tests/unit/subscriptions.test.js +0 -328
  82. package/tests/unit/test-data-permanence-debug.js +0 -197
  83. package/tests/unit/test-data-permanence.js +0 -340
  84. package/tests/unit/test-key-persistence-fixed.js +0 -148
  85. package/tests/unit/test-key-persistence.js +0 -172
  86. package/tests/unit/test-relay-permanence.js +0 -376
  87. package/tests/unit/test-second-node.js +0 -95
  88. package/tests/unit/test-simple-write.js +0 -89
@@ -1,113 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import HoloSphere from '../../../src/index.js';
3
-
4
- describe('Contract: Hierarchical Operations', () => {
5
- let hs;
6
- let testHolonId;
7
-
8
- beforeEach(async () => {
9
- hs = new HoloSphere({ relays: [], appName: 'test-hierarchical' });
10
- testHolonId = await hs.toHolon(37.7749, -122.4194, 9);
11
- });
12
-
13
- describe('upcast()', () => {
14
- it('should propagate data to parent holons', async () => {
15
- await hs.write(testHolonId, 'events', {
16
- id: 'event-001',
17
- name: 'Test Event'
18
- });
19
-
20
- const success = await hs.upcast(testHolonId, 'events', 'event-001');
21
- expect(success).toBe(true);
22
- });
23
-
24
- it('should accept maxLevel option', async () => {
25
- await hs.write(testHolonId, 'events', { id: 'event-001', name: 'Test' });
26
-
27
- const success = await hs.upcast(
28
- testHolonId,
29
- 'events',
30
- 'event-001',
31
- { maxLevel: 3 }
32
- );
33
- expect(success).toBe(true);
34
- });
35
-
36
- it('should accept operation option: summarize', async () => {
37
- await hs.write(testHolonId, 'events', { id: 'event-001', name: 'Test' });
38
-
39
- const success = await hs.upcast(
40
- testHolonId,
41
- 'events',
42
- 'event-001',
43
- { operation: 'summarize' }
44
- );
45
- expect(success).toBe(true);
46
- });
47
-
48
- it('should accept operation option: aggregate', async () => {
49
- await hs.write(testHolonId, 'events', { id: 'event-001', count: 5 });
50
-
51
- const success = await hs.upcast(
52
- testHolonId,
53
- 'events',
54
- 'event-001',
55
- { operation: 'aggregate' }
56
- );
57
- expect(success).toBe(true);
58
- });
59
-
60
- it('should accept operation option: concatenate (default)', async () => {
61
- await hs.write(testHolonId, 'events', { id: 'event-001', items: [1, 2, 3] });
62
-
63
- const success = await hs.upcast(
64
- testHolonId,
65
- 'events',
66
- 'event-001',
67
- { operation: 'concatenate' }
68
- );
69
- expect(success).toBe(true);
70
- });
71
-
72
- it('should throw Error for noospheric holons (no hierarchy)', async () => {
73
- const noospheric = 'nostr://topic/test';
74
-
75
- await hs.write(noospheric, 'content', { id: 'post-1', text: 'Hello' });
76
-
77
- await expect(
78
- hs.upcast(noospheric, 'content', 'post-1')
79
- ).rejects.toThrow(Error);
80
- });
81
-
82
- it('should respect maxLevel limit', async () => {
83
- await hs.write(testHolonId, 'events', { id: 'event-001', name: 'Test' });
84
-
85
- const success = await hs.upcast(
86
- testHolonId,
87
- 'events',
88
- 'event-001',
89
- { maxLevel: 1 }
90
- );
91
- expect(success).toBe(true);
92
-
93
- // Verify data only propagated to immediate parent
94
- const parents = await hs.getParents(testHolonId);
95
- const immediateParent = parents[0];
96
- const data = await hs.read(immediateParent, 'events');
97
- expect(data.length).toBeGreaterThan(0);
98
- });
99
-
100
- it('should handle upcast to resolution 0 (global level)', async () => {
101
- const highResHolon = await hs.toHolon(37.7749, -122.4194, 12);
102
- await hs.write(highResHolon, 'events', { id: 'event-001', name: 'Test' });
103
-
104
- const success = await hs.upcast(
105
- highResHolon,
106
- 'events',
107
- 'event-001',
108
- { maxLevel: Infinity }
109
- );
110
- expect(success).toBe(true);
111
- });
112
- });
113
- });
@@ -1,114 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import HoloSphere from '../../../src/index.js';
3
-
4
- describe('Contract: Schema Operations', () => {
5
- let hs;
6
-
7
- beforeEach(() => {
8
- hs = new HoloSphere({ relays: [], appName: 'test-schema' });
9
- });
10
-
11
- describe('setSchema()', () => {
12
- it('should set schema for a lens', async () => {
13
- await expect(
14
- hs.setSchema('temperature', 'https://example.com/schemas/temperature.json')
15
- ).resolves.toBeUndefined();
16
- });
17
-
18
- it('should set schema with strict mode enabled', async () => {
19
- await expect(
20
- hs.setSchema('temperature', 'https://example.com/schemas/temperature.json', true)
21
- ).resolves.toBeUndefined();
22
- });
23
-
24
- it('should set schema with strict mode disabled (default)', async () => {
25
- await expect(
26
- hs.setSchema('temperature', 'https://example.com/schemas/temperature.json', false)
27
- ).resolves.toBeUndefined();
28
- });
29
-
30
- it('should accept URI schemas without fetching', async () => {
31
- // URI schemas are stored as references without fetching
32
- await expect(
33
- hs.setSchema('temperature', 'https://nonexistent.invalid/schema.json')
34
- ).resolves.toBeUndefined();
35
- });
36
-
37
- it('should store invalid schema objects and validate on use', async () => {
38
- // Invalid schemas are caught during validation, not during setSchema
39
- const invalidSchema = { notAValidSchema: true };
40
- await expect(
41
- hs.setSchema('temperature', invalidSchema)
42
- ).rejects.toThrow('ValidationError');
43
- });
44
-
45
- it('should validate JSON Schema 2019 format', async () => {
46
- const validSchema = {
47
- $schema: 'https://json-schema.org/draft/2019-09/schema',
48
- type: 'object',
49
- properties: {
50
- id: { type: 'string' },
51
- value: { type: 'number' }
52
- }
53
- };
54
-
55
- // Assuming schema can be passed directly or via URI
56
- await expect(
57
- hs.setSchema('temperature', 'test://valid-schema')
58
- ).resolves.toBeUndefined();
59
- });
60
- });
61
-
62
- describe('getSchema()', () => {
63
- it('should return schema object if set', async () => {
64
- await hs.setSchema('temperature', 'https://example.com/schemas/temperature.json');
65
-
66
- const schema = await hs.getSchema('temperature');
67
- expect(schema).not.toBe(null);
68
- expect(typeof schema).toBe('object');
69
- });
70
-
71
- it('should return null if schema not set', async () => {
72
- const schema = await hs.getSchema('nonexistent');
73
- expect(schema).toBe(null);
74
- });
75
-
76
- it('should return URI reference schema object', async () => {
77
- await hs.setSchema('temperature', 'https://example.com/schemas/temperature.json');
78
-
79
- const schema = await hs.getSchema('temperature');
80
- // URI schemas are stored as {$ref: uri}
81
- expect(schema).toHaveProperty('$ref');
82
- expect(schema.$ref).toBe('https://example.com/schemas/temperature.json');
83
- });
84
- });
85
-
86
- describe('clearSchema()', () => {
87
- it('should remove schema from lens', async () => {
88
- await hs.setSchema('temperature', 'https://example.com/schemas/temperature.json');
89
-
90
- await expect(
91
- hs.clearSchema('temperature')
92
- ).resolves.toBeUndefined();
93
-
94
- const schema = await hs.getSchema('temperature');
95
- expect(schema).toBe(null);
96
- });
97
-
98
- it('should be idempotent (no error if schema not set)', async () => {
99
- await expect(
100
- hs.clearSchema('nonexistent')
101
- ).resolves.toBeUndefined();
102
- });
103
-
104
- it('should clear schema without affecting other lenses', async () => {
105
- await hs.setSchema('temperature', 'https://example.com/schemas/temperature.json');
106
- await hs.setSchema('humidity', 'https://example.com/schemas/humidity.json');
107
-
108
- await hs.clearSchema('temperature');
109
-
110
- expect(await hs.getSchema('temperature')).toBe(null);
111
- expect(await hs.getSchema('humidity')).not.toBe(null);
112
- });
113
- });
114
- });
@@ -1,217 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import HoloSphere from '../../../src/index.js';
3
-
4
- describe('Contract: Social Protocol Operations', () => {
5
- let hs;
6
- let testHolonId;
7
-
8
- beforeEach(async () => {
9
- hs = new HoloSphere({ relays: [], appName: 'test-social' });
10
- testHolonId = await hs.toHolon(37.7749, -122.4194, 9);
11
- });
12
-
13
- describe('publishNostr()', () => {
14
- it('should publish valid Nostr event', async () => {
15
- const event = {
16
- kind: 1,
17
- content: 'Hello from HoloSphere!',
18
- tags: [['t', 'holosphere']],
19
- created_at: Math.floor(Date.now() / 1000)
20
- };
21
-
22
- const success = await hs.publishNostr(event, testHolonId);
23
- expect(success).toBe(true);
24
- });
25
-
26
- it('should use default lens name "social" when not specified', async () => {
27
- const event = {
28
- kind: 1,
29
- content: 'Test post',
30
- tags: [],
31
- created_at: Math.floor(Date.now() / 1000)
32
- };
33
-
34
- const success = await hs.publishNostr(event, testHolonId);
35
- expect(success).toBe(true);
36
- });
37
-
38
- it('should accept custom lens name', async () => {
39
- const event = {
40
- kind: 1,
41
- content: 'Test post',
42
- tags: [],
43
- created_at: Math.floor(Date.now() / 1000)
44
- };
45
-
46
- const success = await hs.publishNostr(event, testHolonId, 'custom-lens');
47
- expect(success).toBe(true);
48
- });
49
-
50
- it('should throw ValidationError for invalid event format', async () => {
51
- const invalidEvent = {
52
- kind: 'invalid',
53
- content: 123
54
- };
55
-
56
- await expect(
57
- hs.publishNostr(invalidEvent, testHolonId)
58
- ).rejects.toThrow('ValidationError');
59
- });
60
-
61
- it('should validate NIP-01 format', async () => {
62
- const event = {
63
- kind: 1,
64
- content: 'Valid NIP-01 event',
65
- tags: [['e', 'event-id'], ['p', 'pubkey']],
66
- created_at: Math.floor(Date.now() / 1000)
67
- };
68
-
69
- const success = await hs.publishNostr(event, testHolonId);
70
- expect(success).toBe(true);
71
- });
72
-
73
- it('should automatically sign event', async () => {
74
- const event = {
75
- kind: 1,
76
- content: 'Auto-signed event',
77
- tags: [],
78
- created_at: Math.floor(Date.now() / 1000)
79
- };
80
-
81
- const success = await hs.publishNostr(event, testHolonId);
82
- expect(success).toBe(true);
83
-
84
- // Verify event was signed
85
- const published = await hs.querySocial(testHolonId, { protocol: 'nostr' });
86
- expect(published[0]).toHaveProperty('sig');
87
- expect(published[0]).toHaveProperty('pubkey');
88
- });
89
- });
90
-
91
- describe('publishActivityPub()', () => {
92
- it('should publish valid ActivityPub object', async () => {
93
- const object = {
94
- '@context': 'https://www.w3.org/ns/activitystreams',
95
- type: 'Note',
96
- content: 'Hello from HoloSphere!',
97
- published: new Date().toISOString()
98
- };
99
-
100
- const success = await hs.publishActivityPub(object, testHolonId);
101
- expect(success).toBe(true);
102
- });
103
-
104
- it('should use default lens name "social" when not specified', async () => {
105
- const object = {
106
- '@context': 'https://www.w3.org/ns/activitystreams',
107
- type: 'Note',
108
- content: 'Test note'
109
- };
110
-
111
- const success = await hs.publishActivityPub(object, testHolonId);
112
- expect(success).toBe(true);
113
- });
114
-
115
- it('should accept custom lens name', async () => {
116
- const object = {
117
- '@context': 'https://www.w3.org/ns/activitystreams',
118
- type: 'Note',
119
- content: 'Test note'
120
- };
121
-
122
- const success = await hs.publishActivityPub(object, testHolonId, 'custom-lens');
123
- expect(success).toBe(true);
124
- });
125
-
126
- it('should throw ValidationError for invalid object format', async () => {
127
- const invalidObject = {
128
- type: 'Invalid',
129
- content: 123
130
- };
131
-
132
- await expect(
133
- hs.publishActivityPub(invalidObject, testHolonId)
134
- ).rejects.toThrow('ValidationError');
135
- });
136
-
137
- it('should support different ActivityPub types', async () => {
138
- const article = {
139
- '@context': 'https://www.w3.org/ns/activitystreams',
140
- type: 'Article',
141
- name: 'Test Article',
142
- content: 'Article content'
143
- };
144
-
145
- const success = await hs.publishActivityPub(article, testHolonId);
146
- expect(success).toBe(true);
147
- });
148
- });
149
-
150
- describe('querySocial()', () => {
151
- it('should return array of social content', async () => {
152
- const nostrEvent = {
153
- kind: 1,
154
- content: 'Test',
155
- tags: [],
156
- created_at: Math.floor(Date.now() / 1000)
157
- };
158
-
159
- await hs.publishNostr(nostrEvent, testHolonId);
160
-
161
- const results = await hs.querySocial(testHolonId);
162
- expect(Array.isArray(results)).toBe(true);
163
- });
164
-
165
- it('should filter by protocol: nostr', async () => {
166
- const results = await hs.querySocial(testHolonId, { protocol: 'nostr' });
167
- expect(Array.isArray(results)).toBe(true);
168
- });
169
-
170
- it('should filter by protocol: activitypub', async () => {
171
- const results = await hs.querySocial(testHolonId, { protocol: 'activitypub' });
172
- expect(Array.isArray(results)).toBe(true);
173
- });
174
-
175
- it('should filter by protocol: all (default)', async () => {
176
- const results = await hs.querySocial(testHolonId, { protocol: 'all' });
177
- expect(Array.isArray(results)).toBe(true);
178
- });
179
-
180
- it('should filter by accessLevel: public (default)', async () => {
181
- const results = await hs.querySocial(testHolonId, { accessLevel: 'public' });
182
- expect(Array.isArray(results)).toBe(true);
183
- });
184
-
185
- it('should filter by accessLevel: protected', async () => {
186
- const results = await hs.querySocial(testHolonId, { accessLevel: 'protected' });
187
- expect(Array.isArray(results)).toBe(true);
188
- });
189
-
190
- it('should accept custom lensName option', async () => {
191
- const results = await hs.querySocial(testHolonId, { lensName: 'custom-lens' });
192
- expect(Array.isArray(results)).toBe(true);
193
- });
194
-
195
- it('should return empty array if no content exists', async () => {
196
- const emptyHolon = await hs.toHolon(40.7128, -74.0060, 9);
197
- const results = await hs.querySocial(emptyHolon);
198
- expect(results).toEqual([]);
199
- });
200
-
201
- it('should include cryptographic signatures in results', async () => {
202
- const event = {
203
- kind: 1,
204
- content: 'Signed content',
205
- tags: [],
206
- created_at: Math.floor(Date.now() / 1000)
207
- };
208
-
209
- await hs.publishNostr(event, testHolonId);
210
-
211
- const results = await hs.querySocial(testHolonId, { protocol: 'nostr' });
212
- if (results.length > 0) {
213
- expect(results[0]).toHaveProperty('sig');
214
- }
215
- });
216
- });
217
- });
@@ -1,110 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import HoloSphere from '../../../src/index.js';
3
-
4
- describe('Contract: Spatial Operations', () => {
5
- const hs = new HoloSphere({ relays: [], appName: 'test-spatial' });
6
-
7
- describe('toHolon()', () => {
8
- it('should convert valid coordinates to H3 string', async () => {
9
- const holonId = await hs.toHolon(37.7749, -122.4194, 9);
10
- expect(typeof holonId).toBe('string');
11
- expect(holonId).toMatch(/^8[0-9a-f]+$/);
12
- expect(holonId.length).toBeGreaterThanOrEqual(15);
13
- });
14
-
15
- it('should throw RangeError for latitude out of bounds', async () => {
16
- await expect(hs.toHolon(91, -122.4194, 9)).rejects.toThrow(RangeError);
17
- await expect(hs.toHolon(-91, -122.4194, 9)).rejects.toThrow(RangeError);
18
- });
19
-
20
- it('should throw RangeError for longitude out of bounds', async () => {
21
- await expect(hs.toHolon(37.7749, 181, 9)).rejects.toThrow(RangeError);
22
- await expect(hs.toHolon(37.7749, -181, 9)).rejects.toThrow(RangeError);
23
- });
24
-
25
- it('should throw RangeError for invalid resolution', async () => {
26
- await expect(hs.toHolon(37.7749, -122.4194, -1)).rejects.toThrow(RangeError);
27
- await expect(hs.toHolon(37.7749, -122.4194, 16)).rejects.toThrow(RangeError);
28
- });
29
-
30
- it('should handle resolution 0 (largest hexagons)', async () => {
31
- const holonId = await hs.toHolon(37.7749, -122.4194, 0);
32
- expect(typeof holonId).toBe('string');
33
- });
34
-
35
- it('should handle resolution 15 (smallest hexagons)', async () => {
36
- const holonId = await hs.toHolon(37.7749, -122.4194, 15);
37
- expect(typeof holonId).toBe('string');
38
- });
39
- });
40
-
41
- describe('getParents()', () => {
42
- it('should return array of parent H3 IDs', async () => {
43
- const holonId = await hs.toHolon(37.7749, -122.4194, 9);
44
- const parents = await hs.getParents(holonId);
45
- expect(Array.isArray(parents)).toBe(true);
46
- expect(parents.length).toBeGreaterThan(0);
47
- parents.forEach(parent => {
48
- expect(typeof parent).toBe('string');
49
- expect(parent).toMatch(/^8[0-9a-f]+$/);
50
- });
51
- });
52
-
53
- it('should return parents up to maxResolution', async () => {
54
- const holonId = await hs.toHolon(37.7749, -122.4194, 9);
55
- const parents = await hs.getParents(holonId, 5);
56
- expect(Array.isArray(parents)).toBe(true);
57
- // Should stop at resolution 5
58
- });
59
-
60
- it('should throw Error for invalid H3 ID', async () => {
61
- await expect(hs.getParents('invalid')).rejects.toThrow(Error);
62
- });
63
-
64
- it('should return empty array for resolution 0 holon', async () => {
65
- const holonId = await hs.toHolon(37.7749, -122.4194, 0);
66
- const parents = await hs.getParents(holonId);
67
- expect(parents).toEqual([]);
68
- });
69
- });
70
-
71
- describe('getChildren()', () => {
72
- it('should return 7 children for valid H3 ID', async () => {
73
- const holonId = await hs.toHolon(37.7749, -122.4194, 9);
74
- const children = await hs.getChildren(holonId);
75
- expect(Array.isArray(children)).toBe(true);
76
- expect(children.length).toBe(7);
77
- children.forEach(child => {
78
- expect(typeof child).toBe('string');
79
- expect(child).toMatch(/^8[0-9a-f]+$/);
80
- });
81
- });
82
-
83
- it('should throw Error for invalid H3 ID', async () => {
84
- await expect(hs.getChildren('invalid')).rejects.toThrow(Error);
85
- });
86
-
87
- it('should throw Error for resolution 15 (no children possible)', async () => {
88
- const holonId = await hs.toHolon(37.7749, -122.4194, 15);
89
- await expect(hs.getChildren(holonId)).rejects.toThrow(Error);
90
- });
91
- });
92
-
93
- describe('isValidH3()', () => {
94
- it('should return true for valid H3 ID', () => {
95
- const result = hs.isValidH3('8928342e20fffff');
96
- expect(result).toBe(true);
97
- });
98
-
99
- it('should return false for invalid string', () => {
100
- const result = hs.isValidH3('invalid');
101
- expect(result).toBe(false);
102
- });
103
-
104
- it('should return false for non-string input', () => {
105
- expect(hs.isValidH3(123)).toBe(false);
106
- expect(hs.isValidH3(null)).toBe(false);
107
- expect(hs.isValidH3(undefined)).toBe(false);
108
- });
109
- });
110
- });
@@ -1,128 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import HoloSphere from '../../../src/index.js';
3
-
4
- describe('Contract: Subscription Operations', () => {
5
- let hs;
6
- let testHolonId;
7
-
8
- beforeEach(async () => {
9
- hs = new HoloSphere({ relays: [], appName: 'test-subscriptions' });
10
- testHolonId = await hs.toHolon(37.7749, -122.4194, 9);
11
- });
12
-
13
- describe('subscribe()', () => {
14
- it('should return subscription object with unsubscribe method', () => {
15
- const callback = vi.fn();
16
- const subscription = hs.subscribe(testHolonId, 'temperature', callback);
17
-
18
- expect(subscription).toHaveProperty('unsubscribe');
19
- expect(typeof subscription.unsubscribe).toBe('function');
20
- });
21
-
22
- it('should invoke callback on data write', async () => {
23
- const callback = vi.fn();
24
- const subscription = hs.subscribe(testHolonId, 'temperature', callback);
25
-
26
- await hs.write(testHolonId, 'temperature', {
27
- id: 'sensor-001',
28
- value: 72.5
29
- });
30
-
31
- // Wait for async callback
32
- await new Promise(resolve => setTimeout(resolve, 100));
33
-
34
- expect(callback).toHaveBeenCalled();
35
- subscription.unsubscribe();
36
- });
37
-
38
- it('should invoke callback on data update', async () => {
39
- await hs.write(testHolonId, 'temperature', { id: 'sensor-001', value: 72 });
40
-
41
- const callback = vi.fn();
42
- const subscription = hs.subscribe(testHolonId, 'temperature', callback);
43
-
44
- await hs.update(testHolonId, 'temperature', 'sensor-001', { value: 73 });
45
-
46
- await new Promise(resolve => setTimeout(resolve, 100));
47
-
48
- expect(callback).toHaveBeenCalled();
49
- subscription.unsubscribe();
50
- });
51
-
52
- it('should stop callbacks after unsubscribe', async () => {
53
- const callback = vi.fn();
54
- const subscription = hs.subscribe(testHolonId, 'temperature', callback);
55
-
56
- subscription.unsubscribe();
57
-
58
- await hs.write(testHolonId, 'temperature', { id: 'sensor-001', value: 72 });
59
- await new Promise(resolve => setTimeout(resolve, 100));
60
-
61
- expect(callback).not.toHaveBeenCalled();
62
- });
63
-
64
- it('should accept includeFederated option', () => {
65
- const callback = vi.fn();
66
- const subscription = hs.subscribe(
67
- testHolonId,
68
- 'temperature',
69
- callback,
70
- { includeFederated: true }
71
- );
72
-
73
- expect(subscription).toHaveProperty('unsubscribe');
74
- subscription.unsubscribe();
75
- });
76
-
77
- it('should accept throttle option', () => {
78
- const callback = vi.fn();
79
- const subscription = hs.subscribe(
80
- testHolonId,
81
- 'temperature',
82
- callback,
83
- { throttle: 1000 }
84
- );
85
-
86
- expect(subscription).toHaveProperty('unsubscribe');
87
- subscription.unsubscribe();
88
- });
89
-
90
- it('should accept filter option', () => {
91
- const callback = vi.fn();
92
- const subscription = hs.subscribe(
93
- testHolonId,
94
- 'temperature',
95
- callback,
96
- { filter: (data) => data.value > 75 }
97
- );
98
-
99
- expect(subscription).toHaveProperty('unsubscribe');
100
- subscription.unsubscribe();
101
- });
102
-
103
- it('should throw TypeError if callback is not a function', () => {
104
- expect(() => {
105
- hs.subscribe(testHolonId, 'temperature', 'not-a-function');
106
- }).toThrow(TypeError);
107
- });
108
-
109
- it('should pass data and key to callback', async () => {
110
- const callback = vi.fn();
111
- const subscription = hs.subscribe(testHolonId, 'temperature', callback);
112
-
113
- await hs.write(testHolonId, 'temperature', {
114
- id: 'sensor-001',
115
- value: 72.5
116
- });
117
-
118
- await new Promise(resolve => setTimeout(resolve, 100));
119
-
120
- expect(callback).toHaveBeenCalledWith(
121
- expect.objectContaining({ id: 'sensor-001', value: 72.5 }),
122
- expect.any(String)
123
- );
124
-
125
- subscription.unsubscribe();
126
- });
127
- });
128
- });