holosphere 1.1.9 → 1.1.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.
package/node.js ADDED
@@ -0,0 +1,155 @@
1
+ // holo_node.js
2
+
3
+ /**
4
+ * Stores a specific gun node in a given holon and lens.
5
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
6
+ * @param {string} holon - The holon identifier.
7
+ * @param {string} lens - The lens under which to store the node.
8
+ * @param {object} data - The node to store.
9
+ */
10
+ export async function putNode(holoInstance, holon, lens, data) {
11
+ if (!holon || !lens || !data) {
12
+ throw new Error('putNode: Missing required parameters');
13
+ }
14
+
15
+ return new Promise((resolve, reject) => {
16
+ try {
17
+ // Remove isHologram field before storing - NO LONGER NEEDED
18
+ // if (data && data.isHologram !== undefined) {
19
+ // delete data.isHologram;
20
+ // }
21
+ holoInstance.gun.get(holoInstance.appname)
22
+ .get(holon)
23
+ .get(lens)
24
+ .get('value') // Store at 'value' key
25
+ .put(data.value, ack => { // Store the value directly
26
+ if (ack.err) {
27
+ reject(new Error(ack.err));
28
+ } else {
29
+ resolve(true);
30
+ }
31
+ });
32
+ } catch (error) {
33
+ reject(error);
34
+ }
35
+ });
36
+ }
37
+
38
+ /**
39
+ * Retrieves a specific gun node from the specified holon and lens.
40
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
41
+ * @param {string} holon - The holon identifier.
42
+ * @param {string} lens - The lens identifier.
43
+ * @param {string} key - The specific key to retrieve.
44
+ * @returns {Promise<any>} - The retrieved node or null if not found.
45
+ */
46
+ export async function getNode(holoInstance, holon, lens, key) {
47
+ if (!holon || !lens || !key) {
48
+ throw new Error('getNode: Missing required parameters');
49
+ }
50
+
51
+ return new Promise((resolve, reject) => {
52
+ try {
53
+ holoInstance.gun.get(holoInstance.appname)
54
+ .get(holon)
55
+ .get(lens)
56
+ .get(key)
57
+ .once((data) => {
58
+ if (!data) {
59
+ resolve(null);
60
+ return;
61
+ }
62
+ resolve(data); // Return the data directly
63
+ });
64
+ } catch (error) {
65
+ reject(error);
66
+ }
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Retrieves a Gun node reference using its soul path
72
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
73
+ * @param {string} soul - The soul path of the node
74
+ * @returns {Gun.ChainReference} - The Gun node reference
75
+ */
76
+ export function getNodeRef(holoInstance, soul) {
77
+ if (typeof soul !== 'string' || !soul) {
78
+ throw new Error('getNodeRef: Invalid soul parameter');
79
+ }
80
+
81
+ const parts = soul.split('/').filter(part => {
82
+ if (!part.trim() || /[<>:"/\\|?*]/.test(part)) { // Escaped backslash for regex
83
+ throw new Error('getNodeRef: Invalid path segment');
84
+ }
85
+ return part.trim();
86
+ });
87
+
88
+ if (parts.length === 0) {
89
+ throw new Error('getNodeRef: Invalid soul format');
90
+ }
91
+
92
+ let ref = holoInstance.gun.get(holoInstance.appname);
93
+ parts.forEach(part => {
94
+ ref = ref.get(part);
95
+ });
96
+ return ref;
97
+ }
98
+
99
+ /**
100
+ * Retrieves a node directly using its soul path
101
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
102
+ * @param {string} soul - The soul path of the node
103
+ * @returns {Promise<any>} - The retrieved node or null if not found.
104
+ */
105
+ export async function getNodeBySoul(holoInstance, soul) {
106
+ if (!soul) {
107
+ throw new Error('getNodeBySoul: Missing soul parameter');
108
+ }
109
+
110
+ console.log(`getNodeBySoul: Accessing soul ${soul}`);
111
+
112
+ return new Promise((resolve, reject) => {
113
+ try {
114
+ const ref = getNodeRef(holoInstance, soul); // Use the exported getNodeRef
115
+ ref.once((data) => {
116
+ console.log(`getNodeBySoul: Retrieved data:`, data);
117
+ if (!data) {
118
+ resolve(null);
119
+ return;
120
+ }
121
+ resolve(data); // Return the data directly
122
+ });
123
+ } catch (error) {
124
+ console.error(`getNodeBySoul error:`, error);
125
+ reject(error);
126
+ }
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Deletes a specific gun node from a given holon and lens.
132
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
133
+ * @param {string} holon - The holon identifier.
134
+ * @param {string} lens - The lens identifier.
135
+ * @param {string} key - The key of the node to delete.
136
+ * @returns {Promise<boolean>} - Returns true if successful
137
+ */
138
+ export async function deleteNode(holoInstance, holon, lens, key) {
139
+ if (!holon || !lens || !key) {
140
+ throw new Error('deleteNode: Missing required parameters');
141
+ }
142
+ return new Promise((resolve, reject) => {
143
+ holoInstance.gun.get(holoInstance.appname)
144
+ .get(holon)
145
+ .get(lens)
146
+ .get(key)
147
+ .put(null, ack => {
148
+ if (ack.err) {
149
+ reject(new Error(ack.err));
150
+ } else {
151
+ resolve(true);
152
+ }
153
+ });
154
+ });
155
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "holosphere",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "Holonic Geospatial Communication Infrastructure",
5
5
  "main": "holosphere.js",
6
6
  "types": "holosphere.d.ts",
@@ -24,9 +24,6 @@
24
24
  },
25
25
  "jest": {
26
26
  "testEnvironment": "node",
27
- "transform": {},
28
- "moduleNameMapper": {
29
- "^(\\.{1,2}/.*)\\.js$": "$1"
30
- }
27
+ "transform": {}
31
28
  }
32
29
  }
package/schema.js ADDED
@@ -0,0 +1,132 @@
1
+ // holo_schema.js
2
+
3
+ import Ajv2019 from 'ajv/dist/2019.js';
4
+
5
+ /**
6
+ * Sets the JSON schema for a specific lens.
7
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
8
+ * @param {string} lens - The lens identifier.
9
+ * @param {object} schema - The JSON schema to set.
10
+ * @returns {Promise<boolean>} - Resolves when the schema is set.
11
+ */
12
+ export async function setSchema(holoInstance, lens, schema) {
13
+ if (!lens || !schema) {
14
+ throw new Error('setSchema: Missing required parameters');
15
+ }
16
+
17
+ // Basic schema validation
18
+ if (!schema.type || typeof schema.type !== 'string') {
19
+ throw new Error('setSchema: Schema must have a type field');
20
+ }
21
+
22
+ const metaSchema = {
23
+ type: 'object',
24
+ required: ['type', 'properties'],
25
+ properties: {
26
+ type: { type: 'string' },
27
+ properties: {
28
+ type: 'object',
29
+ additionalProperties: {
30
+ type: 'object',
31
+ required: ['type'],
32
+ properties: {
33
+ type: { type: 'string' }
34
+ }
35
+ }
36
+ },
37
+ required: {
38
+ type: 'array',
39
+ items: { type: 'string' }
40
+ }
41
+ }
42
+ };
43
+
44
+ // Use the validator from the instance
45
+ const valid = holoInstance.validator.validate(metaSchema, schema);
46
+ if (!valid) {
47
+ throw new Error(`Invalid schema structure: ${JSON.stringify(holoInstance.validator.errors)}`);
48
+ }
49
+
50
+ if (!schema.properties || typeof schema.properties !== 'object') {
51
+ throw new Error('Schema must have properties in strict mode');
52
+ }
53
+
54
+ if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
55
+ throw new Error('Schema must have required fields in strict mode');
56
+ }
57
+
58
+ // Store schema in global table with lens as key using instance's method
59
+ await holoInstance.putGlobal('schemas', {
60
+ id: lens,
61
+ schema: schema,
62
+ timestamp: Date.now()
63
+ });
64
+
65
+ // Update the instance's cache with the new schema
66
+ holoInstance.schemaCache.set(lens, {
67
+ schema,
68
+ timestamp: Date.now()
69
+ });
70
+
71
+ return true;
72
+ }
73
+
74
+ /**
75
+ * Retrieves the JSON schema for a specific lens.
76
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
77
+ * @param {string} lens - The lens identifier.
78
+ * @param {object} [options] - Additional options
79
+ * @param {boolean} [options.useCache=true] - Whether to use the schema cache
80
+ * @param {number} [options.maxCacheAge=3600000] - Maximum cache age in milliseconds (default: 1 hour)
81
+ * @returns {Promise<object|null>} - The retrieved schema or null if not found.
82
+ */
83
+ export async function getSchema(holoInstance, lens, options = {}) {
84
+ if (!lens) {
85
+ throw new Error('getSchema: Missing lens parameter');
86
+ }
87
+
88
+ const { useCache = true, maxCacheAge = 3600000 } = options;
89
+
90
+ // Check instance's cache first if enabled
91
+ if (useCache && holoInstance.schemaCache.has(lens)) {
92
+ const cached = holoInstance.schemaCache.get(lens);
93
+ const cacheAge = Date.now() - cached.timestamp;
94
+
95
+ // Use cache if it's fresh enough
96
+ if (cacheAge < maxCacheAge) {
97
+ return cached.schema;
98
+ }
99
+ }
100
+
101
+ // Cache miss or expired, fetch from storage using instance's method
102
+ const schemaData = await holoInstance.getGlobal('schemas', lens);
103
+
104
+ if (!schemaData || !schemaData.schema) {
105
+ return null;
106
+ }
107
+
108
+ // Update instance's cache with fetched schema
109
+ holoInstance.schemaCache.set(lens, {
110
+ schema: schemaData.schema,
111
+ timestamp: Date.now()
112
+ });
113
+
114
+ return schemaData.schema;
115
+ }
116
+
117
+ /**
118
+ * Clears the schema cache or a specific schema from the cache.
119
+ * @param {HoloSphere} holoInstance - The HoloSphere instance.
120
+ * @param {string} [lens] - Optional lens to clear from cache. If not provided, clears entire cache.
121
+ * @returns {boolean} - Returns true if successful
122
+ */
123
+ export function clearSchemaCache(holoInstance, lens = null) {
124
+ if (lens) {
125
+ // Clear specific schema from instance's cache
126
+ return holoInstance.schemaCache.delete(lens);
127
+ } else {
128
+ // Clear entire instance's cache
129
+ holoInstance.schemaCache.clear();
130
+ return true;
131
+ }
132
+ }
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;
@@ -12,10 +10,22 @@ describe('HoloSphere Authentication and Authorization', () => {
12
10
  const testPassword = 'TestPass123!';
13
11
  const testHolon = 'test-holon';
14
12
  const testLens = 'test-lens';
13
+ const PUBLIC_GLOBAL_TABLE = 'publicTestTable'; // For public global data
14
+ const PRIVATE_GLOBAL_TABLE = 'veryPrivateGlobalTable'; // For all private global data tests
15
15
 
16
16
  beforeAll(async () => {
17
17
  holoSphere = new HoloSphere('test-app', false, null);
18
18
  strictHoloSphere = new HoloSphere('test-app-strict', true, null);
19
+ // Wait for initialization
20
+ await new Promise(resolve => setTimeout(resolve, 1000));
21
+ });
22
+
23
+ beforeEach(async () => {
24
+ // Clean state before each test
25
+ await holoSphere.deleteAll(testHolon, testLens);
26
+ await holoSphere.deleteAllGlobal(PUBLIC_GLOBAL_TABLE);
27
+ await holoSphere.deleteAllGlobal(PRIVATE_GLOBAL_TABLE, testPassword);
28
+ await new Promise(resolve => setTimeout(resolve, 100));
19
29
  });
20
30
 
21
31
  afterEach(async () => {
@@ -34,38 +44,70 @@ describe('HoloSphere Authentication and Authorization', () => {
34
44
 
35
45
  afterAll(async () => {
36
46
  // Clean up all test data
37
- await holoSphere.deleteAll(testHolon, testLens);
38
- await holoSphere.deleteAllGlobal('testTable');
39
-
40
- // Close Gun connections
41
- if (holoSphere.gun) {
42
- holoSphere.gun.off();
47
+ try {
48
+ if (holoSphere) {
49
+ await holoSphere.deleteAll(testHolon, testLens);
50
+ await holoSphere.deleteAllGlobal(PUBLIC_GLOBAL_TABLE);
51
+ await holoSphere.deleteAllGlobal(PRIVATE_GLOBAL_TABLE, testPassword);
52
+ }
53
+ if (strictHoloSphere) {
54
+ await strictHoloSphere.deleteAll(testHolon, testLens);
55
+ await strictHoloSphere.deleteAllGlobal(PUBLIC_GLOBAL_TABLE);
56
+ await strictHoloSphere.deleteAllGlobal(PRIVATE_GLOBAL_TABLE, testPassword);
57
+ }
58
+ } catch (error) {
59
+ console.error('Error during afterAll data cleanup:', error);
43
60
  }
44
- if (strictHoloSphere.gun) {
45
- strictHoloSphere.gun.off();
61
+
62
+ // Close HoloSphere instances
63
+ try {
64
+ if (holoSphere) {
65
+ console.log('Closing non-strict HoloSphere instance...');
66
+ await holoSphere.close();
67
+ console.log('Non-strict HoloSphere instance closed.');
68
+ }
69
+ if (strictHoloSphere) {
70
+ console.log('Closing strict HoloSphere instance...');
71
+ await strictHoloSphere.close();
72
+ console.log('Strict HoloSphere instance closed.');
73
+ }
74
+ } catch (error) {
75
+ console.error('Error during afterAll close:', error);
46
76
  }
47
-
48
- // Wait for connections to close
49
- await new Promise(resolve => setTimeout(resolve, 1000));
77
+
78
+ // Add a slightly longer, more explicit wait after close calls
79
+ console.log('Waiting extra time for cleanup...');
80
+ await new Promise(resolve => setTimeout(resolve, 2000));
81
+ console.log('Finished afterAll.');
50
82
  });
51
83
 
52
84
  describe('Authentication System', () => {
53
- it('should authenticate with password and store/retrieve data', async () => {
85
+ it('should authenticate with password and handle auth failures', async () => {
54
86
  const testData = { id: 'test1', value: 'private-data' };
55
87
 
56
88
  // Test storing with authentication
57
89
  await holoSphere.put(testHolon, testLens, testData, testPassword);
90
+ // Wait for data to be properly stored
91
+ await new Promise(resolve => setTimeout(resolve, 100));
58
92
 
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);
93
+ // Test retrieving with wrong password
94
+ const wrongResult = await holoSphere.get(testHolon, testLens, testData.id, 'wrong_password');
95
+ expect(wrongResult).toBeNull();
96
+
97
+ // Test retrieving with no password
98
+ const noPassResult = await holoSphere.get(testHolon, testLens, testData.id);
99
+ expect(noPassResult).toBeNull();
100
+
101
+ // Test retrieving with correct password
102
+ const correctResult = await holoSphere.get(testHolon, testLens, testData.id, testPassword);
103
+ expect(correctResult).toEqual(testData); // Use full object comparison
63
104
  });
64
105
 
65
106
  it('should handle authentication errors gracefully', async () => {
66
107
  const testData = { id: 'test2', value: 'private-data' };
67
108
 
68
109
  // Store data with correct password
110
+ await new Promise(resolve => setTimeout(resolve, 100)); // Added delay
69
111
  await holoSphere.put(testHolon, testLens, testData, testPassword);
70
112
 
71
113
  // Try to retrieve with wrong password
@@ -103,15 +145,16 @@ describe('HoloSphere Authentication and Authorization', () => {
103
145
  const testData = { id: 'test', value: 123 };
104
146
 
105
147
  // Should work in non-strict mode without schema
106
- await expect(holoSphere.put(testHolon, testLens, testData)).resolves.toBeTruthy();
148
+ await expect(holoSphere.put(testHolon, 'nonExistentLens', testData)).resolves.toBeTruthy();
107
149
 
108
- // Delete any existing schema
109
- await strictHoloSphere.putGlobal('schemas', { id: testLens, schema: null });
150
+ // Delete any existing schema for the lens
151
+ await strictHoloSphere.putGlobal('schemas', { id: 'nonExistentLens', schema: null });
110
152
 
111
- // Should fail in strict mode without schema
112
153
  try {
113
- await strictHoloSphere.put(testHolon, testLens, testData);
114
- fail('Should have thrown an error');
154
+ // This should throw an error in strict mode
155
+ await strictHoloSphere.put(testHolon, 'nonExistentLens', testData);
156
+ // If we get here, the test should fail
157
+ expect('Expected an error').toBe('but none was thrown');
115
158
  } catch (error) {
116
159
  expect(error.message).toBe('Schema required in strict mode');
117
160
  }
@@ -186,41 +229,35 @@ describe('HoloSphere Authentication and Authorization', () => {
186
229
 
187
230
  describe('Global Data Operations', () => {
188
231
  it('should handle private global data', async () => {
189
- const testData = { id: 'global', value: 111 };
190
-
191
- await holoSphere.putGlobal('testTable', testData, testPassword);
192
-
193
- const result = await holoSphere.getGlobal('testTable', testData.id, testPassword);
232
+ const testData = { id: 'globalPrivateItem', value: 111 };
233
+ await holoSphere.putGlobal(PRIVATE_GLOBAL_TABLE, testData, testPassword);
234
+ const result = await holoSphere.getGlobal(PRIVATE_GLOBAL_TABLE, testData.id, testPassword);
194
235
  expect(result).toEqual(testData);
195
236
  });
196
237
 
197
238
  it('should handle public global data', async () => {
198
- const testData = { id: 'public_global', value: 222 };
199
-
200
- await holoSphere.putGlobal('testTable', testData);
201
-
202
- const result = await holoSphere.getGlobal('testTable', testData.id);
239
+ const testData = { id: 'publicGlobalItem', value: 222 };
240
+ await holoSphere.putGlobal(PUBLIC_GLOBAL_TABLE, testData);
241
+ const result = await holoSphere.getGlobal(PUBLIC_GLOBAL_TABLE, testData.id);
203
242
  expect(result).toEqual(testData);
204
243
  });
205
244
 
206
245
  it('should handle getAllGlobal with private data', async () => {
207
246
  const testData = [
208
- { id: 'global1', value: 1 },
209
- { id: 'global2', value: 2 }
247
+ { id: 'globalPrivate1', value: 1 },
248
+ { id: 'globalPrivate2', value: 2 }
210
249
  ];
211
250
 
212
- // Clean up any existing data first
213
- await holoSphere.deleteAllGlobal('testTable', testPassword);
251
+ // Clean up before this specific test (already done in beforeEach, but good for clarity)
252
+ // await holoSphere.deleteAllGlobal(PRIVATE_GLOBAL_TABLE, testPassword); // This might be re-enabled later if tests pass without it
214
253
 
215
- // Store each item
216
254
  for (const data of testData) {
217
- await holoSphere.putGlobal('testTable', data, testPassword);
255
+ await holoSphere.putGlobal(PRIVATE_GLOBAL_TABLE, data, testPassword);
218
256
  }
219
257
 
220
- // Wait a bit for data to settle
221
- await new Promise(resolve => setTimeout(resolve, 1000));
258
+ await new Promise(resolve => setTimeout(resolve, 1000)); // Settle time
222
259
 
223
- const results = await holoSphere.getAllGlobal('testTable', testPassword);
260
+ const results = await holoSphere.getAllGlobal(PRIVATE_GLOBAL_TABLE, testPassword);
224
261
  expect(results.length).toBe(testData.length);
225
262
  for (const data of testData) {
226
263
  expect(results).toContainEqual(expect.objectContaining(data));
@@ -228,13 +265,10 @@ describe('HoloSphere Authentication and Authorization', () => {
228
265
  }, 10000);
229
266
 
230
267
  it('should handle deleteGlobal with private data', async () => {
231
- const testData = { id: 'delete_global', value: 333 };
232
-
233
- await holoSphere.putGlobal('testTable', testData, testPassword);
234
-
235
- await holoSphere.deleteGlobal('testTable', testData.id, testPassword);
236
-
237
- const result = await holoSphere.getGlobal('testTable', testData.id, testPassword);
268
+ const testData = { id: 'deleteGlobalPrivate', value: 333 };
269
+ await holoSphere.putGlobal(PRIVATE_GLOBAL_TABLE, testData, testPassword);
270
+ await holoSphere.deleteGlobal(PRIVATE_GLOBAL_TABLE, testData.id, testPassword);
271
+ const result = await holoSphere.getGlobal(PRIVATE_GLOBAL_TABLE, testData.id, testPassword);
238
272
  expect(result).toBeNull();
239
273
  });
240
274
  });
@@ -4,6 +4,9 @@ import { jest } from '@jest/globals';
4
4
  // Configure Jest
5
5
  jest.setTimeout(30000); // 30 second timeout
6
6
 
7
+ // Utility to wait for GunDB propagation
8
+ const waitForGun = (delay = 250) => new Promise(resolve => setTimeout(resolve, delay));
9
+
7
10
  describe('HoloSphere Deletion Tests', () => {
8
11
  const testAppName = 'test-app-deletion';
9
12
  const testHolon = 'testHolonDeletion';
@@ -106,20 +109,21 @@ describe('HoloSphere Deletion Tests', () => {
106
109
  test('should delete global items properly', async () => {
107
110
  // Create global test data
108
111
  const globalData = { id: 'global-delete-test', value: 'global delete me' };
109
-
110
- // Store global data
112
+
113
+ // 1. Store global data
111
114
  await holoSphere.putGlobal(testGlobalTable, globalData);
112
-
113
- // Verify global data exists
114
- const storedGlobalData = await holoSphere.getGlobal(testGlobalTable, globalData.id);
115
- expect(storedGlobalData).toBeDefined();
116
- expect(storedGlobalData.value).toBe(globalData.value);
117
-
118
- // Delete global data
115
+
116
+ // 2. Wait significantly for put to settle
117
+ await waitForGun(1500); // Generous wait after put
118
+
119
+ // 3. Delete global data
119
120
  const deleteResult = await holoSphere.deleteGlobal(testGlobalTable, globalData.id);
120
121
  expect(deleteResult).toBe(true);
121
-
122
- // Verify global data is deleted
122
+
123
+ // 4. Wait for delete to settle
124
+ await waitForGun(500); // Wait after delete
125
+
126
+ // 5. Verify global data is deleted
123
127
  const deletedGlobalData = await holoSphere.getGlobal(testGlobalTable, globalData.id);
124
128
  expect(deletedGlobalData).toBeNull();
125
129
  });