holosphere 1.0.4 → 1.0.5

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.
@@ -0,0 +1,22 @@
1
+ declare module 'holosphere' {
2
+ export default class HoloSphere {
3
+ constructor(name: string, openaikey?: string | null);
4
+ subscribe(hex: string, lense: string, callback: (item: any, key: string) => void): void;
5
+ put(hex: string, lense: string, content: any): Promise<void>;
6
+ delete(id: string, tag: string): Promise<void>;
7
+ get(hex: string, lense: string): Promise<any[]>;
8
+ getKey(hex: string, lense: string, key: string): Promise<any | null>;
9
+ getNode(hex: string, lense: string, key: string): Promise<any | null>;
10
+ compute(hex: string, lense: string, operation: string): Promise<void>;
11
+ clearlense(hex: string, lense: string): Promise<void>;
12
+ summarize(history: string): Promise<string>;
13
+ upcast(hex: string, lense: string, content: any): Promise<any>;
14
+ updateParent(id: string, report: string): Promise<any>;
15
+ getHex(lat: number, lng: number, resolution: number): Promise<string>;
16
+ getScalespace(lat: number, lng: number): string[];
17
+ getHexScalespace(hex: string): string[];
18
+ aggregateVotes(hexId: string, topic: string): object;
19
+ delegateVote(userId: string, topic: string, delegateTo: string): Promise<void>;
20
+ vote(userId: string, hexId: string, topic: string, vote: string): Promise<void>;
21
+ }
22
+ }
@@ -4,10 +4,15 @@ import Gun from 'gun'
4
4
  import Ajv2019 from 'ajv/dist/2019.js'
5
5
 
6
6
  class HoloSphere {
7
+ /**
8
+ * Initializes a new instance of the HoloSphere class.
9
+ * @param {string} appname - The name of the application.
10
+ * @param {string|null} openaikey - The OpenAI API key.
11
+ */
7
12
  constructor(appname, openaikey = null) {
8
13
  this.validator = new Ajv2019({ allErrors: false, strict: false });
9
14
  this.gun = Gun({
10
- peers: ['http://gun.holons.io', 'https://59.src.eco/gun'],
15
+ peers: ['https://59.src.eco/gun'],
11
16
  axe: false,
12
17
  // uuid: (content) => { // generate a unique id for each node
13
18
  // console.log('uuid', content);
@@ -15,119 +20,22 @@ class HoloSphere {
15
20
  });
16
21
 
17
22
  this.gun = this.gun.get(appname)
23
+ this.users = {}; // Initialize users
24
+ this.hexagonVotes = {}; // Initialize hexagonVotes
18
25
 
19
26
  if (openaikey != null) {
20
27
  this.openai = new OpenAI({
21
28
  apiKey: openaikey,
22
29
  });
23
30
  }
24
-
25
- //this.bot.command('sethex', async (ctx) => { this.setHex(ctx) }) TODO: MOVE HERE FROM SETTINGS
26
-
27
- // this.bot.command('resethex', async (ctx) => {
28
- // let chatID = ctx.message.chat.id;
29
- // let hex = (await this.db.get('settings', chatID)).hex
30
- // this.delete(hex, ctx.message.text.split(' ')[1])
31
- // })
32
-
33
- // this.bot.command('get', async (ctx) => {
34
- // const chatID = ctx.message.chat.id;
35
- // const lense = ctx.message.text.split(' ')[1];
36
- // if (!lense) {
37
- // return ctx.reply('Please specify a tag.');
38
- // }
39
- // let hex = (await this.db.get('settings', chatID)).hex
40
- // //let hex = settings.hex
41
- // console.log('hex', hex)
42
-
43
- // let data = await this.get(ctx, hex, lense)
44
-
45
- // })
46
-
47
- // this.bot.command('gethex', async (ctx) => {
48
- // let settings = await this.db.get('settings', chatID)
49
- // let id = settings.hex ? settings.hex : 'Hex not set, use /sethex'
50
- // ctx.reply(id)
51
- // })
52
-
53
- // this.bot.command('compute', async (ctx) => {
54
- // const chatID = ctx.message.chat.id;
55
- // let operation = ctx.message.text.split(' ')[1];
56
- // if (operation != 'sum') {
57
- // ctx.reply('Operation not implemented')
58
- // return
59
- // }
60
- // let lense = ctx.message.text.split(' ')[2]
61
- // if (!lense) {
62
- // ctx.reply('Please specify a lense where to perform the operation ')
63
- // return
64
- // }
65
- // let hex = (await this.db.get('settings', chatID)).hex
66
- // await this.compute(hex, lense, operation)
67
-
68
- // // let parentInfo = await this.getCellInfo(parent)
69
- // // parentInfo.wisdom[id] = report
70
- // // //update summary
71
- // // let summary = await this.summarize(Object.values(parentInfo.wisdom).join('\n'))
72
- // // parentInfo.summary = summary
73
-
74
- // // await this.db.put('cell', parentInfo)
75
- // // return parentInfo
76
-
77
- // // let content = await this.db.getAll(hex+'/tags')
78
- // })
79
-
80
-
81
-
82
-
83
- // this.bot.command('cast', async (ctx) => {
84
- // if (!ctx.message.reply_to_message) {
85
- // return ctx.reply('Please reply to a message you want to tag.');
86
- // }
87
- // const tags = ctx.message.text.split(' ').slice(1);
88
- // if (tags.length === 0) {
89
- // return ctx.reply('Please provide at least one tag.');
90
- // }
91
-
92
- // const messageID = ctx.message.reply_to_message.message_id;
93
- // const chatID = ctx.message.chat.id;
94
- // const messageContent = ctx.message.reply_to_message.text;
95
- // let settings = await this.db.get('settings', chatID)
96
- // let id = settings.hex ? settings.hex : 'Hex not set, use /sethex'
97
- // //create root node for the item
98
- // let node = await this.gun.get(chatID + '/' + messageID).put({ id: chatID + '/' + messageID, content: messageContent })
99
- // for (let tag of tags) {
100
- // await this.gun.get(id).get(tag).set(node)
101
- // this.upcast(id, tag, node)
102
- // }
103
- // })
104
-
105
- // this.bot.command('publish', async (ctx) => {
106
- // console.log(ctx.message)
107
- // if (!ctx.message.reply_to_message) {
108
- // return ctx.reply('Please reply to a message you want to tag.');
109
- // }
110
- // const tags = ctx.message.text.split(' ').slice(1);
111
- // if (tags.length === 0) {
112
- // return ctx.reply('Please provide at least one tag.');
113
- // }
114
-
115
- // const messageID = ctx.message.reply_to_message.message_id;
116
- // const chatID = ctx.message.chat.id;
117
- // const messageContent = ctx.message.reply_to_message.text;
118
- // let settings = await this.db.get('settings', chatID)
119
- // let hex = settings.hex
120
-
121
- // for (let tag of tags) {
122
- // let node = await this.gun.get(chatID + '/' + messageID).put({ id: chatID + '/' + messageID, content: messageContent })
123
- // await this.put(hex, tag, node)
124
- // }
125
-
126
- // ctx.reply('Tag published.');
127
- // });
128
-
129
31
  }
130
32
 
33
+ /**
34
+ * Sets the JSON schema for a specific lense.
35
+ * @param {string} lense - The lense identifier.
36
+ * @param {object} schema - The JSON schema to set.
37
+ * @returns {Promise} - Resolves when the schema is set.
38
+ */
131
39
  async setSchema(lense, schema) {
132
40
  return new Promise((resolve, reject) => {
133
41
  this.gun.get(lense).get('schema').put(JSON.stringify(schema), ack => {
@@ -141,6 +49,11 @@ class HoloSphere {
141
49
  })
142
50
  }
143
51
 
52
+ /**
53
+ * Retrieves the JSON schema for a specific lense.
54
+ * @param {string} lense - The lense identifier.
55
+ * @returns {Promise<object|null>} - The retrieved schema or null if not found.
56
+ */
144
57
  async getSchema(lense) {
145
58
  return new Promise((resolve) => {
146
59
  this.gun.get(lense).get('schema').once(data => {
@@ -159,28 +72,21 @@ class HoloSphere {
159
72
  })
160
73
  })
161
74
  }
162
-
163
- async setHex(ctx) {
164
- const chatID = ctx.message.chat.id;
165
- const hex = ctx.message.text.split(' ')[1];
166
- this.db.gun.get(hex).set('chats').put(chatID)
167
- this.db.gun.get('settings').get(chatID).put(hex)
168
- return hex
169
- }
170
-
171
- async getHexContent(ctx) {
172
- const chatID = ctx.message.chat.id;
173
- let settings = await this.getSettings(chatID)
174
- let hex = settings.hex
175
- let content = await this.db.getAll(hex + '/tags')
176
- //console.log(content)
177
- return content ? content[0].id : 'not found'
178
- }
179
-
75
+ /**
76
+ * Deletes a specific tag from a given ID.
77
+ * @param {string} id - The identifier from which to delete the tag.
78
+ * @param {string} tag - The tag to delete.
79
+ */
180
80
  async delete(id, tag) {
181
81
  await this.gun.get(id).get(tag).put(null)
182
82
  }
183
83
 
84
+ /**
85
+ * Stores content in the specified hex and lense.
86
+ * @param {string} hex - The hex identifier.
87
+ * @param {string} lense - The lense under which to store the content.
88
+ * @param {object} content - The content to store.
89
+ */
184
90
  async put(hex, lense, content) {
185
91
  // Retrieve the schema for the lense
186
92
  let schema = await this.getSchema(lense)
@@ -202,28 +108,25 @@ class HoloSphere {
202
108
  noderef = this.gun.get(lense).get(content.id).put(payload)
203
109
  this.gun.get(hex.toString()).get(lense).get(content.id).put(payload)
204
110
  } else { // create a content-addressable reference like IPFS. Note: no updates possible using put
205
- //const hash = createHash("sha256").update(payload).digest("hex");
206
- const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload));
207
- // const hashHex = Array.from(new Uint8Array(hash))
208
- // .map(byte => byte.toString(16).padStart(2, "0"))
209
- // .join("");
210
- noderef = this.gun.get(lense).get(hash).put(payload)
211
- this.gun.get(hex.toString()).get(lense).get(hash).put(payload)
111
+ const hashBuffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload));
112
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
113
+ const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, "0")).join("");
114
+ noderef = this.gun.get(lense).get(hashHex).put(payload)
115
+ this.gun.get(hex.toString()).get(lense).get(hashHex).put(payload)
212
116
  }
213
117
 
214
- // return new Promise((resolve, reject) => {
215
- // this.gun.get(hex.toString()).get(lense).set(noderef, ack => {
216
- // if (ack.err) {
217
- // reject(new Error('Failed to add content: ' + ack.err));
218
- // } else {
219
- // console.log('Content added successfully under tag:', lense);
220
- // resolve(ack);
221
- // }
222
- // });
223
- // });
118
+ }
224
119
 
120
+ async putNode(hex, lense, node) {
121
+ this.gun.get(hex).get(lense).set(node)
225
122
  }
226
123
 
124
+ /**
125
+ * Retrieves content from the specified hex and lense.
126
+ * @param {string} hex - The hex identifier.
127
+ * @param {string} lense - The lense from which to retrieve content.
128
+ * @returns {Promise<Array<object>>} - The retrieved content.
129
+ */
227
130
  async get(hex, lense) {
228
131
  // Wrap the GunDB operation in a promise
229
132
  //retrieve lense schema
@@ -244,11 +147,16 @@ class HoloSphere {
244
147
  this.gun.get(hex.toString()).get(lense).map().once(async (itemdata, key) => {
245
148
  counter += 1
246
149
  if (itemdata) {
247
- // if (itemdata._["#"]) {
248
- // // If the data is a reference, fetch the actual content
249
- // itemdata = await this.gun.get(itemdata._['#']).then();
250
- // console.log("Data :",itemdata)
251
- // }
150
+ if (itemdata._["#"]) {
151
+
152
+ // If the data is a reference, fetch the actual content
153
+ let query = itemdata._['#'].split('/')
154
+ let hex = query[1]
155
+ let lense = query[2]
156
+ let key = query[3]
157
+ itemdata = await this.getNode(hex, lense, key)
158
+
159
+ }
252
160
  var parsed = {}
253
161
  try {
254
162
  parsed = JSON.parse(itemdata);
@@ -283,8 +191,47 @@ class HoloSphere {
283
191
  );
284
192
  }
285
193
 
286
- // Operations
194
+ /**
195
+ * Retrieves a specific key from the specified hex and lense.
196
+ * @param {string} hex - The hex identifier.
197
+ * @param {string} lense - The lense from which to retrieve the key.
198
+ * @param {string} key - The specific key to retrieve.
199
+ * @returns {Promise<object|null>} - The retrieved content or null if not found.
200
+ */
201
+ async getKey(hex, lense, key) {
202
+ return new Promise((resolve) => {
203
+ // Use Gun to get the data
204
+ this.gun.get(hex).get(lense).get(key).once((data, key) => {
205
+ if (data) {
206
+ resolve(JSON.parse(data)); // Resolve the promise with the data if data is found
207
+ } else {
208
+ resolve(null); // Reject the promise if no data is found
209
+ }
210
+ });
211
+ });
212
+
213
+ }
214
+
215
+ /**
216
+ * Retrieves a specific gundb node from the specified hex and lense.
217
+ * @param {string} hex - The hex identifier.
218
+ * @param {string} lense - The lense from which to retrieve the key.
219
+ * @param {string} key - The specific key to retrieve.
220
+ * @returns {Promise<object|null>} - The retrieved content or null if not found.
221
+ */
222
+ getNode(hex, lense, key) {
223
+ // Use Gun to get the data
224
+ return this.gun.get(hex).get(lense).get(key)
225
+ }
226
+
227
+
287
228
 
229
+ /**
230
+ * Computes summaries based on the content within a hex and lense.
231
+ * @param {string} hex - The hex identifier.
232
+ * @param {string} lense - The lense to compute.
233
+ * @param {string} operation - The operation to perform.
234
+ */
288
235
  async compute(hex, lense, operation) {
289
236
 
290
237
  let res = h3.getResolution(hex);
@@ -304,7 +251,7 @@ class HoloSphere {
304
251
  resolve(); // Resolve the promise to prevent it from hanging
305
252
  }, 1000); // Timeout of 5 seconds
306
253
 
307
- this.db.gun.get(siblings[i]).get(lense).map().once((data, key) => {
254
+ this.gun.get(siblings[i]).get(lense).map().once((data, key) => {
308
255
  clearTimeout(timeout); // Clear the timeout if data is received
309
256
  if (data) {
310
257
  content.push(data.content);
@@ -324,6 +271,11 @@ class HoloSphere {
324
271
  this.compute(parent, lense, operation)
325
272
  }
326
273
 
274
+ /**
275
+ * Clears all entities under a specific hex and lense.
276
+ * @param {string} hex - The hex identifier.
277
+ * @param {string} lense - The lense to clear.
278
+ */
327
279
  async clearlense(hex, lense) {
328
280
  let entities = {};
329
281
 
@@ -336,6 +288,11 @@ class HoloSphere {
336
288
  }
337
289
 
338
290
 
291
+ /**
292
+ * Summarizes provided history text using OpenAI.
293
+ * @param {string} history - The history text to summarize.
294
+ * @returns {Promise<string>} - The summarized text.
295
+ */
339
296
  async summarize(history) {
340
297
  if (!this.openai) {
341
298
  return 'OpenAI not initialized, please specify the API key in the constructor.'
@@ -368,21 +325,34 @@ class HoloSphere {
368
325
  return summary
369
326
  }
370
327
 
328
+ /**
329
+ * Upcasts content to parent hexagons recursively.
330
+ * @param {string} hex - The current hex identifier.
331
+ * @param {string} lense - The lense under which to upcast.
332
+ * @param {object} content - The content to upcast.
333
+ * @returns {Promise<object>} - The upcasted content.
334
+ */
371
335
  async upcast(hex, lense, content) {
372
336
  let res = h3.getResolution(hex)
373
-
374
-
375
- console.log('Upcasting ', hex, lense, content)
376
- let parent = h3.cellToParent(hex, res - 1)
377
- await this.put(parent, lense, content)
378
- if (res == 0)
337
+ if (res == 0) {
338
+ await this.putNode(hex, lense, content)
379
339
  return content
380
- else
340
+ }
341
+ else {
342
+ console.log('Upcasting ', hex, lense, content, res)
343
+ await this.putNode(hex, lense, content)
344
+ let parent = h3.cellToParent(hex, res - 1)
381
345
  return this.upcast(parent, lense, content)
346
+ }
382
347
  }
383
348
 
384
349
 
385
- // send information upwards, triggers the parent to update its summary
350
+ /**
351
+ * Updates the parent hexagon with a new report.
352
+ * @param {string} id - The child hex identifier.
353
+ * @param {string} report - The report to update.
354
+ * @returns {Promise<object>} - The updated parent information.
355
+ */
386
356
  async updateParent(id, report) {
387
357
  let cellinfo = await this.getCellInfo(id)
388
358
  let res = h3.getResolution(id)
@@ -398,11 +368,23 @@ class HoloSphere {
398
368
  }
399
369
 
400
370
 
371
+ /**
372
+ * Converts latitude and longitude to a hex identifier.
373
+ * @param {number} lat - The latitude.
374
+ * @param {number} lng - The longitude.
375
+ * @param {number} resolution - The resolution level.
376
+ * @returns {Promise<string>} - The resulting hex identifier.
377
+ */
401
378
  async getHex(lat, lng, resolution) {
402
379
  return h3.latLngToCell(lat, lng, resolution);
403
380
  }
404
381
 
405
- // returns the list of all the containing hexagons at xall scales
382
+ /**
383
+ * Retrieves all containing hexagons at all scales for given coordinates.
384
+ * @param {number} lat - The latitude.
385
+ * @param {number} lng - The longitude.
386
+ * @returns {Array<string>} - List of hex identifiers.
387
+ */
406
388
  getScalespace(lat, lng) {
407
389
  let list = []
408
390
  let cell = h3.latLngToCell(lat, lng, 14);
@@ -413,7 +395,11 @@ class HoloSphere {
413
395
  return list
414
396
  }
415
397
 
416
- // returns the list of all the containing hexagons at xall scales
398
+ /**
399
+ * Retrieves all containing hexagons at all scales for a given hex.
400
+ * @param {string} hex - The hex identifier.
401
+ * @returns {Array<string>} - List of hex identifiers.
402
+ */
417
403
  getHexScalespace(hex) {
418
404
  let list = []
419
405
  let res = h3.getResolution(hex)
@@ -423,43 +409,73 @@ class HoloSphere {
423
409
  return list
424
410
  }
425
411
 
412
+ /**
413
+ * Subscribes to changes in a specific hex and lense.
414
+ * @param {string} hex - The hex identifier.
415
+ * @param {string} lense - The lense to subscribe to.
416
+ * @param {function} callback - The callback to execute on changes.
417
+ */
426
418
  subscribe(hex, lense, callback) {
427
419
  this.gun.get(hex).get(lense).map().on((data, key) => {
428
420
  callback(data, key)
429
421
  })
430
422
  }
431
423
 
432
- // VOTING SYSTEM
424
+ /**
425
+ * Retrieves the final vote for a user, considering delegations.
426
+ * @param {string} userId - The user's identifier.
427
+ * @param {string} topic - The voting topic.
428
+ * @param {object} votes - The current votes.
429
+ * @param {Set<string>} [visited=new Set()] - Set of visited users to prevent cycles.
430
+ * @returns {string|null} - The final vote or null if not found.
431
+ */
432
+ getFinalVote(userId, topic, votes, visited = new Set()) {
433
+ if (this.users[userId]) { // Added this.users
434
+ if (visited.has(userId)) {
435
+ return null; // Avoid circular delegations
436
+ }
437
+ visited.add(userId);
433
438
 
434
- getFinalVote(userId, topic, votes, visited = new Set()) {
435
- if (visited.has(userId)) {
436
- return null; // Avoid circular delegations
437
- }
438
- visited.add(userId);
439
+ const delegation = this.users[userId].delegations[topic];
440
+ if (delegation && votes[delegation] === undefined) {
441
+ return this.getFinalVote(delegation, topic, votes, visited); // Prefixed with this
442
+ }
439
443
 
440
- const delegation = users[userId].delegations[topic];
441
- if (delegation && votes[delegation] === undefined) {
442
- return getFinalVote(delegation, topic, votes, visited); // Follow delegation
443
- }
444
+ return votes[userId] !== undefined ? votes[userId] : null;
445
+ }
446
+ return null;
447
+ }
444
448
 
445
- return votes[userId] !== undefined ? votes[userId] : null; // Return direct vote or null
446
- }
449
+ /**
450
+ * Aggregates votes for a specific hex and topic.
451
+ * @param {string} hexId - The hex identifier.
452
+ * @param {string} topic - The voting topic.
453
+ * @returns {object} - Aggregated vote counts.
454
+ */
455
+ aggregateVotes(hexId, topic) {
456
+ if (!this.hexagonVotes[hexId] || !this.hexagonVotes[hexId][topic]) {
457
+ return {}; // Handle undefined votes
458
+ }
459
+ const votes = this.hexagonVotes[hexId][topic];
460
+ const aggregatedVotes = {};
447
461
 
448
- aggregateVotes(hexId, topic) {
449
- const votes = hexagonVotes[hexId][topic];
450
- const aggregatedVotes = {};
462
+ Object.keys(votes).forEach(userId => {
463
+ const finalVote = this.getFinalVote(userId, topic, votes); // Prefixed with this
464
+ if (finalVote !== null) {
465
+ aggregatedVotes[finalVote] = (aggregatedVotes[finalVote] || 0) + 1;
466
+ }
467
+ });
451
468
 
452
- Object.keys(votes).forEach(userId => {
453
- const finalVote = getFinalVote(userId, topic, votes);
454
- if (finalVote !== null) {
455
- aggregatedVotes[finalVote] = (aggregatedVotes[finalVote] || 0) + 1;
469
+ return aggregatedVotes;
456
470
  }
457
- });
458
471
 
459
- return aggregatedVotes;
460
- }
461
-
462
- async delegateVote(userId, topic, delegateTo) {
472
+ /**
473
+ * Delegates a user's vote to another user.
474
+ * @param {string} userId - The user's identifier.
475
+ * @param {string} topic - The voting topic.
476
+ * @param {string} delegateTo - The user to delegate the vote to.
477
+ */
478
+ async delegateVote(userId, topic, delegateTo) {
463
479
  const response = await fetch('/delegate', {
464
480
  method: 'POST',
465
481
  headers: { 'Content-Type': 'application/json' },
@@ -468,6 +484,13 @@ async delegateVote(userId, topic, delegateTo) {
468
484
  alert(await response.text());
469
485
  }
470
486
 
487
+ /**
488
+ * Casts a vote for a user on a specific topic and hex.
489
+ * @param {string} userId - The user's identifier.
490
+ * @param {string} hexId - The hex identifier.
491
+ * @param {string} topic - The voting topic.
492
+ * @param {string} vote - The vote choice.
493
+ */
471
494
  async vote(userId, hexId, topic, vote) {
472
495
  const response = await fetch('/vote', {
473
496
  method: 'POST',
@@ -478,6 +501,8 @@ async delegateVote(userId, topic, delegateTo) {
478
501
  }
479
502
 
480
503
 
504
+
505
+
481
506
  }
482
507
 
483
508
  export default HoloSphere;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "holosphere",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Holonic Geospatial Communication Infrastructure based on h3.js and gun.js",
5
- "main": "index.js",
5
+ "main": "holosphere.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "test": "echo \"Error: no test specified\" && exit 1"