holosphere 1.1.3 → 1.1.4

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/holosphere.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as h3 from 'h3-js';
2
2
  import OpenAI from 'openai';
3
3
  import Gun from 'gun'
4
- import 'gun/sea' // Import SEA module
4
+ import SEA from 'gun/sea.js'
5
5
  import Ajv2019 from 'ajv/dist/2019.js'
6
6
 
7
7
 
@@ -21,7 +21,7 @@ class HoloSphere {
21
21
  strict: false, // Keep this false to avoid Ajv strict mode issues
22
22
  validateSchema: true // Always validate schemas
23
23
  });
24
-
24
+
25
25
  // Use provided Gun instance or create new one
26
26
  this.gun = gunInstance || Gun({
27
27
  peers: ['https://gun.holons.io/gun', 'https://59.src.eco/gun'],
@@ -32,7 +32,7 @@ class HoloSphere {
32
32
  });
33
33
 
34
34
  // Initialize SEA
35
- this.sea = Gun.SEA;
35
+ this.sea = SEA;
36
36
 
37
37
  if (openaikey != null) {
38
38
  this.openai = new OpenAI({
@@ -42,6 +42,9 @@ class HoloSphere {
42
42
 
43
43
  // Add currentSpace property to track logged in space
44
44
  this.currentSpace = null;
45
+
46
+ // Initialize subscriptions
47
+ this.subscriptions = {};
45
48
  }
46
49
 
47
50
  // ================================ SCHEMA FUNCTIONS ================================
@@ -108,7 +111,7 @@ class HoloSphere {
108
111
  // Only set owner if there's an authenticated space
109
112
  ...(this.currentSpace && { owner: this.currentSpace.alias })
110
113
  };
111
-
114
+
112
115
  this.gun.get(this.appname)
113
116
  .get(lens)
114
117
  .get('schema')
@@ -159,7 +162,7 @@ class HoloSphere {
159
162
  resolve(JSON.parse(data.schema));
160
163
  } else {
161
164
  // Legacy format or direct string
162
- const schemaStr = typeof data === 'string' ? data :
165
+ const schemaStr = typeof data === 'string' ? data :
163
166
  Object.values(data).find(v => typeof v === 'string' && v.includes('"type":'));
164
167
  resolve(schemaStr ? JSON.parse(schemaStr) : null);
165
168
  }
@@ -190,8 +193,8 @@ class HoloSphere {
190
193
  // If updating existing data, check ownership
191
194
  if (data.id) {
192
195
  const existing = await this.get(holon, lens, data.id);
193
- if (existing && existing.owner &&
194
- existing.owner !== this.currentSpace.alias &&
196
+ if (existing && existing.owner &&
197
+ existing.owner !== this.currentSpace.alias &&
195
198
  !existing.federation) { // Skip ownership check for federated data
196
199
  throw new Error('Unauthorized to modify this data');
197
200
  }
@@ -221,7 +224,7 @@ class HoloSphere {
221
224
  // Deep clone data to avoid modifying the original
222
225
  const dataToValidate = JSON.parse(JSON.stringify(dataWithMeta));
223
226
  const valid = this.validator.validate(schema, dataToValidate);
224
-
227
+
225
228
  if (!valid) {
226
229
  const errorMsg = `Schema validation failed: ${JSON.stringify(this.validator.errors)}`;
227
230
  // Always throw on schema validation failure, regardless of strict mode
@@ -243,6 +246,12 @@ class HoloSphere {
243
246
  if (ack.err) {
244
247
  reject(new Error(ack.err));
245
248
  } else {
249
+ // Notify subscribers after successful put
250
+ this.notifySubscribers({
251
+ holon,
252
+ lens,
253
+ ...dataWithMeta
254
+ });
246
255
  resolve(true);
247
256
  }
248
257
  });
@@ -275,7 +284,7 @@ class HoloSphere {
275
284
  }
276
285
 
277
286
  // Propagate to each federated space
278
- const propagationPromises = fedInfo.notify.map(spaceId =>
287
+ const propagationPromises = fedInfo.notify.map(spaceId =>
279
288
  new Promise((resolve) => {
280
289
  // Store data in the federated space's lens
281
290
  this.gun.get(this.appname)
@@ -335,7 +344,7 @@ class HoloSphere {
335
344
 
336
345
  // Get local data
337
346
  const localData = await this._getAllLocal(holon, lens, schema);
338
-
347
+
339
348
  // If authenticated, get federated data
340
349
  let federatedData = [];
341
350
  if (this.currentSpace) {
@@ -344,7 +353,7 @@ class HoloSphere {
344
353
 
345
354
  // Combine and deduplicate data based on ID
346
355
  const combined = new Map();
347
-
356
+
348
357
  // Add local data first
349
358
  localData.forEach(item => {
350
359
  if (item.id) {
@@ -356,7 +365,7 @@ class HoloSphere {
356
365
  federatedData.forEach(item => {
357
366
  if (item.id) {
358
367
  const existing = combined.get(item.id);
359
- if (!existing ||
368
+ if (!existing ||
360
369
  (item.federation?.timestamp > (existing.federation?.timestamp || 0))) {
361
370
  combined.set(item.id, item);
362
371
  }
@@ -426,7 +435,7 @@ class HoloSphere {
426
435
  const output = new Map();
427
436
  let isResolved = false;
428
437
  let listener = null;
429
-
438
+
430
439
  const hardTimeout = setTimeout(() => {
431
440
  cleanup();
432
441
  resolve(Array.from(output.values()));
@@ -505,7 +514,7 @@ class HoloSphere {
505
514
  }
506
515
 
507
516
  const federatedData = new Map();
508
-
517
+
509
518
  // Get data from each federated space
510
519
  const fedPromises = fedInfo.federation.map(spaceId =>
511
520
  new Promise((resolve) => {
@@ -632,7 +641,7 @@ class HoloSphere {
632
641
  .get(key)
633
642
  .once(async (data) => {
634
643
  clearTimeout(timeout);
635
-
644
+
636
645
  if (!data) {
637
646
  resolve(null);
638
647
  return;
@@ -658,7 +667,7 @@ class HoloSphere {
658
667
  // 2. User is the owner
659
668
  // 3. User is in shared list
660
669
  // 4. Data is from federation
661
- if (parsed.owner &&
670
+ if (parsed.owner &&
662
671
  this.currentSpace?.alias !== parsed.owner &&
663
672
  (!parsed.shared || !parsed.shared.includes(this.currentSpace?.alias)) &&
664
673
  (!parsed.federation || !parsed.federation.origin)) {
@@ -711,7 +720,7 @@ class HoloSphere {
711
720
  if (!data) {
712
721
  return true; // Nothing to delete
713
722
  }
714
-
723
+
715
724
  if (data.owner && data.owner !== this.currentSpace.alias) {
716
725
  throw new Error('Unauthorized to delete this data');
717
726
  }
@@ -780,7 +789,7 @@ class HoloSphere {
780
789
  .catch(error => {
781
790
  console.error('Error in deleteAll:', error);
782
791
  resolve(false);
783
- });
792
+ });
784
793
  });
785
794
  });
786
795
  }
@@ -985,12 +994,12 @@ class HoloSphere {
985
994
  }
986
995
 
987
996
  const keys = Object.keys(data).filter(key => key !== '_');
988
- const promises = keys.map(key =>
997
+ const promises = keys.map(key =>
989
998
  new Promise(async (resolveItem) => {
990
999
  const itemData = await new Promise(resolveData => {
991
1000
  this.gun.get(this.appname).get(tableName).get(key).once(resolveData);
992
1001
  });
993
-
1002
+
994
1003
  if (itemData) {
995
1004
  try {
996
1005
  const parsed = await this.parse(itemData);
@@ -1088,7 +1097,7 @@ class HoloSphere {
1088
1097
  }
1089
1098
 
1090
1099
  const keys = Object.keys(data).filter(key => key !== '_');
1091
- const promises = keys.map(key =>
1100
+ const promises = keys.map(key =>
1092
1101
  new Promise((resolveDelete) => {
1093
1102
  this.gun.get(this.appname)
1094
1103
  .get(tableName)
@@ -1120,21 +1129,62 @@ class HoloSphere {
1120
1129
 
1121
1130
  // ================================ COMPUTE FUNCTIONS ================================
1122
1131
  /**
1123
- * Computes summaries based on the content within a holon and lens.
1132
+
1133
+ /**
1134
+ * Computes operations across multiple layers up the hierarchy
1135
+ * @param {string} holon - Starting holon identifier
1136
+ * @param {string} lens - The lens to compute
1137
+ * @param {object} options - Computation options
1138
+ * @param {number} [maxLevels=15] - Maximum levels to compute up
1139
+ */
1140
+ async computeHierarchy(holon, lens, options, maxLevels = 15) {
1141
+ let currentHolon = holon;
1142
+ let currentRes = h3.getResolution(currentHolon);
1143
+ const results = [];
1144
+
1145
+ while (currentRes > 0 && maxLevels > 0) {
1146
+ try {
1147
+ const result = await this.compute(currentHolon, lens, options);
1148
+ if (result) {
1149
+ results.push(result);
1150
+ }
1151
+ currentHolon = h3.cellToParent(currentHolon, currentRes - 1);
1152
+ currentRes--;
1153
+ maxLevels--;
1154
+ } catch (error) {
1155
+ console.error('Error in compute hierarchy:', error);
1156
+ break;
1157
+ }
1158
+ }
1159
+
1160
+ return results;
1161
+ }
1162
+
1163
+ /* Computes operations on content within a holon and lens for one layer up.
1124
1164
  * @param {string} holon - The holon identifier.
1125
1165
  * @param {string} lens - The lens to compute.
1126
- * @param {string} operation - The operation to perform.
1127
- * @param {number} [depth=0] - Current recursion depth.
1128
- * @param {number} [maxDepth=15] - Maximum recursion depth.
1166
+ * @param {object} options - Computation options
1167
+ * @param {string} options.operation - The operation to perform ('summarize', 'aggregate', 'concatenate')
1168
+ * @param {string[]} [options.fields] - Fields to perform operation on
1169
+ * @param {string} [options.targetField] - Field to store the result in
1129
1170
  * @throws {Error} If parameters are invalid or missing
1130
1171
  */
1131
- async compute(holon, lens, operation, depth = 0, maxDepth = 15) {
1172
+ async compute(holon, lens, options) {
1132
1173
  // Validate required parameters
1133
- if (!holon || !lens || !operation) {
1174
+ if (!holon || !lens) {
1175
+ throw new Error('compute: Missing required parameters');
1176
+ }
1177
+
1178
+ // Convert string operation to options object
1179
+ if (typeof options === 'string') {
1180
+ options = { operation: options };
1181
+ }
1182
+
1183
+ if (!options?.operation) {
1134
1184
  throw new Error('compute: Missing required parameters');
1135
1185
  }
1136
1186
 
1137
- // Validate holon format and resolution
1187
+ // Validate holon format and resolution first
1138
1188
  let res;
1139
1189
  try {
1140
1190
  res = h3.getResolution(holon);
@@ -1146,141 +1196,108 @@ class HoloSphere {
1146
1196
  throw new Error('compute: Invalid holon resolution (must be between 1 and 15)');
1147
1197
  }
1148
1198
 
1149
- // Validate depth parameters
1150
- if (typeof depth !== 'number' || depth < 0) {
1199
+ const {
1200
+ operation,
1201
+ fields = [],
1202
+ targetField,
1203
+ depth,
1204
+ maxDepth
1205
+ } = options;
1206
+
1207
+ // Validate depth parameters if provided
1208
+ if (depth !== undefined && depth < 0) {
1151
1209
  throw new Error('compute: Invalid depth parameter');
1152
1210
  }
1153
1211
 
1154
- if (typeof maxDepth !== 'number' || maxDepth < 1 || maxDepth > 15) {
1212
+ if (maxDepth !== undefined && (maxDepth < 1 || maxDepth > 15)) {
1155
1213
  throw new Error('compute: Invalid maxDepth parameter (must be between 1 and 15)');
1156
1214
  }
1157
1215
 
1158
- if (depth >= maxDepth) {
1159
- return;
1160
- }
1161
-
1162
1216
  // Validate operation
1163
- if (typeof operation !== 'string' || !['summarize'].includes(operation)) {
1164
- throw new Error('compute: Invalid operation (must be "summarize")');
1217
+ const validOperations = ['summarize', 'aggregate', 'concatenate'];
1218
+ if (!validOperations.includes(operation)) {
1219
+ throw new Error(`compute: Invalid operation (must be one of ${validOperations.join(', ')})`);
1165
1220
  }
1166
1221
 
1167
1222
  const parent = h3.cellToParent(holon, res - 1);
1168
1223
  const siblings = h3.cellToChildren(parent, res);
1169
1224
 
1170
- const content = [];
1171
- const promises = siblings.map(sibling =>
1172
- new Promise((resolve) => {
1173
- const timeout = setTimeout(() => {
1174
- console.warn(`Timeout for sibling ${sibling}`);
1175
- resolve();
1176
- }, 10000);
1177
-
1178
- this.gun.get(this.appname)
1179
- .get(sibling)
1180
- .get(lens)
1181
- .map()
1182
- .once((data) => {
1183
- clearTimeout(timeout);
1184
- if (!data) {
1185
- resolve();
1186
- return;
1187
- }
1188
-
1189
- try {
1190
- // Parse the data if it's a string
1191
- const parsed = typeof data === 'string' ? JSON.parse(data) : data;
1192
- if (parsed && parsed.content) {
1193
- content.push(parsed.content);
1194
- }
1195
- } catch (error) {
1196
- console.warn('Error parsing data:', error);
1197
- }
1198
- resolve();
1199
- });
1200
- })
1225
+ // Collect all content from siblings
1226
+ const contents = await Promise.all(
1227
+ siblings.map(sibling => this.getAll(sibling, lens))
1201
1228
  );
1202
1229
 
1203
- await Promise.all(promises);
1230
+ const flatContents = contents.flat().filter(Boolean);
1204
1231
 
1205
- if (content.length > 0) {
1232
+ if (flatContents.length > 0) {
1206
1233
  try {
1207
- const computed = await this.summarize(content.join('\n'));
1234
+ let computed;
1235
+ switch (operation) {
1236
+ case 'summarize':
1237
+ // For summarize, concatenate specified fields or use entire content
1238
+ const textToSummarize = fields.length > 0
1239
+ ? flatContents.map(item => fields.map(field => item[field]).filter(Boolean).join('\n')).join('\n')
1240
+ : JSON.stringify(flatContents);
1241
+ computed = await this.summarize(textToSummarize);
1242
+ break;
1243
+
1244
+ case 'aggregate':
1245
+ // For aggregate, sum numeric fields
1246
+ computed = fields.reduce((acc, field) => {
1247
+ acc[field] = flatContents.reduce((sum, item) => {
1248
+ return sum + (Number(item[field]) || 0);
1249
+ }, 0);
1250
+ return acc;
1251
+ }, {});
1252
+ break;
1253
+
1254
+ case 'concatenate':
1255
+ // For concatenate, combine arrays or strings
1256
+ computed = fields.reduce((acc, field) => {
1257
+ acc[field] = flatContents.reduce((combined, item) => {
1258
+ const value = item[field];
1259
+ if (Array.isArray(value)) {
1260
+ return [...combined, ...value];
1261
+ } else if (value) {
1262
+ return [...combined, value];
1263
+ }
1264
+ return combined;
1265
+ }, []);
1266
+ // Remove duplicates if array
1267
+ acc[field] = Array.from(new Set(acc[field]));
1268
+ return acc;
1269
+ }, {});
1270
+ break;
1271
+ }
1272
+
1208
1273
  if (computed) {
1209
- const summaryId = `${parent}_summary`;
1210
- await this.put(parent, lens, {
1211
- id: summaryId,
1212
- content: computed,
1274
+ const resultId = `${parent}_${operation}`;
1275
+ const result = {
1276
+ id: resultId,
1213
1277
  timestamp: Date.now()
1214
- });
1278
+ };
1215
1279
 
1216
- if (res > 1) { // Only recurse if not at top level
1217
- await this.compute(parent, lens, operation, depth + 1, maxDepth);
1280
+ // Store result in targetField if specified, otherwise at root level
1281
+ if (targetField) {
1282
+ result[targetField] = computed;
1283
+ } else if (typeof computed === 'object') {
1284
+ Object.assign(result, computed);
1285
+ } else {
1286
+ result.value = computed;
1218
1287
  }
1288
+
1289
+ await this.put(parent, lens, result);
1290
+ return result;
1219
1291
  }
1220
1292
  } catch (error) {
1221
1293
  console.warn('Error in compute operation:', error);
1222
- // Don't throw here to maintain graceful handling of compute errors
1294
+ throw error;
1223
1295
  }
1224
1296
  }
1225
1297
 
1226
- // Return successfully even if no content was found or processed
1227
- return;
1228
- }
1229
-
1230
- /**
1231
- * Clears all entities under a specific holon and lens.
1232
- * @param {string} holon - The holon identifier.
1233
- * @param {string} lens - The lens to clear.
1234
- */
1235
- async clearlens(holon, lens) {
1236
- if (!holon || !lens) {
1237
- throw new Error('clearlens: Missing required parameters');
1238
- }
1239
-
1240
- return new Promise((resolve, reject) => {
1241
- try {
1242
- const deletions = new Set();
1243
- const timeout = setTimeout(() => {
1244
- if (deletions.size === 0) {
1245
- resolve(); // No data to delete
1246
- }
1247
- }, 1000);
1248
-
1249
- this.gun.get(this.appname)
1250
- .get(holon)
1251
- .get(lens)
1252
- .map()
1253
- .once((data, key) => {
1254
- if (data) {
1255
- const deletion = new Promise((resolveDelete) => {
1256
- this.gun.get(this.appname)
1257
- .get(holon)
1258
- .get(lens)
1259
- .get(key)
1260
- .put(null, ack => {
1261
- if (ack.err) {
1262
- console.error(`Failed to delete ${key}:`, ack.err);
1263
- }
1264
- resolveDelete();
1265
- });
1266
- });
1267
- deletions.add(deletion);
1268
- deletion.finally(() => {
1269
- deletions.delete(deletion);
1270
- if (deletions.size === 0) {
1271
- clearTimeout(timeout);
1272
- resolve();
1273
- }
1274
- });
1275
- }
1276
- });
1277
- } catch (error) {
1278
- reject(error);
1279
- }
1280
- });
1298
+ return null;
1281
1299
  }
1282
1300
 
1283
-
1284
1301
  /**
1285
1302
  * Summarizes provided history text using OpenAI.
1286
1303
  * @param {string} history - The history text to summarize.
@@ -1406,47 +1423,46 @@ class HoloSphere {
1406
1423
  * @param {function} callback - The callback to execute on changes.
1407
1424
  */
1408
1425
  async subscribe(holon, lens, callback) {
1409
- if (!holon || !lens || !callback) {
1410
- throw new Error('subscribe: Missing required parameters');
1411
- }
1412
-
1413
- const ref = this.gun.get(this.appname)
1414
- .get(holon)
1415
- .get(lens);
1416
-
1417
- // Create a more robust handler
1418
- const handler = async (data, key) => {
1419
- if (!data || key === '_') return; // Skip empty data or Gun metadata
1420
-
1421
- try {
1422
- const parsed = typeof data === 'string' ? await this.parse(data) : data;
1423
- if (parsed) {
1424
- await callback(parsed);
1425
- }
1426
- } catch (error) {
1427
- console.warn('Subscription handler error:', error);
1428
- }
1426
+ const subscriptionId = this.generateSubscriptionId();
1427
+ this.subscriptions[subscriptionId] = {
1428
+ query: { holon, lens },
1429
+ callback,
1430
+ active: true
1429
1431
  };
1430
1432
 
1431
- // Subscribe using Gun's map() and on()
1432
- const chain = ref.map();
1433
- chain.on(handler);
1434
-
1435
- // Return subscription object
1433
+ // Add cleanup to ensure callback isn't called after unsubscribe
1436
1434
  return {
1437
- off: () => {
1438
- if (chain) {
1439
- chain.off();
1435
+ unsubscribe: () => {
1436
+ if (this.subscriptions[subscriptionId]) {
1437
+ delete this.subscriptions[subscriptionId];
1440
1438
  }
1441
1439
  }
1442
1440
  };
1443
1441
  }
1444
1442
 
1443
+ notifySubscribers(data) {
1444
+ Object.values(this.subscriptions).forEach(subscription => {
1445
+ if (subscription.active && this.matchesQuery(data, subscription.query)) {
1446
+ subscription.callback(data);
1447
+ }
1448
+ });
1449
+ }
1450
+
1445
1451
  // Add ID generation method
1446
1452
  generateId() {
1447
1453
  return Date.now().toString(10) + Math.random().toString(2);
1448
1454
  }
1449
1455
 
1456
+ generateSubscriptionId() {
1457
+ return Date.now().toString(10) + Math.random().toString(2);
1458
+ }
1459
+
1460
+ matchesQuery(data, query) {
1461
+ return data && query &&
1462
+ data.holon === query.holon &&
1463
+ data.lens === query.lens;
1464
+ }
1465
+
1450
1466
  /**
1451
1467
  * Creates a new space with the given credentials
1452
1468
  * @param {string} spacename - The space identifier/username
@@ -1467,7 +1483,7 @@ class HoloSphere {
1467
1483
  try {
1468
1484
  // Generate key pair
1469
1485
  const pair = await Gun.SEA.pair();
1470
-
1486
+
1471
1487
  // Create auth record with SEA
1472
1488
  const salt = await Gun.SEA.random(64).toString('base64');
1473
1489
  const hash = await Gun.SEA.work(password, salt);
@@ -1505,8 +1521,8 @@ class HoloSphere {
1505
1521
  */
1506
1522
  async login(spacename, password) {
1507
1523
  // Validate input
1508
- if (!spacename || !password ||
1509
- typeof spacename !== 'string' ||
1524
+ if (!spacename || !password ||
1525
+ typeof spacename !== 'string' ||
1510
1526
  typeof password !== 'string') {
1511
1527
  throw new Error('Invalid credentials format');
1512
1528
  }
@@ -1738,10 +1754,10 @@ class HoloSphere {
1738
1754
 
1739
1755
  // Get federation info for current space
1740
1756
  const fedInfo = await this.getFederation(this.currentSpace?.alias);
1741
-
1757
+
1742
1758
  // Get local data
1743
1759
  const localData = await this.getAll(holon, lens);
1744
-
1760
+
1745
1761
  // If no federation or not authenticated, return local data only
1746
1762
  if (!fedInfo || !fedInfo.federation || fedInfo.federation.length === 0) {
1747
1763
  return localData;
@@ -1828,15 +1844,15 @@ class HoloSphere {
1828
1844
  if (!removeDuplicates) {
1829
1845
  return allData;
1830
1846
  }
1831
-
1847
+
1832
1848
  // Remove duplicates keeping the most recent version
1833
1849
  const uniqueMap = new Map();
1834
1850
  allData.forEach(item => {
1835
1851
  const id = item[idField];
1836
1852
  if (!id) return;
1837
-
1853
+
1838
1854
  const existing = uniqueMap.get(id);
1839
- if (!existing ||
1855
+ if (!existing ||
1840
1856
  (item.federation?.timestamp > (existing.federation?.timestamp || 0))) {
1841
1857
  uniqueMap.set(id, item);
1842
1858
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "holosphere",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Holonic Geospatial Communication Infrastructure",
5
5
  "main": "holosphere.js",
6
6
  "types": "holosphere.d.ts",
@@ -14,6 +14,7 @@
14
14
  "license": "GPL-3.0-or-later",
15
15
  "dependencies": {
16
16
  "ajv": "^8.12.0",
17
+ "dotenv": "^16.4.7",
17
18
  "gun": "^0.2020.1240",
18
19
  "h3-js": "^4.1.0",
19
20
  "openai": "^4.85.1"
@@ -0,0 +1,233 @@
1
+ import HoloSphere from '../holosphere.js';
2
+ import * as h3 from 'h3-js';
3
+ import { jest } from '@jest/globals';
4
+ import 'dotenv/config';
5
+
6
+ // Set global timeout for all tests
7
+ jest.setTimeout(120000);
8
+
9
+ describe('AI Operations', () => {
10
+ let holoSphere;
11
+ const testAppName = 'test-ai-app';
12
+ const testHolon = h3.latLngToCell(40.7128, -74.0060, 7);
13
+ const testLens = 'aiTestLens';
14
+ const testCredentials = {
15
+ spacename: 'aitest@example.com',
16
+ password: 'AiTest123!'
17
+ };
18
+
19
+ beforeAll(async () => {
20
+ holoSphere = new HoloSphere(testAppName, false, process.env.OPENAI_API_KEY);
21
+
22
+ // Clean up any existing test space and data
23
+ try {
24
+ await holoSphere.deleteAllGlobal('federation');
25
+ await holoSphere.deleteGlobal('spaces', testCredentials.spacename);
26
+ } catch (error) {
27
+ console.log('Cleanup error (can be ignored):', error);
28
+ }
29
+ // Create and login to test space
30
+ await holoSphere.createSpace(testCredentials.spacename, testCredentials.password);
31
+ await holoSphere.login(testCredentials.spacename, testCredentials.password);
32
+ // Set up base schema for compute tests
33
+ const baseSchema = {
34
+ type: 'object',
35
+ properties: {
36
+ id: { type: 'string' },
37
+ content: { type: 'string' },
38
+ value: { type: 'number' },
39
+ tags: { type: 'array', items: { type: 'string' } },
40
+ timestamp: { type: 'number' }
41
+ },
42
+ required: ['id']
43
+ };
44
+
45
+ await holoSphere.setSchema(testLens, baseSchema);
46
+ }, 30000);
47
+
48
+ describe('Summarize Operations', () => {
49
+ test('should generate summary from text content', async () => {
50
+ const testContent = `
51
+ The HoloSphere project is a decentralized data management system.
52
+ It uses Gun.js for peer-to-peer data synchronization and SEA for encryption.
53
+ The system supports federation between spaces and implements schema validation.
54
+ Data can be organized in holons and viewed through different lenses.
55
+ `;
56
+
57
+ const summary = await holoSphere.summarize(testContent);
58
+ console.log("summary",summary);
59
+ expect(summary).toBeDefined();
60
+ expect(typeof summary).toBe('string');
61
+ expect(summary.length).toBeGreaterThan(0);
62
+ }, 15000);
63
+
64
+ test('should handle empty content gracefully', async () => {
65
+ const summary = await holoSphere.summarize('');
66
+ expect(summary).toBeDefined();
67
+ expect(typeof summary).toBe('string');
68
+ }, 10000);
69
+
70
+ test('should handle long content', async () => {
71
+ const longContent = Array(10).fill(
72
+ 'This is a long paragraph of text that needs to be summarized. ' +
73
+ 'It contains multiple sentences with various information. ' +
74
+ 'The summary should capture the key points while remaining concise.'
75
+ ).join('\n');
76
+
77
+ const summary = await holoSphere.summarize(longContent);
78
+ expect(summary).toBeDefined();
79
+ expect(typeof summary).toBe('string');
80
+ expect(summary.length).toBeLessThan(longContent.length);
81
+ }, 20000);
82
+
83
+ test('should fail gracefully without API key', async () => {
84
+ const noKeyHoloSphere = new HoloSphere(testAppName, false);
85
+ const result = await noKeyHoloSphere.summarize('Test content');
86
+ expect(result).toBe('OpenAI not initialized, please specify the API key in the constructor.');
87
+ });
88
+ });
89
+
90
+ describe('Compute Operations', () => {
91
+ beforeEach(async () => {
92
+ // Ensure we're logged in
93
+ if (!holoSphere.currentSpace) {
94
+ await holoSphere.login(testCredentials.spacename, testCredentials.password);
95
+ }
96
+
97
+ // Clean up any existing test data
98
+ await holoSphere.deleteAll(testHolon, testLens);
99
+ }, 15000);
100
+
101
+ test('should compute summaries for nested holons', async () => {
102
+ const childHolon = h3.cellToChildren(testHolon, 8)[0];
103
+ const testData = {
104
+ id: 'test1',
105
+ content: 'This is test content for the child holon that should be summarized.',
106
+ timestamp: Date.now()
107
+ };
108
+
109
+ // Put data in child holon
110
+ await holoSphere.put(childHolon, testLens, testData);
111
+
112
+ // Compute summaries
113
+ const result = await holoSphere.compute(childHolon, testLens, {
114
+ operation: 'summarize',
115
+ fields: ['content'],
116
+ targetField: 'summary'
117
+ });
118
+
119
+ expect(result).toBeDefined();
120
+ expect(result.id).toMatch(/_summarize$/);
121
+ expect(result.summary).toBeDefined();
122
+ expect(typeof result.summary).toBe('string');
123
+ }, 60000);
124
+
125
+ test('should compute aggregations for numeric fields', async () => {
126
+ const childHolon = h3.cellToChildren(testHolon, 8)[0];
127
+ const testData = [
128
+ { id: 'test1', value: 10, timestamp: Date.now() },
129
+ { id: 'test2', value: 20, timestamp: Date.now() }
130
+ ];
131
+
132
+ // Put test data
133
+ await Promise.all(testData.map(data => holoSphere.put(childHolon, testLens, data)));
134
+
135
+ // Compute aggregation
136
+ const result = await holoSphere.compute(childHolon, testLens, {
137
+ operation: 'aggregate',
138
+ fields: ['value']
139
+ });
140
+
141
+ expect(result).toBeDefined();
142
+ expect(result.id).toMatch(/_aggregate$/);
143
+ expect(result.value).toBe(30);
144
+ }, 30000);
145
+
146
+ test('should compute concatenations for array fields', async () => {
147
+ const childHolon = h3.cellToChildren(testHolon, 8)[0];
148
+ const testData = [
149
+ { id: 'test1', tags: ['tag1', 'tag2'], timestamp: Date.now() },
150
+ { id: 'test2', tags: ['tag2', 'tag3'], timestamp: Date.now() }
151
+ ];
152
+
153
+ // Put test data
154
+ await Promise.all(testData.map(data => holoSphere.put(childHolon, testLens, data)));
155
+
156
+ // Compute concatenation
157
+ const result = await holoSphere.compute(childHolon, testLens, {
158
+ operation: 'concatenate',
159
+ fields: ['tags']
160
+ });
161
+
162
+ expect(result).toBeDefined();
163
+ expect(result.id).toMatch(/_concatenate$/);
164
+ expect(result.tags).toEqual(['tag1', 'tag2', 'tag3']);
165
+ }, 30000);
166
+
167
+
168
+ test('should handle empty holons', async () => {
169
+ // Clean up any existing data first
170
+ await holoSphere.deleteAll(testHolon, testLens);
171
+
172
+ // Try to compute on empty holon
173
+ const result = await holoSphere.compute(testHolon, testLens, {
174
+ operation: 'summarize',
175
+ fields: ['content']
176
+ });
177
+
178
+ expect(result).toBeNull();
179
+ }, 30000);
180
+
181
+ test('should compute hierarchy across multiple levels', async () => {
182
+ const childHolon = h3.cellToChildren(testHolon, 9)[0];
183
+ const testData = {
184
+ id: 'test-hierarchy',
185
+ content: 'Content for testing hierarchy computation',
186
+ value: 42,
187
+ tags: ['test', 'hierarchy'],
188
+ timestamp: Date.now()
189
+ };
190
+
191
+ // Put test data
192
+ await holoSphere.put(childHolon, testLens, testData);
193
+
194
+ // Compute hierarchy
195
+ const results = await holoSphere.computeHierarchy(childHolon, testLens, {
196
+ operation: 'summarize',
197
+ fields: ['content'],
198
+ targetField: 'summary'
199
+ }, 3);
200
+
201
+ expect(Array.isArray(results)).toBe(true);
202
+ expect(results.length).toBeGreaterThan(0);
203
+ results.forEach(result => {
204
+ expect(result.id).toMatch(/_summarize$/);
205
+ expect(result.summary).toBeDefined();
206
+ expect(typeof result.summary).toBe('string');
207
+ });
208
+ }, 60000);
209
+ });
210
+
211
+ afterEach(async () => {
212
+ // Clean up test data
213
+ if (holoSphere.currentSpace) {
214
+ await holoSphere.deleteAll(testHolon, testLens);
215
+ }
216
+ }, 15000);
217
+
218
+ afterAll(async () => {
219
+ // Clean up test space and data
220
+ try {
221
+ await holoSphere.deleteAll(testHolon, testLens);
222
+ await holoSphere.deleteGlobal('spaces', testCredentials.spacename);
223
+ await holoSphere.deleteAllGlobal('federation');
224
+ } catch (error) {
225
+ console.log('Cleanup error (can be ignored):', error);
226
+ }
227
+
228
+ // Clean up Gun instance
229
+ if (holoSphere.gun) {
230
+ holoSphere.gun.off();
231
+ }
232
+ }, 30000);
233
+ });
@@ -1,9 +1,8 @@
1
1
  import HoloSphere from '../holosphere.js';
2
- import * as h3 from 'h3-js';
3
2
  import { jest } from '@jest/globals';
4
3
 
5
4
  // Set global timeout for all tests
6
- jest.setTimeout(30000);
5
+ jest.setTimeout(3000);
7
6
 
8
7
  describe('Federation Operations', () => {
9
8
  const testAppName = 'test-app';
@@ -28,8 +27,6 @@ describe('Federation Operations', () => {
28
27
  await holoSphere.deleteAllGlobal('federation');
29
28
  await holoSphere.deleteGlobal('spaces', space1.spacename);
30
29
  await holoSphere.deleteGlobal('spaces', space2.spacename);
31
- // Wait for cleanup
32
- await new Promise(resolve => setTimeout(resolve, 2000));
33
30
  } catch (error) {
34
31
  // Ignore errors during cleanup
35
32
  console.log('Cleanup error (expected):', error.message);
@@ -44,12 +41,9 @@ describe('Federation Operations', () => {
44
41
  } catch (error) {
45
42
  console.log('Space creation attempt', i + 1, 'failed:', error.message);
46
43
  if (i === 2) throw error;
47
- await new Promise(resolve => setTimeout(resolve, 2000));
48
44
  }
49
45
  }
50
46
 
51
- // Wait for space creation to complete
52
- await new Promise(resolve => setTimeout(resolve, 5000));
53
47
 
54
48
  // Verify spaces were created
55
49
  const space1Created = await holoSphere.getGlobal('spaces', space1.spacename);
@@ -58,9 +52,6 @@ describe('Federation Operations', () => {
58
52
  if (!space1Created || !space2Created) {
59
53
  throw new Error('Failed to create test spaces');
60
54
  }
61
-
62
- // Wait for everything to settle
63
- await new Promise(resolve => setTimeout(resolve, 2000));
64
55
  }, 30000);
65
56
 
66
57
  beforeEach(async () => {
@@ -91,9 +82,7 @@ describe('Federation Operations', () => {
91
82
  await holoSphere.setSchema(testLens, baseSchema);
92
83
  await strictHoloSphere.setSchema(testLens, baseSchema);
93
84
 
94
- // Wait for schema to be set
95
- await new Promise(resolve => setTimeout(resolve, 1000));
96
-
85
+
97
86
  // Verify spaces exist before proceeding
98
87
  const space1Exists = await holoSphere.getGlobal('spaces', space1.spacename);
99
88
  const space2Exists = await holoSphere.getGlobal('spaces', space2.spacename);
@@ -102,7 +91,6 @@ describe('Federation Operations', () => {
102
91
  if (!space1Exists) {
103
92
  try {
104
93
  await holoSphere.createSpace(space1.spacename, space1.password);
105
- await new Promise(resolve => setTimeout(resolve, 2000));
106
94
  } catch (error) {
107
95
  if (error.message !== 'Space already exists') {
108
96
  throw error;
@@ -112,7 +100,6 @@ describe('Federation Operations', () => {
112
100
  if (!space2Exists) {
113
101
  try {
114
102
  await holoSphere.createSpace(space2.spacename, space2.password);
115
- await new Promise(resolve => setTimeout(resolve, 2000));
116
103
  } catch (error) {
117
104
  if (error.message !== 'Space already exists') {
118
105
  throw error;
@@ -120,9 +107,6 @@ describe('Federation Operations', () => {
120
107
  }
121
108
  }
122
109
 
123
- // Wait for space creation to complete
124
- await new Promise(resolve => setTimeout(resolve, 2000));
125
-
126
110
  // Verify spaces again
127
111
  const space1Verified = await holoSphere.getGlobal('spaces', space1.spacename);
128
112
  const space2Verified = await holoSphere.getGlobal('spaces', space2.spacename);
@@ -142,17 +126,12 @@ describe('Federation Operations', () => {
142
126
  // Login as first space to holoSphere
143
127
  await holoSphere.login(space1.spacename, space1.password);
144
128
 
145
- // Wait for login to complete
146
- await new Promise(resolve => setTimeout(resolve, 2000));
147
129
  }, 20000);
148
130
 
149
131
  test('should create federation relationship between spaces', async () => {
150
132
  // Create federation relationship
151
133
  await holoSphere.federate(space1.spacename, space2.spacename);
152
134
 
153
- // Wait for federation to be established
154
- await new Promise(resolve => setTimeout(resolve, 1000));
155
-
156
135
  // Verify federation was created
157
136
  const fedInfo = await holoSphere.getFederation(space1.spacename);
158
137
  expect(fedInfo).toBeDefined();
@@ -163,9 +142,6 @@ describe('Federation Operations', () => {
163
142
  // Create bidirectional federation
164
143
  await holoSphere.federate(space1.spacename, space2.spacename);
165
144
 
166
- // Wait for federation to be established
167
- await new Promise(resolve => setTimeout(resolve, 2000));
168
-
169
145
  // Login to space1 to verify federation
170
146
  await holoSphere.login(space1.spacename, space1.password);
171
147
 
@@ -186,9 +162,6 @@ describe('Federation Operations', () => {
186
162
  // Create initial federation
187
163
  await holoSphere.federate(space1.spacename, space2.spacename);
188
164
 
189
- // Wait for federation to be established
190
- await new Promise(resolve => setTimeout(resolve, 3000));
191
-
192
165
  // Verify federation exists
193
166
  const fedInfo = await holoSphere.getFederation(space1.spacename);
194
167
  expect(fedInfo).toBeDefined();
@@ -213,18 +186,12 @@ describe('Federation Operations', () => {
213
186
  // Set up federation
214
187
  await holoSphere.federate(space1.spacename, space2.spacename);
215
188
 
216
- // Wait for federation to be established
217
- await new Promise(resolve => setTimeout(resolve, 5000));
218
-
219
189
  // Login to space2 with strict instance
220
190
  await strictHoloSphere.login(space2.spacename, space2.password);
221
191
 
222
192
  // Put data in first space
223
193
  await holoSphere.put(testHolon, testLens, testData);
224
194
 
225
- // Wait for propagation
226
- await new Promise(resolve => setTimeout(resolve, 5000));
227
-
228
195
  // Verify data was propagated to federated space
229
196
  const federatedData = await strictHoloSphere.get(testHolon, testLens, testData.id);
230
197
  expect(federatedData).toBeDefined();
@@ -239,9 +206,6 @@ describe('Federation Operations', () => {
239
206
  // Clean up any existing test data first
240
207
  await holoSphere.deleteAll(testHolon, testLens);
241
208
  await strictHoloSphere.deleteAll(testHolon, testLens);
242
-
243
- // Wait for cleanup to complete
244
- await new Promise(resolve => setTimeout(resolve, 5000));
245
209
 
246
210
  // Set up federation using non-strict instance (already logged in as space1)
247
211
  await holoSphere.federate(space1.spacename, space2.spacename);
@@ -249,9 +213,6 @@ describe('Federation Operations', () => {
249
213
  // Login to space2 with strict instance
250
214
  await strictHoloSphere.login(space2.spacename, space2.password);
251
215
 
252
- // Wait for federation to be established
253
- await new Promise(resolve => setTimeout(resolve, 5000));
254
-
255
216
  // Test data with overlapping IDs and different fields
256
217
  const testData1 = {
257
218
  id: 'user1',
@@ -281,18 +242,13 @@ describe('Federation Operations', () => {
281
242
  // Put data using both instances and wait between puts
282
243
  console.log('Putting test data 1:', JSON.stringify(testData1, null, 2));
283
244
  await holoSphere.put(testHolon, testLens, testData1);
284
- await new Promise(resolve => setTimeout(resolve, 5000));
285
245
 
286
246
  console.log('Putting test data 2:', JSON.stringify(testData2, null, 2));
287
247
  await strictHoloSphere.put(testHolon, testLens, testData2);
288
- await new Promise(resolve => setTimeout(resolve, 5000));
289
248
 
290
249
  console.log('Putting test data 3:', JSON.stringify(testData3, null, 2));
291
250
  await holoSphere.put(testHolon, testLens, testData3);
292
251
 
293
- // Wait longer for data propagation
294
- await new Promise(resolve => setTimeout(resolve, 10000));
295
-
296
252
  // Test 1: Simple concatenation without deduplication
297
253
  const concatenatedResults = await holoSphere.getFederated(testHolon, testLens, {
298
254
  aggregate: false,
@@ -352,9 +308,6 @@ describe('Federation Operations', () => {
352
308
  // Set up federation
353
309
  await holoSphere.federate(space1.spacename, space2.spacename);
354
310
 
355
- // Wait for federation to be established
356
- await new Promise(resolve => setTimeout(resolve, 5000));
357
-
358
311
  // Verify federation exists
359
312
  let fedInfo1 = await holoSphere.getFederation(space1.spacename);
360
313
  expect(fedInfo1).toBeDefined();
@@ -364,9 +317,6 @@ describe('Federation Operations', () => {
364
317
  // Remove federation
365
318
  await holoSphere.unfederate(space1.spacename, space2.spacename);
366
319
 
367
- // Wait for unfederation to complete
368
- await new Promise(resolve => setTimeout(resolve, 5000));
369
-
370
320
  // Verify federation is removed
371
321
  fedInfo1 = await holoSphere.getFederation(space1.spacename);
372
322
  const fedInfo2 = await holoSphere.getFederation(space2.spacename);
@@ -391,9 +341,6 @@ describe('Federation Operations', () => {
391
341
  if (strictHoloSphere.currentSpace) {
392
342
  await strictHoloSphere.logout();
393
343
  }
394
-
395
- // Wait for cleanup
396
- await new Promise(resolve => setTimeout(resolve, 1000));
397
344
  });
398
345
 
399
346
  afterAll(async () => {
@@ -401,8 +348,6 @@ describe('Federation Operations', () => {
401
348
  try {
402
349
  await holoSphere.deleteGlobal('spaces', space1.spacename);
403
350
  await holoSphere.deleteGlobal('spaces', space2.spacename);
404
- // Wait for cleanup
405
- await new Promise(resolve => setTimeout(resolve, 1000));
406
351
  } catch (error) {
407
352
  console.error('Error during final cleanup:', error.message);
408
353
  }
@@ -152,8 +152,6 @@ describe('HoloSphere', () => {
152
152
  // Store schema
153
153
  await strictHoloSphere.setSchema(testLensWithIndex, schema);
154
154
 
155
- // Add delay to ensure schema is stored
156
- await new Promise(resolve => setTimeout(resolve, 100));
157
155
 
158
156
  // Retrieve schema
159
157
  const retrievedSchema = await strictHoloSphere.getSchema(testLensWithIndex);
@@ -198,7 +196,6 @@ describe('HoloSphere', () => {
198
196
  // Clean up the strict instance
199
197
  if (strictHoloSphere.gun) {
200
198
  await strictHoloSphere.logout();
201
- await new Promise(resolve => setTimeout(resolve, 100)); // Allow time for cleanup
202
199
  }
203
200
  }, 10000); // Increase timeout to 10 seconds
204
201
 
@@ -220,16 +217,12 @@ describe('HoloSphere', () => {
220
217
  required: ['id', 'value']
221
218
  };
222
219
  expectedSchemas.push({ lens, schema });
223
- // Add small delay between operations to prevent race conditions
224
- await new Promise(resolve => setTimeout(resolve, 50));
225
220
  promises.push(holoSphere.setSchema(lens, schema));
226
221
  }
227
222
 
228
223
  // Wait for all operations to complete
229
224
  await Promise.all(promises);
230
225
 
231
- // Add delay before verification to ensure data is settled
232
- await new Promise(resolve => setTimeout(resolve, 500));
233
226
 
234
227
  // Verify each schema was stored correctly
235
228
  for (const { lens, schema } of expectedSchemas) {
@@ -480,9 +473,6 @@ describe('HoloSphere', () => {
480
473
  await holoSphere.put(testHolon, testLens, storeData);
481
474
  }
482
475
 
483
- // Wait a bit to ensure data is settled
484
- await new Promise(resolve => setTimeout(resolve, 100));
485
-
486
476
  // Get data multiple times
487
477
  const results = await Promise.all(
488
478
  Array.from({ length: 5 }, () => holoSphere.getAll(testHolon, testLens))
@@ -671,27 +661,33 @@ describe('HoloSphere', () => {
671
661
  test('should stop receiving data after unsubscribe', async () => {
672
662
  const testData1 = { id: 'test1', data: 'first' };
673
663
  const testData2 = { id: 'test2', data: 'second' };
674
- let received = false;
664
+ let receivedData = [];
675
665
 
676
- const subscription = await holoSphere.subscribe(testHolon, testLens, async (data) => {
677
- if (!received && data.id === testData1.id) {
678
- received = true;
679
- await subscription.off();
680
-
681
- // Put second piece of data after unsubscribe
682
- await holoSphere.put(testHolon, testLens, testData2);
666
+ return new Promise(async (resolve, reject) => {
667
+ const timeout = setTimeout(() => {
668
+ // If we only received the first piece of data, test passes
669
+ if (receivedData.length === 1 && receivedData[0].id === testData1.id) {
670
+ resolve();
671
+ } else {
672
+ reject(new Error('Test timeout or received unexpected data'));
673
+ }
674
+ }, 5000);
675
+
676
+ const subscription = await holoSphere.subscribe(testHolon, testLens, async (data) => {
677
+ receivedData.push(data);
683
678
 
684
- // Wait a bit to ensure no more data is received
685
- setTimeout(() => {
686
- expect(received).toBe(true);
687
- }, 1000);
688
- } else if (data.id === testData2.id) {
689
- throw new Error('Received data after unsubscribe');
690
- }
691
- });
679
+ if (data.id === testData1.id) {
680
+ subscription.unsubscribe();
681
+ resolve();
682
+ } else if (data.id === testData2.id) {
683
+ clearTimeout(timeout);
684
+ reject(new Error('Received data after unsubscribe'));
685
+ }
686
+ });
692
687
 
693
- // Put first piece of data
694
- await holoSphere.put(testHolon, testLens, testData1);
688
+ // Put first piece of data
689
+ await holoSphere.put(testHolon, testLens, testData1);
690
+ });
695
691
  }, 10000);
696
692
 
697
693
  test('should handle multiple subscriptions', async () => {
@@ -846,16 +842,13 @@ describe('HoloSphere', () => {
846
842
  for (let i = 0; i < numOperations; i++) {
847
843
  const data = { id: `concurrent${i}`, value: `value${i}` };
848
844
  expectedData.push(data);
849
- // Add small delay between operations
850
- await new Promise(resolve => setTimeout(resolve, 50));
851
845
  promises.push(holoSphere.putGlobal(testTable, data));
852
846
  }
853
847
 
854
848
  // Wait for all operations to complete
855
849
  await Promise.all(promises);
856
850
 
857
- // Add delay before verification
858
- await new Promise(resolve => setTimeout(resolve, 500));
851
+
859
852
 
860
853
  // Retrieve and verify data
861
854
  const retrievedData = await holoSphere.getAllGlobal(testTable);
@@ -908,24 +901,31 @@ describe('HoloSphere', () => {
908
901
 
909
902
  test('should validate holon resolution', async () => {
910
903
  const invalidHolon = h3.latLngToCell(40.7128, -74.0060, 0); // Resolution 0
911
- await expect(holoSphere.compute(invalidHolon, testLens, 'summarize'))
904
+ await expect(holoSphere.compute(invalidHolon, testLens, { operation: 'summarize' }))
912
905
  .rejects.toThrow('compute: Invalid holon resolution (must be between 1 and 15)');
913
906
  });
914
907
 
915
908
  test('should validate depth parameters', async () => {
916
- await expect(holoSphere.compute(testHolon, testLens, 'summarize', -1))
917
- .rejects.toThrow('compute: Invalid depth parameter');
918
-
919
- await expect(holoSphere.compute(testHolon, testLens, 'summarize', 0, 0))
920
- .rejects.toThrow('compute: Invalid maxDepth parameter (must be between 1 and 15)');
921
-
922
- await expect(holoSphere.compute(testHolon, testLens, 'summarize', 0, 16))
923
- .rejects.toThrow('compute: Invalid maxDepth parameter (must be between 1 and 15)');
909
+ await expect(holoSphere.compute(testHolon, testLens, {
910
+ operation: 'summarize',
911
+ depth: -1
912
+ })).rejects.toThrow('compute: Invalid depth parameter');
913
+
914
+ await expect(holoSphere.compute(testHolon, testLens, {
915
+ operation: 'summarize',
916
+ maxDepth: 0
917
+ })).rejects.toThrow('compute: Invalid maxDepth parameter (must be between 1 and 15)');
918
+
919
+ await expect(holoSphere.compute(testHolon, testLens, {
920
+ operation: 'summarize',
921
+ maxDepth: 16
922
+ })).rejects.toThrow('compute: Invalid maxDepth parameter (must be between 1 and 15)');
924
923
  });
925
924
 
926
925
  test('should validate operation type', async () => {
927
- await expect(holoSphere.compute(testHolon, testLens, 'invalid-operation'))
928
- .rejects.toThrow('compute: Invalid operation (must be "summarize")');
926
+ await expect(holoSphere.compute(testHolon, testLens, {
927
+ operation: 'invalid-operation'
928
+ })).rejects.toThrow('compute: Invalid operation (must be one of summarize, aggregate, concatenate)');
929
929
  });
930
930
 
931
931
  afterEach(async () => {
@@ -967,8 +967,6 @@ describe('HoloSphere', () => {
967
967
  for (let i = 0; i < numOperations; i++) {
968
968
  const id = `concurrent${i}`;
969
969
  expectedIds.add(id);
970
- // Add small delay between operations to prevent race conditions
971
- await new Promise(resolve => setTimeout(resolve, 50));
972
970
  promises.push(holoSphere.put(testHolon, testLens, {
973
971
  id: id,
974
972
  data: 'test'
@@ -978,9 +976,6 @@ describe('HoloSphere', () => {
978
976
  // Wait for all operations to complete
979
977
  await Promise.all(promises);
980
978
 
981
- // Add delay before verification to ensure data is settled
982
- await new Promise(resolve => setTimeout(resolve, 500));
983
-
984
979
  // Get and verify results
985
980
  const results = await holoSphere.getAll(testHolon, testLens);
986
981
  const resultIds = new Set(results.map(r => r.id));
@@ -1,7 +1,5 @@
1
1
  import HoloSphere from '../holosphere.js';
2
2
  import * as h3 from 'h3-js';
3
- import Gun from 'gun';
4
- import 'gun/sea';
5
3
 
6
4
  describe('Space Authentication and Authorization', () => {
7
5
  // Global HoloSphere instances