holosphere 1.1.9 → 1.1.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "holosphere",
3
- "version": "1.1.9",
3
+ "version": "1.1.10",
4
4
  "description": "Holonic Geospatial Communication Infrastructure",
5
5
  "main": "holosphere.js",
6
6
  "types": "holosphere.d.ts",
package/test/auth.test.js CHANGED
@@ -1,10 +1,8 @@
1
- import Gun from 'gun';
2
1
  import HoloSphere from '../holosphere.js';
3
- import * as h3 from 'h3-js';
4
2
  import { jest } from '@jest/globals';
5
3
 
6
4
  // Increase timeout for all tests
7
- jest.setTimeout(3000);
5
+ jest.setTimeout(30000);
8
6
 
9
7
  describe('HoloSphere Authentication and Authorization', () => {
10
8
  let holoSphere;
@@ -16,6 +14,14 @@ describe('HoloSphere Authentication and Authorization', () => {
16
14
  beforeAll(async () => {
17
15
  holoSphere = new HoloSphere('test-app', false, null);
18
16
  strictHoloSphere = new HoloSphere('test-app-strict', true, null);
17
+ // Wait for initialization
18
+ await new Promise(resolve => setTimeout(resolve, 1000));
19
+ });
20
+
21
+ beforeEach(async () => {
22
+ // Clean state before each test
23
+ await holoSphere.deleteAll(testHolon, testLens);
24
+ await new Promise(resolve => setTimeout(resolve, 100));
19
25
  });
20
26
 
21
27
  afterEach(async () => {
@@ -37,12 +43,12 @@ describe('HoloSphere Authentication and Authorization', () => {
37
43
  await holoSphere.deleteAll(testHolon, testLens);
38
44
  await holoSphere.deleteAllGlobal('testTable');
39
45
 
40
- // Close Gun connections
41
- if (holoSphere.gun) {
42
- holoSphere.gun.off();
46
+ // Close HoloSphere instances
47
+ if (holoSphere) {
48
+ await holoSphere.close();
43
49
  }
44
- if (strictHoloSphere.gun) {
45
- strictHoloSphere.gun.off();
50
+ if (strictHoloSphere) {
51
+ await strictHoloSphere.close();
46
52
  }
47
53
 
48
54
  // Wait for connections to close
@@ -50,16 +56,25 @@ describe('HoloSphere Authentication and Authorization', () => {
50
56
  });
51
57
 
52
58
  describe('Authentication System', () => {
53
- it('should authenticate with password and store/retrieve data', async () => {
59
+ it('should authenticate with password and handle auth failures', async () => {
54
60
  const testData = { id: 'test1', value: 'private-data' };
55
61
 
56
62
  // Test storing with authentication
57
63
  await holoSphere.put(testHolon, testLens, testData, testPassword);
64
+ // Wait for data to be properly stored
65
+ await new Promise(resolve => setTimeout(resolve, 100));
58
66
 
59
- // Test retrieving with authentication
60
- const result = await holoSphere.get(testHolon, testLens, testData.id, testPassword);
61
- expect(result).toBeDefined();
62
- expect(result.value).toBe(testData.value);
67
+ // Test retrieving with wrong password
68
+ const wrongResult = await holoSphere.get(testHolon, testLens, testData.id, 'wrong_password');
69
+ expect(wrongResult).toBeNull();
70
+
71
+ // Test retrieving with no password
72
+ const noPassResult = await holoSphere.get(testHolon, testLens, testData.id);
73
+ expect(noPassResult).toBeNull();
74
+
75
+ // Test retrieving with correct password
76
+ const correctResult = await holoSphere.get(testHolon, testLens, testData.id, testPassword);
77
+ expect(correctResult).toEqual(testData); // Use full object comparison
63
78
  });
64
79
 
65
80
  it('should handle authentication errors gracefully', async () => {
@@ -103,15 +118,16 @@ describe('HoloSphere Authentication and Authorization', () => {
103
118
  const testData = { id: 'test', value: 123 };
104
119
 
105
120
  // Should work in non-strict mode without schema
106
- await expect(holoSphere.put(testHolon, testLens, testData)).resolves.toBeTruthy();
121
+ await expect(holoSphere.put(testHolon, 'nonExistentLens', testData)).resolves.toBeTruthy();
107
122
 
108
- // Delete any existing schema
109
- await strictHoloSphere.putGlobal('schemas', { id: testLens, schema: null });
123
+ // Delete any existing schema for the lens
124
+ await strictHoloSphere.putGlobal('schemas', { id: 'nonExistentLens', schema: null });
110
125
 
111
- // Should fail in strict mode without schema
112
126
  try {
113
- await strictHoloSphere.put(testHolon, testLens, testData);
114
- fail('Should have thrown an error');
127
+ // This should throw an error in strict mode
128
+ await strictHoloSphere.put(testHolon, 'nonExistentLens', testData);
129
+ // If we get here, the test should fail
130
+ expect('Expected an error').toBe('but none was thrown');
115
131
  } catch (error) {
116
132
  expect(error.message).toBe('Schema required in strict mode');
117
133
  }
@@ -113,6 +113,7 @@ describe('HoloSphere Deletion Tests', () => {
113
113
  // Verify global data exists
114
114
  const storedGlobalData = await holoSphere.getGlobal(testGlobalTable, globalData.id);
115
115
  expect(storedGlobalData).toBeDefined();
116
+ console.log(storedGlobalData);
116
117
  expect(storedGlobalData.value).toBe(globalData.value);
117
118
 
118
119
  // Delete global data
@@ -36,12 +36,12 @@ describe('HoloSphere', () => {
36
36
  await holoSphere.deleteAll(testHolon, testLens);
37
37
  await holoSphere.deleteAllGlobal('testTable');
38
38
 
39
- // Close Gun connections
40
- if (holoSphere.gun) {
41
- holoSphere.gun.off();
39
+ // Close HoloSphere instances
40
+ if (holoSphere) {
41
+ await holoSphere.close();
42
42
  }
43
- if (strictHoloSphere.gun) {
44
- strictHoloSphere.gun.off();
43
+ if (strictHoloSphere) {
44
+ await strictHoloSphere.close();
45
45
  }
46
46
 
47
47
  // Wait for connections to close
@@ -167,4 +167,188 @@ describe('HoloSphere', () => {
167
167
  expect(deletedResult).toBeNull();
168
168
  });
169
169
  });
170
+
171
+ describe('Schema Functions', () => {
172
+ test('should set and get a schema', async () => {
173
+ const schema = {
174
+ type: 'object',
175
+ properties: {
176
+ id: { type: 'string' },
177
+ value: { type: 'string' }
178
+ },
179
+ required: ['id', 'value']
180
+ };
181
+
182
+ await holoSphere.setSchema('testLens', schema);
183
+ const retrieved = await holoSphere.getSchema('testLens');
184
+
185
+ expect(retrieved).toEqual(schema);
186
+ });
187
+
188
+ test('should cache schemas when fetched', async () => {
189
+ const schema = {
190
+ type: 'object',
191
+ properties: {
192
+ id: { type: 'string' },
193
+ test: { type: 'string' }
194
+ },
195
+ required: ['id', 'test']
196
+ };
197
+
198
+ // Clear any existing cache and set up fresh schema
199
+ holoSphere.clearSchemaCache();
200
+ await holoSphere.setSchema('cacheTestLens', schema);
201
+
202
+ // Cache should be populated by setSchema
203
+ expect(holoSphere.schemaCache.has('cacheTestLens')).toBe(true);
204
+
205
+ // Save the getGlobal method to create a spy
206
+ const originalGetGlobal = holoSphere.getGlobal;
207
+ let globalCalled = false;
208
+
209
+ // Replace with a spy
210
+ holoSphere.getGlobal = async (...args) => {
211
+ globalCalled = true;
212
+ return originalGetGlobal.apply(holoSphere, args);
213
+ };
214
+
215
+ // This call should use the cache and not call getGlobal
216
+ const cachedFetch = await holoSphere.getSchema('cacheTestLens');
217
+ expect(cachedFetch).toEqual(schema);
218
+
219
+ // Verify getGlobal was not called because we used the cache
220
+ expect(globalCalled).toBe(false);
221
+
222
+ // Now force a non-cached fetch
223
+ globalCalled = false;
224
+ const forcedFetch = await holoSphere.getSchema('cacheTestLens', { useCache: false });
225
+ expect(forcedFetch).toEqual(schema);
226
+
227
+ // Verify getGlobal was called this time
228
+ expect(globalCalled).toBe(true);
229
+
230
+ // Restore original method
231
+ holoSphere.getGlobal = originalGetGlobal;
232
+ });
233
+
234
+ test('should respect cache max age', async () => {
235
+ const schema = {
236
+ type: 'object',
237
+ properties: {
238
+ id: { type: 'string' },
239
+ age: { type: 'number' }
240
+ },
241
+ required: ['id', 'age']
242
+ };
243
+
244
+ // Clear any existing cache
245
+ holoSphere.clearSchemaCache();
246
+ await holoSphere.setSchema('ageTestLens', schema);
247
+
248
+ // First fetch to populate cache
249
+ await holoSphere.getSchema('ageTestLens');
250
+
251
+ // Verify cache has the entry now
252
+ expect(holoSphere.schemaCache.has('ageTestLens')).toBe(true);
253
+
254
+ // Manually set an old timestamp on the cache entry
255
+ const oldTimestamp = Date.now() - 3700000; // Older than the default maxCacheAge
256
+ holoSphere.schemaCache.set('ageTestLens', {
257
+ schema,
258
+ timestamp: oldTimestamp
259
+ });
260
+
261
+ // Save the getGlobal method to create a spy
262
+ const originalGetGlobal = holoSphere.getGlobal;
263
+ let globalCalled = false;
264
+
265
+ // Replace with a spy
266
+ holoSphere.getGlobal = async (...args) => {
267
+ globalCalled = true;
268
+ return originalGetGlobal.apply(holoSphere, args);
269
+ };
270
+
271
+ // Call should bypass the cache due to age
272
+ const secondFetch = await holoSphere.getSchema('ageTestLens');
273
+ expect(secondFetch).toEqual(schema);
274
+
275
+ // getGlobal should have been called again
276
+ expect(globalCalled).toBe(true);
277
+
278
+ // Restore original method
279
+ holoSphere.getGlobal = originalGetGlobal;
280
+ });
281
+
282
+ test('should clear cache properly', async () => {
283
+ const schema1 = {
284
+ type: 'object',
285
+ properties: { id: { type: 'string' } },
286
+ required: ['id']
287
+ };
288
+
289
+ const schema2 = {
290
+ type: 'object',
291
+ properties: { name: { type: 'string' } },
292
+ required: ['name']
293
+ };
294
+
295
+ // Set two schemas
296
+ await holoSphere.setSchema('clearTest1', schema1);
297
+ await holoSphere.setSchema('clearTest2', schema2);
298
+
299
+ // Verify they're cached
300
+ expect(holoSphere.schemaCache.has('clearTest1')).toBe(true);
301
+ expect(holoSphere.schemaCache.has('clearTest2')).toBe(true);
302
+
303
+ // Clear one schema
304
+ holoSphere.clearSchemaCache('clearTest1');
305
+ expect(holoSphere.schemaCache.has('clearTest1')).toBe(false);
306
+ expect(holoSphere.schemaCache.has('clearTest2')).toBe(true);
307
+
308
+ // Clear all schemas
309
+ holoSphere.clearSchemaCache();
310
+ expect(holoSphere.schemaCache.has('clearTest1')).toBe(false);
311
+ expect(holoSphere.schemaCache.has('clearTest2')).toBe(false);
312
+ });
313
+
314
+ test('should provide significant performance improvement with caching', async () => {
315
+ const schema = {
316
+ type: 'object',
317
+ properties: {
318
+ id: { type: 'string' },
319
+ value: { type: 'number' },
320
+ name: { type: 'string' }
321
+ },
322
+ required: ['id', 'value']
323
+ };
324
+
325
+ // Set up the schema
326
+ await holoSphere.setSchema('perfTestLens', schema);
327
+
328
+ // Measure time without caching (force bypass)
329
+ const start1 = Date.now();
330
+ for (let i = 0; i < 100; i++) {
331
+ await holoSphere.getSchema('perfTestLens', { useCache: false });
332
+ }
333
+ const end1 = Date.now();
334
+ const timeWithoutCache = end1 - start1;
335
+
336
+ // Measure time with caching
337
+ const start2 = Date.now();
338
+ for (let i = 0; i < 100; i++) {
339
+ await holoSphere.getSchema('perfTestLens');
340
+ }
341
+ const end2 = Date.now();
342
+ const timeWithCache = end2 - start2;
343
+
344
+ console.log(`Performance comparison:
345
+ Without cache: ${timeWithoutCache}ms
346
+ With cache: ${timeWithCache}ms
347
+ Improvement factor: ${timeWithoutCache / timeWithCache}x
348
+ `);
349
+
350
+ // The cached version should be at least 2x faster
351
+ expect(timeWithCache).toBeLessThan(timeWithoutCache / 2);
352
+ });
353
+ });
170
354
  });
@@ -0,0 +1,211 @@
1
+ import HoloSphere from '../holosphere.js';
2
+ import { jest } from '@jest/globals';
3
+
4
+ // Configure timeout
5
+ jest.setTimeout(30000); // 30 second timeout
6
+
7
+ // Setup
8
+ describe('HoloSphere Reference System', () => {
9
+ let holoSphere;
10
+
11
+ beforeEach(async () => {
12
+ // Create a new HoloSphere instance for each test
13
+ holoSphere = new HoloSphere('testApp');
14
+ });
15
+
16
+ afterEach(async () => {
17
+ // Clean up after each test
18
+ if (holoSphere) {
19
+ await holoSphere.close();
20
+ // Wait for connections to close
21
+ await new Promise(resolve => setTimeout(resolve, 500));
22
+ }
23
+ });
24
+
25
+ test('should create and parse a reference correctly', async () => {
26
+ // Test data
27
+ const holon = 'testHolon';
28
+ const lens = 'testLens';
29
+ const data = {
30
+ id: 'test-data-123',
31
+ title: 'Test Data',
32
+ content: 'This is test content'
33
+ };
34
+
35
+ // Create a reference
36
+ const reference = holoSphere.createReference(holon, lens, data);
37
+
38
+ // Validate reference structure
39
+ expect(reference).toBeTruthy();
40
+ expect(reference.id).toBe(data.id);
41
+ expect(reference.soul).toBe(`testApp/${holon}/${lens}/${data.id}`);
42
+
43
+ // Test soul path parsing
44
+ const soulInfo = holoSphere.parseSoulPath(reference.soul);
45
+ expect(soulInfo).toBeTruthy();
46
+ expect(soulInfo.appname).toBe('testApp');
47
+ expect(soulInfo.holon).toBe(holon);
48
+ expect(soulInfo.lens).toBe(lens);
49
+ expect(soulInfo.key).toBe(data.id);
50
+ });
51
+
52
+ test('should detect different types of references correctly', async () => {
53
+ // Soul reference
54
+ const soulReference = {
55
+ id: 'test-data-123',
56
+ soul: 'testApp/testHolon/testLens/test-data-123'
57
+ };
58
+
59
+ // Legacy federation reference
60
+ const legacyReference = {
61
+ id: 'test-data-456',
62
+ _federation: {
63
+ isReference: true,
64
+ origin: 'originHolon',
65
+ lens: 'originLens'
66
+ }
67
+ };
68
+
69
+ // Regular data (not a reference)
70
+ const regularData = {
71
+ id: 'test-data-789',
72
+ title: 'Regular Data',
73
+ content: 'This is not a reference'
74
+ };
75
+
76
+ // Test reference detection
77
+ expect(holoSphere.isReference(soulReference)).toBe(true);
78
+ expect(holoSphere.isReference(legacyReference)).toBe(true);
79
+ expect(holoSphere.isReference(regularData)).toBe(false);
80
+ expect(holoSphere.isReference(null)).toBe(false);
81
+ expect(holoSphere.isReference(undefined)).toBe(false);
82
+ expect(holoSphere.isReference("not an object")).toBe(false);
83
+ });
84
+
85
+ test('should store and retrieve references properly', async () => {
86
+ // Original data
87
+ const originalData = {
88
+ id: 'original-123',
89
+ title: 'Original Data',
90
+ content: 'This is the original content'
91
+ };
92
+
93
+ // Store original data
94
+ await holoSphere.put('originHolon', 'testLens', originalData);
95
+
96
+ // Create a reference to the original data
97
+ const reference = holoSphere.createReference('originHolon', 'testLens', originalData);
98
+
99
+ // Store the reference in a different holon
100
+ await holoSphere.put('referenceHolon', 'testLens', reference);
101
+
102
+ // Retrieve the reference with resolution enabled (default)
103
+ const resolvedData = await holoSphere.get('referenceHolon', 'testLens', 'original-123');
104
+
105
+ // Verify the original data is present
106
+ expect(resolvedData).toBeTruthy();
107
+ expect(resolvedData.title).toBe(originalData.title);
108
+ expect(resolvedData.content).toBe(originalData.content);
109
+
110
+ // Verify the federation metadata is present
111
+ expect(resolvedData._federation).toBeTruthy();
112
+ expect(resolvedData._federation.isReference).toBe(true);
113
+ expect(resolvedData._federation.resolved).toBe(true);
114
+ expect(resolvedData._federation.soul).toBe(reference.soul);
115
+ expect(resolvedData._federation.timestamp).toBeGreaterThan(0);
116
+
117
+ // Retrieve the reference without resolution
118
+ const unresolvedData = await holoSphere.get('referenceHolon', 'testLens', 'original-123', null, {
119
+ resolveReferences: false
120
+ });
121
+
122
+ // Verify we got the actual reference
123
+ expect(unresolvedData).toBeTruthy();
124
+ expect(unresolvedData.id).toBe('original-123');
125
+ expect(unresolvedData.soul).toBe(reference.soul);
126
+ expect(unresolvedData.title).toBeUndefined();
127
+ });
128
+
129
+ test('should update original data and have references reflect the changes', async () => {
130
+ // Original data
131
+ const originalData = {
132
+ id: 'original-456',
133
+ title: 'Original Data',
134
+ content: 'This content will be updated'
135
+ };
136
+
137
+ // Store original data
138
+ await holoSphere.put('originHolon', 'testLens', originalData);
139
+
140
+ // Create a reference
141
+ const reference = holoSphere.createReference('originHolon', 'testLens', originalData);
142
+
143
+ // Store the reference in a different holon
144
+ await holoSphere.put('referenceHolon', 'testLens', reference);
145
+
146
+ // Update the original data
147
+ const updatedData = {
148
+ ...originalData,
149
+ title: 'Updated Data',
150
+ content: 'This content has been updated'
151
+ };
152
+ await holoSphere.put('originHolon', 'testLens', updatedData);
153
+
154
+ // Retrieve the reference
155
+ const resolvedData = await holoSphere.get('referenceHolon', 'testLens', 'original-456');
156
+
157
+ // Verify the reference resolves to the updated data
158
+ expect(resolvedData).toBeTruthy();
159
+ expect(resolvedData.title).toBe('Updated Data');
160
+ expect(resolvedData.content).toBe('This content has been updated');
161
+ });
162
+
163
+ test('should create and use manual references across holons', async () => {
164
+ // Original data to reference
165
+ const originalData = {
166
+ id: 'federated-123',
167
+ title: 'Federated Data',
168
+ content: 'This content should be referenced'
169
+ };
170
+
171
+ // Store original data
172
+ await holoSphere.put('sourceHolon', 'testLens', originalData);
173
+
174
+ // Create a reference to the original data
175
+ const reference = holoSphere.createReference('sourceHolon', 'testLens', originalData);
176
+
177
+ // Store reference in the target holon
178
+ await holoSphere.put('targetHolon', 'testLens', reference);
179
+
180
+ // Retrieve the reference with resolution
181
+ const resolvedData = await holoSphere.get('targetHolon', 'testLens', 'federated-123');
182
+
183
+ // Verify the reference was resolved to the original data
184
+ expect(resolvedData).toBeTruthy();
185
+ expect(resolvedData.title).toBe('Federated Data');
186
+ expect(resolvedData.content).toBe('This content should be referenced');
187
+ expect(resolvedData._federation).toBeTruthy();
188
+ expect(resolvedData._federation.isReference).toBe(true);
189
+ expect(resolvedData._federation.resolved).toBe(true);
190
+ expect(resolvedData._federation.soul).toBe(reference.soul);
191
+
192
+ // Update original data
193
+ const updatedData = {
194
+ ...originalData,
195
+ title: 'Updated Data',
196
+ content: 'This content has been updated'
197
+ };
198
+ await holoSphere.put('sourceHolon', 'testLens', updatedData);
199
+
200
+ // Add a delay to ensure data is updated
201
+ await new Promise(resolve => setTimeout(resolve, 500));
202
+
203
+ // Retrieve the reference again
204
+ const updatedResolvedData = await holoSphere.get('targetHolon', 'testLens', 'federated-123');
205
+
206
+ // Verify the update was reflected via the reference
207
+ expect(updatedResolvedData).toBeTruthy();
208
+ expect(updatedResolvedData.title).toBe('Updated Data');
209
+ expect(updatedResolvedData.content).toBe('This content has been updated');
210
+ });
211
+ });