holosphere 1.1.20 → 2.0.0-alpha0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/.eslintrc.json +16 -0
- package/.prettierrc.json +7 -0
- package/README.md +483 -367
- package/bin/holosphere-activitypub.js +158 -0
- package/cleanup-test-data.js +204 -0
- package/examples/demo.html +1333 -0
- package/examples/example-bot.js +197 -0
- package/package.json +47 -87
- package/scripts/check-bundle-size.js +54 -0
- package/scripts/check-quest-ids.js +77 -0
- package/scripts/import-holons.js +578 -0
- package/scripts/publish-to-relay.js +101 -0
- package/scripts/read-example.js +186 -0
- package/scripts/relay-diagnostic.js +59 -0
- package/scripts/relay-example.js +179 -0
- package/scripts/resync-to-relay.js +245 -0
- package/scripts/revert-import.js +196 -0
- package/scripts/test-hybrid-mode.js +108 -0
- package/scripts/test-local-storage.js +63 -0
- package/scripts/test-nostr-direct.js +55 -0
- package/scripts/test-read-data.js +45 -0
- package/scripts/test-write-read.js +63 -0
- package/scripts/verify-import.js +95 -0
- package/scripts/verify-relay-data.js +139 -0
- package/src/ai/aggregation.js +319 -0
- package/src/ai/breakdown.js +511 -0
- package/src/ai/classifier.js +217 -0
- package/src/ai/council.js +228 -0
- package/src/ai/embeddings.js +279 -0
- package/src/ai/federation-ai.js +324 -0
- package/src/ai/h3-ai.js +955 -0
- package/src/ai/index.js +112 -0
- package/src/ai/json-ops.js +225 -0
- package/src/ai/llm-service.js +205 -0
- package/src/ai/nl-query.js +223 -0
- package/src/ai/relationships.js +353 -0
- package/src/ai/schema-extractor.js +218 -0
- package/src/ai/spatial.js +293 -0
- package/src/ai/tts.js +194 -0
- package/src/content/social-protocols.js +168 -0
- package/src/core/holosphere.js +273 -0
- package/src/crypto/secp256k1.js +259 -0
- package/src/federation/discovery.js +334 -0
- package/src/federation/hologram.js +1042 -0
- package/src/federation/registry.js +386 -0
- package/src/hierarchical/upcast.js +110 -0
- package/src/index.js +2669 -0
- package/src/schema/validator.js +91 -0
- package/src/spatial/h3-operations.js +110 -0
- package/src/storage/backend-factory.js +125 -0
- package/src/storage/backend-interface.js +142 -0
- package/src/storage/backends/activitypub/server.js +653 -0
- package/src/storage/backends/activitypub-backend.js +272 -0
- package/src/storage/backends/gundb-backend.js +233 -0
- package/src/storage/backends/nostr-backend.js +136 -0
- package/src/storage/filesystem-storage-browser.js +41 -0
- package/src/storage/filesystem-storage.js +138 -0
- package/src/storage/global-tables.js +81 -0
- package/src/storage/gun-async.js +281 -0
- package/src/storage/gun-wrapper.js +221 -0
- package/src/storage/indexeddb-storage.js +122 -0
- package/src/storage/key-storage-simple.js +76 -0
- package/src/storage/key-storage.js +136 -0
- package/src/storage/memory-storage.js +59 -0
- package/src/storage/migration.js +338 -0
- package/src/storage/nostr-async.js +811 -0
- package/src/storage/nostr-client.js +939 -0
- package/src/storage/nostr-wrapper.js +211 -0
- package/src/storage/outbox-queue.js +208 -0
- package/src/storage/persistent-storage.js +109 -0
- package/src/storage/sync-service.js +164 -0
- package/src/subscriptions/manager.js +142 -0
- package/test-ai-real-api.js +202 -0
- package/tests/unit/ai/aggregation.test.js +295 -0
- package/tests/unit/ai/breakdown.test.js +446 -0
- package/tests/unit/ai/classifier.test.js +294 -0
- package/tests/unit/ai/council.test.js +262 -0
- package/tests/unit/ai/embeddings.test.js +384 -0
- package/tests/unit/ai/federation-ai.test.js +344 -0
- package/tests/unit/ai/h3-ai.test.js +458 -0
- package/tests/unit/ai/index.test.js +304 -0
- package/tests/unit/ai/json-ops.test.js +307 -0
- package/tests/unit/ai/llm-service.test.js +390 -0
- package/tests/unit/ai/nl-query.test.js +383 -0
- package/tests/unit/ai/relationships.test.js +311 -0
- package/tests/unit/ai/schema-extractor.test.js +384 -0
- package/tests/unit/ai/spatial.test.js +279 -0
- package/tests/unit/ai/tts.test.js +279 -0
- package/tests/unit/content.test.js +332 -0
- package/tests/unit/contract/core.test.js +88 -0
- package/tests/unit/contract/crypto.test.js +198 -0
- package/tests/unit/contract/data.test.js +223 -0
- package/tests/unit/contract/federation.test.js +181 -0
- package/tests/unit/contract/hierarchical.test.js +113 -0
- package/tests/unit/contract/schema.test.js +114 -0
- package/tests/unit/contract/social.test.js +217 -0
- package/tests/unit/contract/spatial.test.js +110 -0
- package/tests/unit/contract/subscriptions.test.js +128 -0
- package/tests/unit/contract/utils.test.js +159 -0
- package/tests/unit/core.test.js +152 -0
- package/tests/unit/crypto.test.js +328 -0
- package/tests/unit/federation.test.js +234 -0
- package/tests/unit/gun-async.test.js +252 -0
- package/tests/unit/hierarchical.test.js +399 -0
- package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
- package/tests/unit/integration/scenario-02-federation.test.js +76 -0
- package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
- package/tests/unit/integration/scenario-04-validation.test.js +129 -0
- package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
- package/tests/unit/integration/scenario-06-social.test.js +135 -0
- package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
- package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
- package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
- package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
- package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
- package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
- package/tests/unit/performance/benchmark.test.js +85 -0
- package/tests/unit/schema.test.js +213 -0
- package/tests/unit/spatial.test.js +158 -0
- package/tests/unit/storage.test.js +195 -0
- package/tests/unit/subscriptions.test.js +328 -0
- package/tests/unit/test-data-permanence-debug.js +197 -0
- package/tests/unit/test-data-permanence.js +340 -0
- package/tests/unit/test-key-persistence-fixed.js +148 -0
- package/tests/unit/test-key-persistence.js +172 -0
- package/tests/unit/test-relay-permanence.js +376 -0
- package/tests/unit/test-second-node.js +95 -0
- package/tests/unit/test-simple-write.js +89 -0
- package/vite.config.js +49 -0
- package/vitest.config.js +20 -0
- package/FEDERATION.md +0 -213
- package/compute.js +0 -298
- package/content.js +0 -980
- package/federation.js +0 -1234
- package/global.js +0 -736
- package/hexlib.js +0 -335
- package/hologram.js +0 -183
- package/holosphere-bundle.esm.js +0 -33256
- package/holosphere-bundle.js +0 -33287
- package/holosphere-bundle.min.js +0 -39
- package/holosphere.d.ts +0 -601
- package/holosphere.js +0 -719
- package/node.js +0 -246
- package/schema.js +0 -139
- package/utils.js +0 -302
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import HoloSphere from '../../src/index.js';
|
|
3
|
+
|
|
4
|
+
describe('Unit: Hierarchical Module (T033.5)', () => {
|
|
5
|
+
let hs;
|
|
6
|
+
let testHolonId;
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
hs = new HoloSphere({ relays: [], appName: 'test-hierarchical-unit' });
|
|
10
|
+
testHolonId = await hs.toHolon(37.7749, -122.4194, 10);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('Upcast operation with summarize mode', () => {
|
|
14
|
+
it('should upcast with summarize operation', async () => {
|
|
15
|
+
await hs.write(testHolonId, 'events', {
|
|
16
|
+
id: 'event-001',
|
|
17
|
+
name: 'Test Event',
|
|
18
|
+
attendees: 25
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const success = await hs.upcast(testHolonId, 'events', 'event-001', {
|
|
22
|
+
operation: 'summarize'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(success).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should create count summary in parent holons', async () => {
|
|
29
|
+
await hs.write(testHolonId, 'items', {
|
|
30
|
+
id: 'item-001',
|
|
31
|
+
value: 'data'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await hs.upcast(testHolonId, 'items', 'item-001', {
|
|
35
|
+
operation: 'summarize',
|
|
36
|
+
maxLevel: 1
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const parents = await hs.getParents(testHolonId);
|
|
40
|
+
if (parents.length > 0) {
|
|
41
|
+
const parentData = await hs.read(parents[0], 'items');
|
|
42
|
+
expect(Array.isArray(parentData)).toBe(true);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should increment count on multiple upcasts', async () => {
|
|
47
|
+
await hs.write(testHolonId, 'counts', { id: 'c1', value: 1 });
|
|
48
|
+
await hs.write(testHolonId, 'counts', { id: 'c2', value: 2 });
|
|
49
|
+
|
|
50
|
+
await hs.upcast(testHolonId, 'counts', 'c1', { operation: 'summarize' });
|
|
51
|
+
await hs.upcast(testHolonId, 'counts', 'c2', { operation: 'summarize' });
|
|
52
|
+
|
|
53
|
+
// Parent should have count of 2
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('Upcast operation with aggregate mode', () => {
|
|
58
|
+
it('should upcast with aggregate operation', async () => {
|
|
59
|
+
await hs.write(testHolonId, 'stats', {
|
|
60
|
+
id: 'stat-001',
|
|
61
|
+
total: 100,
|
|
62
|
+
count: 5
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const success = await hs.upcast(testHolonId, 'stats', 'stat-001', {
|
|
66
|
+
operation: 'aggregate'
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(success).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should merge object fields in parent holons', async () => {
|
|
73
|
+
await hs.write(testHolonId, 'data', {
|
|
74
|
+
id: 'data-001',
|
|
75
|
+
values: { a: 1, b: 2 }
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await hs.upcast(testHolonId, 'data', 'data-001', {
|
|
79
|
+
operation: 'aggregate',
|
|
80
|
+
maxLevel: 1
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const parents = await hs.getParents(testHolonId);
|
|
84
|
+
if (parents.length > 0) {
|
|
85
|
+
const parentData = await hs.read(parents[0], 'data');
|
|
86
|
+
expect(Array.isArray(parentData)).toBe(true);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should combine multiple aggregates', async () => {
|
|
91
|
+
await hs.write(testHolonId, 'totals', {
|
|
92
|
+
id: 't1',
|
|
93
|
+
sum: 10,
|
|
94
|
+
count: 2
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await hs.write(testHolonId, 'totals', {
|
|
98
|
+
id: 't2',
|
|
99
|
+
sum: 15,
|
|
100
|
+
count: 3
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await hs.upcast(testHolonId, 'totals', 't1', { operation: 'aggregate' });
|
|
104
|
+
await hs.upcast(testHolonId, 'totals', 't2', { operation: 'aggregate' });
|
|
105
|
+
|
|
106
|
+
// Parent should have merged aggregates
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('Upcast operation with concatenate mode', () => {
|
|
111
|
+
it('should upcast with concatenate operation (default)', async () => {
|
|
112
|
+
await hs.write(testHolonId, 'lists', {
|
|
113
|
+
id: 'list-001',
|
|
114
|
+
items: [1, 2, 3]
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const success = await hs.upcast(testHolonId, 'lists', 'list-001', {
|
|
118
|
+
operation: 'concatenate'
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(success).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should use concatenate as default operation', async () => {
|
|
125
|
+
await hs.write(testHolonId, 'arrays', {
|
|
126
|
+
id: 'array-001',
|
|
127
|
+
data: ['a', 'b', 'c']
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const success = await hs.upcast(testHolonId, 'arrays', 'array-001');
|
|
131
|
+
|
|
132
|
+
expect(success).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should concatenate arrays in parent holons', async () => {
|
|
136
|
+
await hs.write(testHolonId, 'sequences', {
|
|
137
|
+
id: 'seq-001',
|
|
138
|
+
values: [1, 2]
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await hs.upcast(testHolonId, 'sequences', 'seq-001', {
|
|
142
|
+
operation: 'concatenate',
|
|
143
|
+
maxLevel: 1
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const parents = await hs.getParents(testHolonId);
|
|
147
|
+
if (parents.length > 0) {
|
|
148
|
+
const parentData = await hs.read(parents[0], 'sequences');
|
|
149
|
+
expect(Array.isArray(parentData)).toBe(true);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should handle multiple concatenations', async () => {
|
|
154
|
+
await hs.write(testHolonId, 'combined', {
|
|
155
|
+
id: 'c1',
|
|
156
|
+
items: ['x', 'y']
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await hs.write(testHolonId, 'combined', {
|
|
160
|
+
id: 'c2',
|
|
161
|
+
items: ['z']
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
await hs.upcast(testHolonId, 'combined', 'c1', { operation: 'concatenate' });
|
|
165
|
+
await hs.upcast(testHolonId, 'combined', 'c2', { operation: 'concatenate' });
|
|
166
|
+
|
|
167
|
+
// Parent should have concatenated arrays
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('MaxLevel limit enforcement (default: 3 levels)', () => {
|
|
172
|
+
it('should respect maxLevel limit', async () => {
|
|
173
|
+
await hs.write(testHolonId, 'limited', {
|
|
174
|
+
id: 'lim-001',
|
|
175
|
+
data: 'test'
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const success = await hs.upcast(testHolonId, 'limited', 'lim-001', {
|
|
179
|
+
maxLevel: 2
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(success).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should use default maxLevel of 3 when not specified', async () => {
|
|
186
|
+
await hs.write(testHolonId, 'default', {
|
|
187
|
+
id: 'def-001',
|
|
188
|
+
data: 'test'
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const success = await hs.upcast(testHolonId, 'default', 'def-001');
|
|
192
|
+
|
|
193
|
+
expect(success).toBe(true);
|
|
194
|
+
// Should propagate up 3 levels by default
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should stop at maxLevel', async () => {
|
|
198
|
+
await hs.write(testHolonId, 'stop', {
|
|
199
|
+
id: 'stop-001',
|
|
200
|
+
data: 'test'
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await hs.upcast(testHolonId, 'stop', 'stop-001', { maxLevel: 1 });
|
|
204
|
+
|
|
205
|
+
const parents = await hs.getParents(testHolonId);
|
|
206
|
+
|
|
207
|
+
// Data should be in immediate parent
|
|
208
|
+
if (parents.length > 0) {
|
|
209
|
+
const parent1Data = await hs.read(parents[0], 'stop');
|
|
210
|
+
expect(Array.isArray(parent1Data)).toBe(true);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Data should NOT be in grandparent (beyond maxLevel)
|
|
214
|
+
if (parents.length > 1) {
|
|
215
|
+
const parent2Data = await hs.read(parents[1], 'stop');
|
|
216
|
+
const hasUpcastData = parent2Data.some(d => d.id === 'stop-001');
|
|
217
|
+
expect(hasUpcastData).toBe(false);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should allow unlimited levels with maxLevel: Infinity', async () => {
|
|
222
|
+
await hs.write(testHolonId, 'unlimited', {
|
|
223
|
+
id: 'unl-001',
|
|
224
|
+
data: 'test'
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const success = await hs.upcast(testHolonId, 'unlimited', 'unl-001', {
|
|
228
|
+
maxLevel: Infinity
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(success).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('Parent hierarchy traversal', () => {
|
|
236
|
+
it('should traverse up parent hierarchy', async () => {
|
|
237
|
+
await hs.write(testHolonId, 'traverse', {
|
|
238
|
+
id: 'trav-001',
|
|
239
|
+
data: 'test'
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const success = await hs.upcast(testHolonId, 'traverse', 'trav-001', {
|
|
243
|
+
maxLevel: 3
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(success).toBe(true);
|
|
247
|
+
|
|
248
|
+
const parents = await hs.getParents(testHolonId);
|
|
249
|
+
expect(parents.length).toBeGreaterThan(0);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should write to each parent level', async () => {
|
|
253
|
+
await hs.write(testHolonId, 'levels', {
|
|
254
|
+
id: 'lev-001',
|
|
255
|
+
data: 'test'
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
await hs.upcast(testHolonId, 'levels', 'lev-001', { maxLevel: 2 });
|
|
259
|
+
|
|
260
|
+
const parents = await hs.getParents(testHolonId);
|
|
261
|
+
|
|
262
|
+
// Check each parent level has data
|
|
263
|
+
for (let i = 0; i < Math.min(parents.length, 2); i++) {
|
|
264
|
+
const parentData = await hs.read(parents[i], 'levels');
|
|
265
|
+
expect(Array.isArray(parentData)).toBe(true);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should use H3 parent function for traversal', async () => {
|
|
270
|
+
const parents = await hs.getParents(testHolonId);
|
|
271
|
+
|
|
272
|
+
expect(parents.length).toBeGreaterThan(0);
|
|
273
|
+
|
|
274
|
+
// Each parent should be a valid H3 ID
|
|
275
|
+
parents.forEach(parent => {
|
|
276
|
+
expect(hs.isValidH3(parent)).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe('Error: noospheric holons (no spatial hierarchy)', () => {
|
|
282
|
+
it('should throw Error for noospheric holon upcast', async () => {
|
|
283
|
+
const noospheric = 'nostr://topic/test';
|
|
284
|
+
|
|
285
|
+
await hs.write(noospheric, 'content', {
|
|
286
|
+
id: 'post-001',
|
|
287
|
+
text: 'Test post'
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
await expect(
|
|
291
|
+
hs.upcast(noospheric, 'content', 'post-001')
|
|
292
|
+
).rejects.toThrow(Error);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should throw Error for URI-based holons', async () => {
|
|
296
|
+
const uriHolon = 'concept://philosophy/ethics';
|
|
297
|
+
|
|
298
|
+
await hs.write(uriHolon, 'ideas', {
|
|
299
|
+
id: 'idea-001',
|
|
300
|
+
text: 'Test idea'
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
await expect(
|
|
304
|
+
hs.upcast(uriHolon, 'ideas', 'idea-001')
|
|
305
|
+
).rejects.toThrow(Error);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should throw Error for URL-based holons', async () => {
|
|
309
|
+
const urlHolon = 'https://example.com/topic/test';
|
|
310
|
+
|
|
311
|
+
await hs.write(urlHolon, 'content', {
|
|
312
|
+
id: 'content-001',
|
|
313
|
+
data: 'test'
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
await expect(
|
|
317
|
+
hs.upcast(urlHolon, 'content', 'content-001')
|
|
318
|
+
).rejects.toThrow(Error);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should work only with geographic (H3) holons', async () => {
|
|
322
|
+
const geographic = await hs.toHolon(37.7749, -122.4194, 10);
|
|
323
|
+
|
|
324
|
+
await hs.write(geographic, 'geo-data', {
|
|
325
|
+
id: 'geo-001',
|
|
326
|
+
value: 'test'
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
await expect(
|
|
330
|
+
hs.upcast(geographic, 'geo-data', 'geo-001')
|
|
331
|
+
).resolves.toBe(true);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe('Integration with spatial module', () => {
|
|
336
|
+
it('should use spatial module for parent resolution', async () => {
|
|
337
|
+
const parents = await hs.getParents(testHolonId);
|
|
338
|
+
|
|
339
|
+
expect(parents.length).toBeGreaterThan(0);
|
|
340
|
+
parents.forEach(parent => {
|
|
341
|
+
expect(hs.isValidH3(parent)).toBe(true);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should respect H3 hierarchy levels', async () => {
|
|
346
|
+
await hs.write(testHolonId, 'h3-test', {
|
|
347
|
+
id: 'h3-001',
|
|
348
|
+
data: 'test'
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
await hs.upcast(testHolonId, 'h3-test', 'h3-001', { maxLevel: 2 });
|
|
352
|
+
|
|
353
|
+
// Should follow H3 parent-child relationships
|
|
354
|
+
const parents = await hs.getParents(testHolonId);
|
|
355
|
+
expect(parents.length).toBeGreaterThan(0);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe('Integration with storage module', () => {
|
|
360
|
+
it('should write holograms to parent holons', async () => {
|
|
361
|
+
await hs.write(testHolonId, 'hologram-test', {
|
|
362
|
+
id: 'holo-001',
|
|
363
|
+
data: 'test'
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
await hs.upcast(testHolonId, 'hologram-test', 'holo-001', {
|
|
367
|
+
maxLevel: 1
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const parents = await hs.getParents(testHolonId);
|
|
371
|
+
if (parents.length > 0) {
|
|
372
|
+
const parentData = await hs.read(parents[0], 'hologram-test');
|
|
373
|
+
expect(Array.isArray(parentData)).toBe(true);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('should create references not copies', async () => {
|
|
378
|
+
await hs.write(testHolonId, 'ref-test', {
|
|
379
|
+
id: 'ref-001',
|
|
380
|
+
value: 'original'
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
await hs.upcast(testHolonId, 'ref-test', 'ref-001', { maxLevel: 1 });
|
|
384
|
+
|
|
385
|
+
// Update original
|
|
386
|
+
await hs.update(testHolonId, 'ref-test', 'ref-001', { value: 'updated' });
|
|
387
|
+
|
|
388
|
+
// Parent should reflect update (reference, not copy)
|
|
389
|
+
const parents = await hs.getParents(testHolonId);
|
|
390
|
+
if (parents.length > 0) {
|
|
391
|
+
const parentData = await hs.getFederatedData(parents[0], 'ref-test');
|
|
392
|
+
const item = parentData.find(d => d.id === 'ref-001');
|
|
393
|
+
if (item) {
|
|
394
|
+
expect(item.value).toBe('updated');
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import HoloSphere from '../../../src/index.js';
|
|
3
|
+
|
|
4
|
+
describe('Integration: Scenario 1 - Geographic Data Storage and Retrieval', () => {
|
|
5
|
+
let hs;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
hs = new HoloSphere({ relays: [], appName: 'scenario-01' });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should complete full geographic storage workflow', async () => {
|
|
12
|
+
// Step 1: Convert coordinates to H3 holon at resolution 9 (~1km²)
|
|
13
|
+
const sanFrancisco = await hs.toHolon(37.7749, -122.4194, 9);
|
|
14
|
+
expect(typeof sanFrancisco).toBe('string');
|
|
15
|
+
expect(sanFrancisco).toMatch(/^8[0-9a-f]+$/);
|
|
16
|
+
|
|
17
|
+
// Step 2: Store temperature data in "temperature" lens
|
|
18
|
+
const success = await hs.write(
|
|
19
|
+
sanFrancisco,
|
|
20
|
+
'temperature',
|
|
21
|
+
{
|
|
22
|
+
id: 'sensor-001',
|
|
23
|
+
celsius: 18.5,
|
|
24
|
+
timestamp: Date.now(),
|
|
25
|
+
source: 'weather-station-1'
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
expect(success).toBe(true);
|
|
29
|
+
|
|
30
|
+
// Step 3: Retrieve data by location and category
|
|
31
|
+
const temp = await hs.read(sanFrancisco, 'temperature', 'sensor-001');
|
|
32
|
+
expect(temp).toMatchObject({
|
|
33
|
+
id: 'sensor-001',
|
|
34
|
+
celsius: 18.5,
|
|
35
|
+
source: 'weather-station-1'
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Step 4: Query all temperature data in this holon
|
|
39
|
+
const allTemps = await hs.read(sanFrancisco, 'temperature');
|
|
40
|
+
expect(Array.isArray(allTemps)).toBe(true);
|
|
41
|
+
expect(allTemps.length).toBeGreaterThanOrEqual(1);
|
|
42
|
+
expect(allTemps.some(t => t.id === 'sensor-001')).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should validate coordinates converted to H3 hexagon', async () => {
|
|
46
|
+
const holonId = await hs.toHolon(37.7749, -122.4194, 9);
|
|
47
|
+
expect(hs.isValidH3(holonId)).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should store and retrieve data at specific location with category', async () => {
|
|
51
|
+
const holon = await hs.toHolon(40.7128, -74.0060, 9); // New York
|
|
52
|
+
|
|
53
|
+
await hs.write(holon, 'events', {
|
|
54
|
+
id: 'event-001',
|
|
55
|
+
name: 'Concert',
|
|
56
|
+
venue: 'Madison Square Garden'
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const event = await hs.read(holon, 'events', 'event-001');
|
|
60
|
+
expect(event.name).toBe('Concert');
|
|
61
|
+
expect(event.venue).toBe('Madison Square Garden');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should support multiple data items in same category', async () => {
|
|
65
|
+
const holon = await hs.toHolon(37.7749, -122.4194, 9);
|
|
66
|
+
|
|
67
|
+
await hs.write(holon, 'temperature', { id: 'sensor-A', celsius: 20 });
|
|
68
|
+
await hs.write(holon, 'temperature', { id: 'sensor-B', celsius: 21 });
|
|
69
|
+
await hs.write(holon, 'temperature', { id: 'sensor-C', celsius: 19 });
|
|
70
|
+
|
|
71
|
+
const all = await hs.read(holon, 'temperature');
|
|
72
|
+
expect(all.length).toBeGreaterThanOrEqual(3);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import HoloSphere from '../../../src/index.js';
|
|
3
|
+
|
|
4
|
+
describe('Integration: Scenario 2 - Hierarchical Federation with Single Source of Truth', () => {
|
|
5
|
+
let hs;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
hs = new HoloSphere({ relays: [], appName: 'scenario-02' });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should complete hierarchical federation workflow', async () => {
|
|
12
|
+
// Step 1: Create data in local area (resolution 9)
|
|
13
|
+
const localHolon = await hs.toHolon(37.7749, -122.4194, 9);
|
|
14
|
+
await hs.write(localHolon, 'events', {
|
|
15
|
+
id: 'concert-001',
|
|
16
|
+
name: 'Jazz Night',
|
|
17
|
+
venue: 'Blue Note SF'
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Step 2: Get parent holon (resolution 7 = ~5km² region)
|
|
21
|
+
const parents = await hs.getParents(localHolon, 7);
|
|
22
|
+
const regionalHolon = parents[0];
|
|
23
|
+
expect(typeof regionalHolon).toBe('string');
|
|
24
|
+
|
|
25
|
+
// Step 3: Establish federation (local → regional)
|
|
26
|
+
const fedSuccess = await hs.federate(localHolon, regionalHolon, 'events', {
|
|
27
|
+
direction: 'outbound',
|
|
28
|
+
mode: 'reference' // Uses holograms, not copies
|
|
29
|
+
});
|
|
30
|
+
expect(fedSuccess).toBe(true);
|
|
31
|
+
|
|
32
|
+
// Step 4: Query regional holon - sees local data via reference
|
|
33
|
+
const regionalEvents = await hs.getFederatedData(regionalHolon, 'events');
|
|
34
|
+
expect(Array.isArray(regionalEvents)).toBe(true);
|
|
35
|
+
expect(regionalEvents.length).toBeGreaterThanOrEqual(1);
|
|
36
|
+
expect(regionalEvents.some(e => e.id === 'concert-001')).toBe(true);
|
|
37
|
+
|
|
38
|
+
// Step 5: Update local data
|
|
39
|
+
await hs.update(localHolon, 'events', 'concert-001', {
|
|
40
|
+
name: 'Jazz Night - SOLD OUT'
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Step 6: Regional view automatically reflects change (single source of truth)
|
|
44
|
+
const updatedRegional = await hs.getFederatedData(regionalHolon, 'events');
|
|
45
|
+
const updatedEvent = updatedRegional.find(e => e.id === 'concert-001');
|
|
46
|
+
expect(updatedEvent.name).toBe('Jazz Night - SOLD OUT');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should validate federation uses references (holograms), not copies', async () => {
|
|
50
|
+
const local = await hs.toHolon(37.7749, -122.4194, 10);
|
|
51
|
+
const parents = await hs.getParents(local);
|
|
52
|
+
const regional = parents[0];
|
|
53
|
+
|
|
54
|
+
await hs.write(local, 'data', { id: 'item-1', value: 'original' });
|
|
55
|
+
await hs.federate(local, regional, 'data', { mode: 'reference' });
|
|
56
|
+
|
|
57
|
+
// Update should be reflected immediately
|
|
58
|
+
await hs.update(local, 'data', 'item-1', { value: 'updated' });
|
|
59
|
+
|
|
60
|
+
const regionalData = await hs.getFederatedData(regional, 'data');
|
|
61
|
+
const item = regionalData.find(d => d.id === 'item-1');
|
|
62
|
+
expect(item.value).toBe('updated');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should validate regional queries include federated local data', async () => {
|
|
66
|
+
const local = await hs.toHolon(40.7128, -74.0060, 9);
|
|
67
|
+
const parents = await hs.getParents(local);
|
|
68
|
+
const regional = parents[0];
|
|
69
|
+
|
|
70
|
+
await hs.write(local, 'notes', { id: 'note-1', text: 'Local note' });
|
|
71
|
+
await hs.federate(local, regional, 'notes');
|
|
72
|
+
|
|
73
|
+
const regionalNotes = await hs.getFederatedData(regional, 'notes');
|
|
74
|
+
expect(regionalNotes.some(n => n.id === 'note-1')).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import HoloSphere from '../../../src/index.js';
|
|
3
|
+
|
|
4
|
+
describe('Integration: Scenario 3 - Real-time Subscriptions', () => {
|
|
5
|
+
let hs;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
hs = new HoloSphere({ relays: [], appName: 'scenario-03' });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should complete real-time subscription workflow', async () => {
|
|
12
|
+
// Step 1: Subscribe to temperature changes
|
|
13
|
+
const holon = await hs.toHolon(37.7749, -122.4194, 9);
|
|
14
|
+
const updates = [];
|
|
15
|
+
|
|
16
|
+
const subscription = hs.subscribe(holon, 'temperature', (data, key) => {
|
|
17
|
+
updates.push({ key, data });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(subscription).toHaveProperty('unsubscribe');
|
|
21
|
+
|
|
22
|
+
// Step 2: Write new data - subscription fires
|
|
23
|
+
await hs.write(holon, 'temperature', {
|
|
24
|
+
id: 'sensor-002',
|
|
25
|
+
celsius: 19.2
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Wait for async updates
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
30
|
+
|
|
31
|
+
expect(updates.length).toBeGreaterThanOrEqual(1);
|
|
32
|
+
|
|
33
|
+
// Step 3: Update existing data - subscription fires again
|
|
34
|
+
await hs.update(holon, 'temperature', 'sensor-002', { celsius: 19.8 });
|
|
35
|
+
|
|
36
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
37
|
+
|
|
38
|
+
expect(updates.length).toBeGreaterThanOrEqual(2);
|
|
39
|
+
|
|
40
|
+
// Step 4: Unsubscribe - no more updates
|
|
41
|
+
subscription.unsubscribe();
|
|
42
|
+
|
|
43
|
+
const beforeUnsubscribe = updates.length;
|
|
44
|
+
|
|
45
|
+
await hs.write(holon, 'temperature', { id: 'sensor-003', celsius: 20.0 });
|
|
46
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
47
|
+
|
|
48
|
+
expect(updates.length).toBe(beforeUnsubscribe); // No new updates
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should validate subscriptions receive real-time updates', async () => {
|
|
52
|
+
const holon = await hs.toHolon(40.7128, -74.0060, 9);
|
|
53
|
+
const callback = vi.fn();
|
|
54
|
+
|
|
55
|
+
const sub = hs.subscribe(holon, 'events', callback);
|
|
56
|
+
|
|
57
|
+
await hs.write(holon, 'events', { id: 'event-1', name: 'Test Event' });
|
|
58
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
59
|
+
|
|
60
|
+
expect(callback).toHaveBeenCalled();
|
|
61
|
+
sub.unsubscribe();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should validate callbacks invoked on data changes', async () => {
|
|
65
|
+
const holon = await hs.toHolon(37.7749, -122.4194, 9);
|
|
66
|
+
const dataReceived = [];
|
|
67
|
+
|
|
68
|
+
const sub = hs.subscribe(holon, 'test', (data) => {
|
|
69
|
+
dataReceived.push(data);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await hs.write(holon, 'test', { id: 'item-1', value: 'A' });
|
|
73
|
+
await hs.write(holon, 'test', { id: 'item-2', value: 'B' });
|
|
74
|
+
|
|
75
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
76
|
+
|
|
77
|
+
expect(dataReceived.length).toBeGreaterThanOrEqual(2);
|
|
78
|
+
sub.unsubscribe();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should validate unsubscribe stops updates', async () => {
|
|
82
|
+
const holon = await hs.toHolon(37.7749, -122.4194, 9);
|
|
83
|
+
let callCount = 0;
|
|
84
|
+
|
|
85
|
+
const sub = hs.subscribe(holon, 'counter', () => {
|
|
86
|
+
callCount++;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await hs.write(holon, 'counter', { id: 'c1', value: 1 });
|
|
90
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
91
|
+
|
|
92
|
+
const countBeforeUnsubscribe = callCount;
|
|
93
|
+
|
|
94
|
+
sub.unsubscribe();
|
|
95
|
+
|
|
96
|
+
await hs.write(holon, 'counter', { id: 'c2', value: 2 });
|
|
97
|
+
await hs.write(holon, 'counter', { id: 'c3', value: 3 });
|
|
98
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
99
|
+
|
|
100
|
+
expect(callCount).toBe(countBeforeUnsubscribe);
|
|
101
|
+
});
|
|
102
|
+
});
|