holosphere 1.0.8 → 1.1.1
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/README.md +268 -113
- package/holosphere.d.ts +43 -49
- package/holosphere.js +571 -277
- package/package.json +4 -2
- package/test/holosphere.test.js +224 -109
package/holosphere.js
CHANGED
|
@@ -3,26 +3,30 @@ import OpenAI from 'openai';
|
|
|
3
3
|
import Gun from 'gun'
|
|
4
4
|
import Ajv2019 from 'ajv/dist/2019.js'
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
class HoloSphere {
|
|
7
8
|
/**
|
|
8
9
|
* Initializes a new instance of the HoloSphere class.
|
|
9
10
|
* @param {string} appname - The name of the application.
|
|
11
|
+
* @param {boolean} strict - Whether to enforce strict schema validation.
|
|
10
12
|
* @param {string|null} openaikey - The OpenAI API key.
|
|
11
13
|
*/
|
|
12
|
-
constructor(appname, openaikey = null) {
|
|
13
|
-
this.
|
|
14
|
+
constructor(appname, strict = false, openaikey = null) {
|
|
15
|
+
this.appname = appname
|
|
16
|
+
this.strict = strict;
|
|
17
|
+
this.validator = new Ajv2019({
|
|
18
|
+
allErrors: true,
|
|
19
|
+
strict: false, // Keep this false to avoid Ajv strict mode issues
|
|
20
|
+
validateSchema: true // Always validate schemas
|
|
21
|
+
});
|
|
14
22
|
this.gun = Gun({
|
|
15
|
-
peers: ['
|
|
23
|
+
peers: ['https://gun.holons.io', 'https://59.src.eco/gun'],
|
|
16
24
|
axe: false,
|
|
17
25
|
// uuid: (content) => { // generate a unique id for each node
|
|
18
26
|
// console.log('uuid', content);
|
|
19
27
|
// return content;}
|
|
20
28
|
});
|
|
21
29
|
|
|
22
|
-
this.gun = this.gun.get(appname)
|
|
23
|
-
this.users = {}; // Initialize users
|
|
24
|
-
this.holonagonVotes = {}; // Initialize holonagonVotes
|
|
25
|
-
|
|
26
30
|
if (openaikey != null) {
|
|
27
31
|
this.openai = new OpenAI({
|
|
28
32
|
apiKey: openaikey,
|
|
@@ -30,6 +34,8 @@ class HoloSphere {
|
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
// ================================ SCHEMA FUNCTIONS ================================
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* Sets the JSON schema for a specific lens.
|
|
35
41
|
* @param {string} lens - The lens identifier.
|
|
@@ -37,16 +43,84 @@ class HoloSphere {
|
|
|
37
43
|
* @returns {Promise} - Resolves when the schema is set.
|
|
38
44
|
*/
|
|
39
45
|
async setSchema(lens, schema) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
if (!lens || !schema) {
|
|
47
|
+
console.error('setSchema: Missing required parameters');
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Basic schema validation - check for required fields
|
|
52
|
+
if (!schema.type || typeof schema.type !== 'string') {
|
|
53
|
+
console.error('setSchema: Schema must have a type field');
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (this.strict) {
|
|
58
|
+
try {
|
|
59
|
+
// Validate schema against JSON Schema meta-schema
|
|
60
|
+
const metaSchema = {
|
|
61
|
+
type: 'object',
|
|
62
|
+
required: ['type', 'properties'],
|
|
63
|
+
properties: {
|
|
64
|
+
type: { type: 'string' },
|
|
65
|
+
properties: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
additionalProperties: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
required: ['type'],
|
|
70
|
+
properties: {
|
|
71
|
+
type: { type: 'string' }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
required: {
|
|
76
|
+
type: 'array',
|
|
77
|
+
items: { type: 'string' }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const valid = this.validator.validate(metaSchema, schema);
|
|
83
|
+
if (!valid) {
|
|
84
|
+
console.error('setSchema: Invalid schema structure:', this.validator.errors);
|
|
85
|
+
return false;
|
|
47
86
|
}
|
|
48
|
-
|
|
49
|
-
|
|
87
|
+
|
|
88
|
+
// Additional strict mode checks
|
|
89
|
+
if (!schema.properties || typeof schema.properties !== 'object') {
|
|
90
|
+
console.error('setSchema: Schema must have properties in strict mode');
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
|
|
95
|
+
console.error('setSchema: Schema must have required fields in strict mode');
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('setSchema: Schema validation error:', error);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
try {
|
|
106
|
+
const schemaString = JSON.stringify(schema);
|
|
107
|
+
this.gun.get(this.appname)
|
|
108
|
+
.get(lens)
|
|
109
|
+
.get('schema')
|
|
110
|
+
.put(schemaString, ack => {
|
|
111
|
+
if (ack.err) {
|
|
112
|
+
console.error('Failed to add schema:', ack.err);
|
|
113
|
+
resolve(false);
|
|
114
|
+
} else {
|
|
115
|
+
console.log('Schema added successfully for lens:', lens);
|
|
116
|
+
resolve(true);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error('setSchema: Error stringifying schema:', error);
|
|
121
|
+
resolve(false);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
50
124
|
}
|
|
51
125
|
|
|
52
126
|
/**
|
|
@@ -55,236 +129,455 @@ class HoloSphere {
|
|
|
55
129
|
* @returns {Promise<object|null>} - The retrieved schema or null if not found.
|
|
56
130
|
*/
|
|
57
131
|
async getSchema(lens) {
|
|
132
|
+
if (!lens) {
|
|
133
|
+
console.error('getSchema: Missing lens parameter');
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
58
137
|
return new Promise((resolve) => {
|
|
59
|
-
this.gun.get(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
138
|
+
this.gun.get(this.appname)
|
|
139
|
+
.get(lens)
|
|
140
|
+
.get('schema')
|
|
141
|
+
.once(data => {
|
|
142
|
+
if (!data) {
|
|
143
|
+
resolve(null);
|
|
144
|
+
return;
|
|
64
145
|
}
|
|
65
|
-
|
|
66
|
-
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
// If data is already a string, parse it
|
|
149
|
+
if (typeof data === 'string') {
|
|
150
|
+
resolve(JSON.parse(data));
|
|
151
|
+
}
|
|
152
|
+
// If data is an object with a string value (GunDB format)
|
|
153
|
+
else if (typeof data === 'object' && data !== null) {
|
|
154
|
+
const schemaStr = Object.values(data).find(v =>
|
|
155
|
+
typeof v === 'string' && v.includes('"type":'));
|
|
156
|
+
if (schemaStr) {
|
|
157
|
+
resolve(JSON.parse(schemaStr));
|
|
158
|
+
} else {
|
|
159
|
+
resolve(null);
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
resolve(null);
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error('getSchema: Error parsing schema:', error);
|
|
166
|
+
resolve(null);
|
|
67
167
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
resolve(null);
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
}
|
|
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
|
-
*/
|
|
80
|
-
async delete(id, tag) {
|
|
81
|
-
await this.gun.get(id).get(tag).put(null)
|
|
168
|
+
});
|
|
169
|
+
});
|
|
82
170
|
}
|
|
83
171
|
|
|
172
|
+
// ================================ CONTENT FUNCTIONS ================================
|
|
173
|
+
|
|
84
174
|
/**
|
|
85
175
|
* Stores content in the specified holon and lens.
|
|
86
176
|
* @param {string} holon - The holon identifier.
|
|
87
177
|
* @param {string} lens - The lens under which to store the content.
|
|
88
|
-
* @param {object}
|
|
178
|
+
* @param {object} data - The data to store.
|
|
179
|
+
* @returns {Promise<boolean>} - Returns true if successful, false if there was an error
|
|
89
180
|
*/
|
|
90
|
-
async put(holon, lens,
|
|
91
|
-
if (!holon || !lens || !
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
181
|
+
async put(holon, lens, data) {
|
|
182
|
+
if (!holon || !lens || !data) {
|
|
183
|
+
console.error('put: Missing required parameters:', { holon, lens, data });
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!data.id) {
|
|
188
|
+
console.error('put: Data must have an id field');
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Strict validation of schema and data
|
|
193
|
+
const schema = await this.getSchema(lens);
|
|
95
194
|
if (schema) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
195
|
+
try {
|
|
196
|
+
const valid = this.validator.validate(schema, data);
|
|
197
|
+
if (!valid) {
|
|
198
|
+
const errors = this.validator.errors;
|
|
199
|
+
console.error('put: Schema validation failed:', errors);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error('put: Schema validation error:', error);
|
|
204
|
+
return false;
|
|
101
205
|
}
|
|
206
|
+
} else if (this.strict) {
|
|
207
|
+
console.error('put: Schema required in strict mode for lens:', lens);
|
|
208
|
+
return false;
|
|
102
209
|
}
|
|
103
210
|
|
|
104
|
-
|
|
105
|
-
|
|
211
|
+
return new Promise((resolve) => {
|
|
212
|
+
try {
|
|
213
|
+
const payload = JSON.stringify(data);
|
|
214
|
+
|
|
215
|
+
this.gun.get(this.appname)
|
|
216
|
+
.get(holon)
|
|
217
|
+
.get(lens)
|
|
218
|
+
.get(data.id)
|
|
219
|
+
.put(payload, ack => {
|
|
220
|
+
if (ack.err) {
|
|
221
|
+
console.error("Error adding data to GunDB:", ack.err);
|
|
222
|
+
resolve(false);
|
|
223
|
+
} else {
|
|
224
|
+
resolve(true);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error('Error in put operation:', error);
|
|
229
|
+
resolve(false);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
106
233
|
|
|
107
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Retrieves content from the specified holon and lens.
|
|
236
|
+
* @param {string} holon - The holon identifier.
|
|
237
|
+
* @param {string} lens - The lens from which to retrieve content.
|
|
238
|
+
* @returns {Promise<Array<object>>} - The retrieved content.
|
|
239
|
+
*/
|
|
240
|
+
async getAll(holon, lens) {
|
|
241
|
+
if (!holon || !lens) {
|
|
242
|
+
console.error('getAll: Missing required parameters:', { holon, lens });
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
108
245
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload));
|
|
114
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
115
|
-
const hashholon = hashArray.map(byte => byte.toString(16).padStart(2, "0")).join("");
|
|
116
|
-
noderef = this.gun.get(lens).get(hashholon).put(payload)
|
|
117
|
-
this.gun.get(holon.toString()).get(lens).get(hashholon).put(payload)
|
|
246
|
+
const schema = await this.getSchema(lens);
|
|
247
|
+
if (!schema && this.strict) {
|
|
248
|
+
console.error('getAll: Schema required in strict mode for lens:', lens);
|
|
249
|
+
return [];
|
|
118
250
|
}
|
|
119
251
|
|
|
120
|
-
|
|
252
|
+
return new Promise((resolve) => {
|
|
253
|
+
let output = [];
|
|
254
|
+
let counter = 0;
|
|
121
255
|
|
|
122
|
-
|
|
123
|
-
|
|
256
|
+
this.gun.get(this.appname).get(holon).get(lens).once((data, key) => {
|
|
257
|
+
if (!data) {
|
|
258
|
+
resolve(output);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const mapLength = Object.keys(data).length - 1;
|
|
263
|
+
|
|
264
|
+
this.gun.get(this.appname).get(holon).get(lens).map().once(async (itemdata, key) => {
|
|
265
|
+
counter += 1;
|
|
266
|
+
if (itemdata) {
|
|
267
|
+
try {
|
|
268
|
+
const parsed = await this.parse(itemdata);
|
|
269
|
+
if (schema) {
|
|
270
|
+
const valid = this.validator.validate(schema, parsed);
|
|
271
|
+
if (valid) {
|
|
272
|
+
output.push(parsed);
|
|
273
|
+
} else if (this.strict) {
|
|
274
|
+
console.warn('Invalid data removed:', key, this.validator.errors);
|
|
275
|
+
await this.delete(holon, lens, key);
|
|
276
|
+
} else {
|
|
277
|
+
console.warn('Invalid data found:', key, this.validator.errors);
|
|
278
|
+
output.push(parsed);
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
output.push(parsed);
|
|
282
|
+
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error('Error parsing data:', error);
|
|
285
|
+
if (this.strict) {
|
|
286
|
+
await this.delete(holon, lens, key);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (counter === mapLength) {
|
|
292
|
+
resolve(output);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|
|
124
297
|
}
|
|
125
298
|
|
|
126
|
-
|
|
127
|
-
|
|
299
|
+
/**
|
|
300
|
+
* Parses data from GunDB, handling various data formats and references.
|
|
301
|
+
* @param {*} data - The data to parse, could be a string, object, or GunDB reference.
|
|
302
|
+
* @returns {Promise<object>} - The parsed data.
|
|
303
|
+
*/
|
|
304
|
+
async parse(rawData) {
|
|
305
|
+
let parsedData = {};
|
|
306
|
+
if (rawData.soul) {
|
|
307
|
+
console.log('Parsing link:', rawData.soul);
|
|
308
|
+
this.getNodeRef(rawData.soul).once (data => {return JSON.parse(data)});
|
|
309
|
+
}
|
|
128
310
|
|
|
129
|
-
if (typeof
|
|
130
|
-
if (
|
|
311
|
+
if (typeof rawData === 'object' && rawData !== null) {
|
|
312
|
+
if (rawData._ && rawData._["#"]) {
|
|
313
|
+
console.log('Parsing object reference:', rawData._['#']);
|
|
131
314
|
// If the data is a reference, fetch the actual content
|
|
132
|
-
let
|
|
133
|
-
let
|
|
134
|
-
let
|
|
135
|
-
let
|
|
136
|
-
|
|
137
|
-
} else if (
|
|
138
|
-
|
|
139
|
-
|
|
315
|
+
let pathParts = rawData._['#'].split('/');
|
|
316
|
+
let hexId = pathParts[1];
|
|
317
|
+
let lensId = pathParts[2];
|
|
318
|
+
let dataKey = pathParts[3];
|
|
319
|
+
parsedData = await this.get(hexId, lensId, dataKey);
|
|
320
|
+
} else if (rawData._ && rawData._['>']) {
|
|
321
|
+
console.log('Parsing objectnode:', rawData._['>']);
|
|
322
|
+
// This might be a GunDB node, try to get its value
|
|
323
|
+
const nodeValue = Object.values(rawData).find(v => typeof v !== 'object' && v !== '_');
|
|
140
324
|
if (nodeValue) {
|
|
141
325
|
try {
|
|
142
|
-
|
|
326
|
+
parsedData = JSON.parse(nodeValue);
|
|
143
327
|
} catch (e) {
|
|
144
328
|
console.log('Invalid JSON in node value:', nodeValue);
|
|
145
|
-
|
|
329
|
+
parsedData = nodeValue; // return the raw data
|
|
146
330
|
}
|
|
147
331
|
} else {
|
|
148
|
-
console.log('Unable to parse
|
|
149
|
-
|
|
332
|
+
console.log('Unable to parse GunDB node:', rawData);
|
|
333
|
+
parsedData = rawData; // return the original data
|
|
150
334
|
}
|
|
151
335
|
} else {
|
|
152
|
-
// Treat it as
|
|
153
|
-
|
|
336
|
+
// Treat it as object data
|
|
337
|
+
console.log('Parsing object data:', rawData);
|
|
338
|
+
parsedData = rawData;
|
|
154
339
|
}
|
|
155
340
|
} else {
|
|
156
341
|
// If it's not an object, try parsing it as JSON
|
|
157
342
|
try {
|
|
158
|
-
|
|
343
|
+
parsedData = JSON.parse(rawData);
|
|
344
|
+
//if the data has a soul, return the soul node
|
|
345
|
+
if (parsedData.soul) {
|
|
346
|
+
console.log('Parsing link:', parsedData.soul);
|
|
347
|
+
parsedData = await this.get(parsedData.soul.split('/')[1], parsedData.soul.split('/')[2], parsedData.soul.split('/')[3]);
|
|
348
|
+
}
|
|
159
349
|
} catch (e) {
|
|
160
|
-
console.log('
|
|
161
|
-
|
|
350
|
+
console.log('Failed to parse, returning raw data', e);
|
|
351
|
+
parsedData = rawData; // return the raw data
|
|
162
352
|
}
|
|
163
353
|
}
|
|
164
354
|
|
|
165
|
-
return
|
|
355
|
+
return parsedData;
|
|
166
356
|
}
|
|
167
357
|
|
|
168
358
|
/**
|
|
169
|
-
* Retrieves
|
|
359
|
+
* Retrieves a specific key from the specified holon and lens.
|
|
170
360
|
* @param {string} holon - The holon identifier.
|
|
171
|
-
* @param {string} lens - The lens from which to retrieve
|
|
172
|
-
* @
|
|
361
|
+
* @param {string} lens - The lens from which to retrieve the key.
|
|
362
|
+
* @param {string} key - The specific key to retrieve.
|
|
363
|
+
* @returns {Promise<object|null>} - The retrieved content or null if not found.
|
|
173
364
|
*/
|
|
174
|
-
async get(holon, lens) {
|
|
175
|
-
if (!holon || !lens) {
|
|
176
|
-
console.
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
// Wrap the gun operation in a promise
|
|
180
|
-
//retrieve lens schema
|
|
181
|
-
const schema = await this.getSchema(lens);
|
|
182
|
-
|
|
183
|
-
if (!schema) {
|
|
184
|
-
console.log('The schema for "' + lens + '" is not defined');
|
|
185
|
-
// return null; // No schema found, return null if strict about it
|
|
365
|
+
async get(holon, lens, key) {
|
|
366
|
+
if (!holon || !lens || !key) {
|
|
367
|
+
console.error('get: Missing required parameters:', { holon, lens, key });
|
|
368
|
+
return null;
|
|
186
369
|
}
|
|
187
370
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
let counter = 0
|
|
191
|
-
this.gun.get(holon.toString()).get(lens).once((data, key) => {
|
|
192
|
-
if (data) {
|
|
193
|
-
const maplenght = Object.keys(data).length - 1
|
|
194
|
-
console.log('Map length:', maplenght)
|
|
195
|
-
this.gun.get(holon.toString()).get(lens).map().once(async (itemdata, key) => {
|
|
196
|
-
counter += 1
|
|
197
|
-
if (itemdata) {
|
|
198
|
-
let parsed = await this.parse (itemdata)
|
|
199
|
-
|
|
371
|
+
// Get schema for validation
|
|
372
|
+
const schema = await this.getSchema(lens);
|
|
200
373
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
374
|
+
return new Promise((resolve) => {
|
|
375
|
+
let timeout = setTimeout(() => {
|
|
376
|
+
console.warn('get: Operation timed out');
|
|
377
|
+
resolve(null);
|
|
378
|
+
}, 5000); // 5 second timeout
|
|
379
|
+
|
|
380
|
+
this.gun.get(this.appname)
|
|
381
|
+
.get(holon)
|
|
382
|
+
.get(lens)
|
|
383
|
+
.get(key)
|
|
384
|
+
.once((data,key) => {
|
|
385
|
+
clearTimeout(timeout);
|
|
386
|
+
|
|
387
|
+
if (!data) {
|
|
388
|
+
resolve(null);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
206
391
|
|
|
207
|
-
|
|
208
|
-
|
|
392
|
+
try {
|
|
393
|
+
const parsed = this.parse(data);
|
|
394
|
+
|
|
395
|
+
// Validate against schema if one exists
|
|
396
|
+
if (schema) {
|
|
397
|
+
const valid = this.validator.validate(schema, parsed);
|
|
398
|
+
if (!valid) {
|
|
399
|
+
console.error('get: Invalid data according to schema:', this.validator.errors);
|
|
400
|
+
if (this.strict) {
|
|
401
|
+
resolve(null);
|
|
402
|
+
return;
|
|
209
403
|
}
|
|
210
404
|
}
|
|
211
|
-
else {
|
|
212
|
-
output.push(parsed);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (counter == maplenght) {
|
|
217
|
-
resolve(output);
|
|
218
405
|
}
|
|
406
|
+
resolve(parsed);
|
|
407
|
+
} catch (error) {
|
|
408
|
+
console.error('Error parsing data:', error);
|
|
409
|
+
resolve(null);
|
|
219
410
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Deletes a specific key from a given holon and lens.
|
|
417
|
+
* @param {string} holon - The holon identifier.
|
|
418
|
+
* @param {string} lens - The lens from which to delete the key.
|
|
419
|
+
* @param {string} key - The specific key to delete.
|
|
420
|
+
*/
|
|
421
|
+
async delete(holon, lens, key) {
|
|
422
|
+
return new Promise((resolve, reject) => {
|
|
423
|
+
this.gun.get(this.appname).get(holon).get(lens).get(key).put(null, ack => {
|
|
424
|
+
if (ack.err) {
|
|
425
|
+
resolve(ack.err);
|
|
426
|
+
} else {
|
|
427
|
+
resolve(ack.ok);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Deletes all keys from a given holon and lens.
|
|
435
|
+
* @param {string} holon - The holon identifier.
|
|
436
|
+
* @param {string} lens - The lens from which to delete all keys.
|
|
437
|
+
* @returns {Promise<boolean>} - Returns true if successful, false if there was an error
|
|
438
|
+
*/
|
|
439
|
+
async deleteAll(holon, lens) {
|
|
440
|
+
if (!holon || !lens) {
|
|
441
|
+
console.error('deleteAll: Missing holon or lens parameter');
|
|
442
|
+
return false;
|
|
223
443
|
}
|
|
224
|
-
|
|
444
|
+
|
|
445
|
+
return new Promise((resolve) => {
|
|
446
|
+
let deletionPromises = [];
|
|
447
|
+
|
|
448
|
+
// First get all the data to find keys to delete
|
|
449
|
+
this.gun.get(this.appname).get(holon).get(lens).once((data) => {
|
|
450
|
+
if (!data) {
|
|
451
|
+
resolve(true); // Nothing to delete
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Get all keys except Gun's metadata key '_'
|
|
456
|
+
const keys = Object.keys(data).filter(key => key !== '_');
|
|
457
|
+
|
|
458
|
+
// Create deletion promises for each key
|
|
459
|
+
keys.forEach(key => {
|
|
460
|
+
deletionPromises.push(
|
|
461
|
+
new Promise((resolveDelete) => {
|
|
462
|
+
this.gun.get(this.appname).get(holon).get(lens).get(key).put(null, ack => {
|
|
463
|
+
resolveDelete(!!ack.ok); // Convert to boolean
|
|
464
|
+
});
|
|
465
|
+
})
|
|
466
|
+
);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Wait for all deletions to complete
|
|
470
|
+
Promise.all(deletionPromises)
|
|
471
|
+
.then(results => {
|
|
472
|
+
const allSuccessful = results.every(result => result === true);
|
|
473
|
+
resolve(allSuccessful);
|
|
474
|
+
})
|
|
475
|
+
.catch(error => {
|
|
476
|
+
console.error('Error in deleteAll:', error);
|
|
477
|
+
resolve(false);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
});
|
|
225
481
|
}
|
|
226
482
|
|
|
227
|
-
|
|
228
|
-
|
|
483
|
+
// ================================ NODE FUNCTIONS ================================
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Stores a specific gun node in a given holon and lens.
|
|
229
488
|
* @param {string} holon - The holon identifier.
|
|
230
|
-
* @param {string} lens - The lens
|
|
231
|
-
* @param {
|
|
232
|
-
* @returns {Promise<object|null>} - The retrieved content or null if not found.
|
|
489
|
+
* @param {string} lens - The lens under which to store the node.
|
|
490
|
+
* @param {object} node - The node to store.
|
|
233
491
|
*/
|
|
234
|
-
|
|
492
|
+
async putNode(holon, lens, node) {
|
|
235
493
|
return new Promise((resolve) => {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
try {
|
|
241
|
-
let parsed = JSON.parse(data); // Resolve the promise with the data if data is found
|
|
242
|
-
resolve(parsed);
|
|
243
|
-
}
|
|
244
|
-
catch (e) {
|
|
245
|
-
resolve(data)
|
|
246
|
-
}
|
|
247
|
-
|
|
494
|
+
this.gun.get(this.appname).get(holon).get(lens).put(node, ack => {
|
|
495
|
+
if (ack.err) {
|
|
496
|
+
console.error("Error adding data to GunDB:", ack.err);
|
|
497
|
+
resolve(false);
|
|
248
498
|
} else {
|
|
249
|
-
resolve(
|
|
499
|
+
resolve(true);
|
|
250
500
|
}
|
|
251
501
|
});
|
|
252
502
|
});
|
|
253
|
-
|
|
254
503
|
}
|
|
255
504
|
|
|
256
505
|
/**
|
|
257
506
|
* Retrieves a specific gun node from the specified holon and lens.
|
|
258
507
|
* @param {string} holon - The holon identifier.
|
|
259
|
-
* @param {string} lens - The lens
|
|
508
|
+
* @param {string} lens - The lens identifier.
|
|
260
509
|
* @param {string} key - The specific key to retrieve.
|
|
261
|
-
* @returns {Promise<object|null>} - The retrieved
|
|
510
|
+
* @returns {Promise<object|null>} - The retrieved node or null if not found.
|
|
262
511
|
*/
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
512
|
+
getNode(holon, lens, key) {
|
|
513
|
+
if (!holon || !lens || !key) {
|
|
514
|
+
console.error('getNode: Missing required parameters');
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return this.gun.get(this.appname)
|
|
519
|
+
.get(holon)
|
|
520
|
+
.get(lens)
|
|
521
|
+
.get(key)
|
|
522
|
+
|
|
266
523
|
}
|
|
267
524
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
525
|
+
getNodeRef(soul) {
|
|
526
|
+
const parts = soul.split('/');
|
|
527
|
+
let ref = this.gun.get(this.appname);
|
|
528
|
+
parts.forEach(part => {
|
|
529
|
+
ref = ref.get(part);
|
|
530
|
+
});
|
|
531
|
+
return ref;
|
|
271
532
|
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Deletes a specific gun node from a given holon and lens.
|
|
536
|
+
* @param {string} holon - The holon identifier.
|
|
537
|
+
* @param {string} lens - The lens identifier.
|
|
538
|
+
* @param {string} key - The key of the node to delete.
|
|
539
|
+
* @returns {Promise<boolean>} - Returns true if successful
|
|
540
|
+
*/
|
|
541
|
+
async deleteNode(holon, lens, key) {
|
|
542
|
+
if (!holon || !lens || !key) {
|
|
543
|
+
console.error('deleteNode: Missing required parameters');
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return new Promise((resolve) => {
|
|
548
|
+
this.gun.get(this.appname)
|
|
549
|
+
.get(holon)
|
|
550
|
+
.get(lens)
|
|
551
|
+
.get(key)
|
|
552
|
+
.put(null, ack => {
|
|
553
|
+
if (ack.err) {
|
|
554
|
+
console.error('deleteNode: Error deleting node:', ack.err);
|
|
555
|
+
resolve(false);
|
|
556
|
+
} else {
|
|
557
|
+
resolve(true);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
272
563
|
// ================================ GLOBAL FUNCTIONS ================================
|
|
273
564
|
/**
|
|
274
565
|
* Stores data in a global (non-holon-specific) table.
|
|
275
|
-
* @param {string}
|
|
566
|
+
* @param {string} tableName - The table name to store data in.
|
|
276
567
|
* @param {object} data - The data to store. If it has an 'id' field, it will be used as the key.
|
|
277
568
|
* @returns {Promise<void>}
|
|
278
569
|
*/
|
|
279
570
|
async putGlobal(tableName, data) {
|
|
571
|
+
|
|
280
572
|
return new Promise((resolve, reject) => {
|
|
281
573
|
if (!tableName || !data) {
|
|
282
574
|
reject(new Error('Table name and data are required'));
|
|
283
575
|
return;
|
|
284
576
|
}
|
|
285
577
|
|
|
578
|
+
|
|
286
579
|
if (data.id) {
|
|
287
|
-
this.gun.get(tableName).get(data.id).put(JSON.stringify(data), ack => {
|
|
580
|
+
this.gun.get(this.appname).get(tableName).get(data.id).put(JSON.stringify(data), ack => {
|
|
288
581
|
if (ack.err) {
|
|
289
582
|
reject(new Error(ack.err));
|
|
290
583
|
} else {
|
|
@@ -292,7 +585,7 @@ class HoloSphere {
|
|
|
292
585
|
}
|
|
293
586
|
});
|
|
294
587
|
} else {
|
|
295
|
-
this.gun.get(tableName).put(JSON.stringify(data), ack => {
|
|
588
|
+
this.gun.get(this.appname).get(tableName).put(JSON.stringify(data), ack => {
|
|
296
589
|
if (ack.err) {
|
|
297
590
|
reject(new Error(ack.err));
|
|
298
591
|
} else {
|
|
@@ -303,21 +596,21 @@ class HoloSphere {
|
|
|
303
596
|
});
|
|
304
597
|
}
|
|
305
598
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
599
|
+
/**
|
|
600
|
+
* Retrieves a specific key from a global table.
|
|
601
|
+
* @param {string} tableName - The table name to retrieve from.
|
|
602
|
+
* @param {string} key - The key to retrieve.
|
|
603
|
+
* @returns {Promise<object|null>} - The parsed data for the key or null if not found.
|
|
604
|
+
*/
|
|
605
|
+
async getGlobal(tableName, key) {
|
|
313
606
|
return new Promise((resolve) => {
|
|
314
|
-
this.gun.get(tableName).get(key).once((data) => {
|
|
607
|
+
this.gun.get(this.appname).get(tableName).get(key).once((data) => {
|
|
315
608
|
if (!data) {
|
|
316
609
|
resolve(null);
|
|
317
610
|
return;
|
|
318
611
|
}
|
|
319
612
|
try {
|
|
320
|
-
const parsed =
|
|
613
|
+
const parsed = this.parse(data);
|
|
321
614
|
resolve(parsed);
|
|
322
615
|
} catch (e) {
|
|
323
616
|
resolve(null);
|
|
@@ -326,43 +619,124 @@ class HoloSphere {
|
|
|
326
619
|
});
|
|
327
620
|
}
|
|
328
621
|
|
|
622
|
+
|
|
623
|
+
|
|
329
624
|
/**
|
|
330
625
|
* Retrieves all data from a global table.
|
|
331
626
|
* @param {string} tableName - The table name to retrieve data from.
|
|
332
627
|
* @returns {Promise<object|null>} - The parsed data from the table or null if not found.
|
|
333
628
|
*/
|
|
334
|
-
async getAllGlobal(tableName) {
|
|
629
|
+
// async getAllGlobal(tableName) {
|
|
630
|
+
// return new Promise(async (resolve, reject) => {
|
|
631
|
+
// let output = []
|
|
632
|
+
// let counter = 0
|
|
633
|
+
// this.gun.get(this.appname).get(tableName.toString()).once((data, key) => {
|
|
634
|
+
// if (data) {
|
|
635
|
+
// const maplenght = Object.keys(data).length - 1
|
|
636
|
+
// this.gun.get(this.appname).get(tableName.toString()).map().once(async (itemdata, key) => {
|
|
637
|
+
|
|
638
|
+
// counter += 1
|
|
639
|
+
// if (itemdata) {
|
|
640
|
+
// let parsed = await this.parse(itemdata)
|
|
641
|
+
// output.push(parsed);
|
|
642
|
+
// console.log('getAllGlobal: parsed: ', parsed)
|
|
643
|
+
// }
|
|
644
|
+
|
|
645
|
+
// if (counter == maplenght) {
|
|
646
|
+
// resolve(output);
|
|
647
|
+
|
|
648
|
+
// }
|
|
649
|
+
// }
|
|
650
|
+
// );
|
|
651
|
+
// } else resolve(output)
|
|
652
|
+
// })
|
|
653
|
+
// }
|
|
654
|
+
// )
|
|
655
|
+
// }
|
|
656
|
+
async getAllGlobal(lens) {
|
|
657
|
+
if ( !lens) {
|
|
658
|
+
console.error('getAll: Missing required parameters:', { lens });
|
|
659
|
+
return [];
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const schema = await this.getSchema(lens);
|
|
663
|
+
if (!schema && this.strict) {
|
|
664
|
+
console.error('getAll: Schema required in strict mode for lens:', lens);
|
|
665
|
+
return [];
|
|
666
|
+
}
|
|
667
|
+
|
|
335
668
|
return new Promise((resolve) => {
|
|
336
|
-
|
|
669
|
+
let output = [];
|
|
670
|
+
let counter = 0;
|
|
671
|
+
|
|
672
|
+
this.gun.get(this.appname).get(lens).once((data, key) => {
|
|
337
673
|
if (!data) {
|
|
338
|
-
resolve(
|
|
674
|
+
resolve(output);
|
|
339
675
|
return;
|
|
340
676
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
677
|
+
|
|
678
|
+
const mapLength = Object.keys(data).length - 1;
|
|
679
|
+
|
|
680
|
+
this.gun.get(this.appname).get(lens).map().once(async (itemdata, key) => {
|
|
681
|
+
counter += 1;
|
|
682
|
+
if (itemdata) {
|
|
683
|
+
try {
|
|
684
|
+
const parsed = await this.parse(itemdata);
|
|
685
|
+
if (schema) {
|
|
686
|
+
const valid = this.validator.validate(schema, parsed);
|
|
687
|
+
if (valid) {
|
|
688
|
+
output.push(parsed);
|
|
689
|
+
} else if (this.strict) {
|
|
690
|
+
console.warn('Invalid data removed:', key, this.validator.errors);
|
|
691
|
+
await this.delete(holon, lens, key);
|
|
692
|
+
} else {
|
|
693
|
+
console.warn('Invalid data found:', key, this.validator.errors);
|
|
694
|
+
output.push(parsed);
|
|
695
|
+
}
|
|
696
|
+
} else {
|
|
697
|
+
output.push(parsed);
|
|
698
|
+
}
|
|
699
|
+
} catch (error) {
|
|
700
|
+
console.error('Error parsing data:', error);
|
|
701
|
+
if (this.strict) {
|
|
702
|
+
await this.delete(holon, lens, key);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (counter === mapLength) {
|
|
708
|
+
resolve(output);
|
|
709
|
+
}
|
|
710
|
+
});
|
|
347
711
|
});
|
|
348
712
|
});
|
|
349
713
|
}
|
|
350
|
-
|
|
351
|
-
|
|
714
|
+
/**
|
|
715
|
+
* Deletes a specific key from a global table.
|
|
716
|
+
* @param {string} tableName - The table name to delete from.
|
|
717
|
+
* @param {string} key - The key to delete.
|
|
718
|
+
* @returns {Promise<void>}
|
|
719
|
+
*/
|
|
720
|
+
async deleteGlobal(tableName, key) {
|
|
721
|
+
await this.gun.get(this.appname).get(tableName).get(key).put(null)
|
|
722
|
+
}
|
|
352
723
|
|
|
353
724
|
/**
|
|
354
725
|
* Deletes an entire global table.
|
|
355
|
-
* @param {string}
|
|
726
|
+
* @param {string} tableName - The table name to delete.
|
|
356
727
|
* @returns {Promise<void>}
|
|
357
728
|
*/
|
|
358
729
|
async deleteAllGlobal(tableName) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
730
|
+
// return new Promise((resolve) => {
|
|
731
|
+
this.gun.get(this.appname).get(tableName).map().once( (data, key)=> {
|
|
732
|
+
this.gun.get(this.appname).get(tableName).get(key).put(null)
|
|
733
|
+
})
|
|
734
|
+
this.gun.get(this.appname).get(tableName).put(null, ack => {
|
|
735
|
+
console.log('deleteAllGlobal: ack: ', ack)
|
|
736
|
+
})
|
|
737
|
+
// resolve();
|
|
738
|
+
//});
|
|
739
|
+
// });
|
|
366
740
|
}
|
|
367
741
|
|
|
368
742
|
// ================================ COMPUTE FUNCTIONS ================================
|
|
@@ -391,7 +765,7 @@ class HoloSphere {
|
|
|
391
765
|
resolve(); // Resolve the promise to prevent it from hanging
|
|
392
766
|
}, 1000); // Timeout of 5 seconds
|
|
393
767
|
|
|
394
|
-
this.gun.get(siblings[i]).get(lens).map().once((data, key) => {
|
|
768
|
+
this.gun.get(this.appname).get(siblings[i]).get(lens).map().once((data, key) => {
|
|
395
769
|
clearTimeout(timeout); // Clear the timeout if data is received
|
|
396
770
|
if (data) {
|
|
397
771
|
content.push(data.content);
|
|
@@ -405,7 +779,7 @@ class HoloSphere {
|
|
|
405
779
|
console.log('Content:', content);
|
|
406
780
|
let computed = await this.summarize(content.join('\n'))
|
|
407
781
|
console.log('Computed:', computed)
|
|
408
|
-
let node = await this.gun.get(parent + '_summary').put({ id: parent + '_summary', content: computed })
|
|
782
|
+
let node = await this.gun.get(this.appname).get(parent + '_summary').put({ id: parent + '_summary', content: computed })
|
|
409
783
|
|
|
410
784
|
this.put(parent, lens, node);
|
|
411
785
|
this.compute(parent, lens, operation)
|
|
@@ -420,10 +794,10 @@ class HoloSphere {
|
|
|
420
794
|
let entities = {};
|
|
421
795
|
|
|
422
796
|
// Get list out of Gun
|
|
423
|
-
this.gun.get(holon).get(lens).map().once((data, key) => {
|
|
797
|
+
this.gun.get(this.appname).get(holon).get(lens).map().once((data, key) => {
|
|
424
798
|
//entities = data;
|
|
425
799
|
//const id = Object.keys(entities)[0] // since this would be in object form, you can manipulate it as you would like.
|
|
426
|
-
this.gun.get(holon).get(lens).put({ [key]: null })
|
|
800
|
+
this.gun.get(this.appname).get(holon).get(lens).put({ [key]: null })
|
|
427
801
|
})
|
|
428
802
|
}
|
|
429
803
|
|
|
@@ -555,92 +929,12 @@ class HoloSphere {
|
|
|
555
929
|
* @param {string} lens - The lens to subscribe to.
|
|
556
930
|
* @param {function} callback - The callback to execute on changes.
|
|
557
931
|
*/
|
|
558
|
-
subscribe(holon, lens, callback) {
|
|
559
|
-
this.gun.get(holon).get(lens).map().on((data, key) => {
|
|
560
|
-
|
|
932
|
+
async subscribe(holon, lens, callback) {
|
|
933
|
+
this.gun.get(this.appname).get(holon).get(lens).map().on(async (data, key) => {
|
|
934
|
+
if (data)
|
|
935
|
+
callback( await this.parse(data), key)
|
|
561
936
|
})
|
|
562
937
|
}
|
|
563
|
-
|
|
564
|
-
// ================================ GOVERNANCE FUNCTIONS ================================
|
|
565
|
-
/**
|
|
566
|
-
* Retrieves the final vote for a user, considering delegations.
|
|
567
|
-
* @param {string} userId - The user's identifier.
|
|
568
|
-
* @param {string} topic - The voting topic.
|
|
569
|
-
* @param {object} votes - The current votes.
|
|
570
|
-
* @param {Set<string>} [visited=new Set()] - Set of visited users to prevent cycles.
|
|
571
|
-
* @returns {string|null} - The final vote or null if not found.
|
|
572
|
-
*/
|
|
573
|
-
getFinalVote(userId, topic, votes, visited = new Set()) {
|
|
574
|
-
if (this.users[userId]) { // Added this.users
|
|
575
|
-
if (visited.has(userId)) {
|
|
576
|
-
return null; // Avoid circular delegations
|
|
577
|
-
}
|
|
578
|
-
visited.add(userId);
|
|
579
|
-
|
|
580
|
-
const delegation = this.users[userId].delegations[topic];
|
|
581
|
-
if (delegation && votes[delegation] === undefined) {
|
|
582
|
-
return this.getFinalVote(delegation, topic, votes, visited); // Prefixed with this
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
return votes[userId] !== undefined ? votes[userId] : null;
|
|
586
|
-
}
|
|
587
|
-
return null;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
* Aggregates votes for a specific holon and topic.
|
|
592
|
-
* @param {string} holonId - The holon identifier.
|
|
593
|
-
* @param {string} topic - The voting topic.
|
|
594
|
-
* @returns {object} - Aggregated vote counts.
|
|
595
|
-
*/
|
|
596
|
-
aggregateVotes(holonId, topic) {
|
|
597
|
-
if (!this.holonagonVotes[holonId] || !this.holonagonVotes[holonId][topic]) {
|
|
598
|
-
return {}; // Handle undefined votes
|
|
599
|
-
}
|
|
600
|
-
const votes = this.holonagonVotes[holonId][topic];
|
|
601
|
-
const aggregatedVotes = {};
|
|
602
|
-
|
|
603
|
-
Object.keys(votes).forEach(userId => {
|
|
604
|
-
const finalVote = this.getFinalVote(userId, topic, votes); // Prefixed with this
|
|
605
|
-
if (finalVote !== null) {
|
|
606
|
-
aggregatedVotes[finalVote] = (aggregatedVotes[finalVote] || 0) + 1;
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
return aggregatedVotes;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Delegates a user's vote to another user.
|
|
615
|
-
* @param {string} userId - The user's identifier.
|
|
616
|
-
* @param {string} topic - The voting topic.
|
|
617
|
-
* @param {string} delegateTo - The user to delegate the vote to.
|
|
618
|
-
*/
|
|
619
|
-
async delegateVote(userId, topic, delegateTo) {
|
|
620
|
-
const response = await fetch('/delegate', {
|
|
621
|
-
method: 'POST',
|
|
622
|
-
headers: { 'Content-Type': 'application/json' },
|
|
623
|
-
body: JSON.stringify({ userId, topic, delegateTo })
|
|
624
|
-
});
|
|
625
|
-
alert(await response.text());
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/**
|
|
629
|
-
* Casts a vote for a user on a specific topic and holon.
|
|
630
|
-
* @param {string} userId - The user's identifier.
|
|
631
|
-
* @param {string} holonId - The holon identifier.
|
|
632
|
-
* @param {string} topic - The voting topic.
|
|
633
|
-
* @param {string} vote - The vote choice.
|
|
634
|
-
*/
|
|
635
|
-
async vote(userId, holonId, topic, vote) {
|
|
636
|
-
const response = await fetch('/vote', {
|
|
637
|
-
method: 'POST',
|
|
638
|
-
headers: { 'Content-Type': 'application/json' },
|
|
639
|
-
body: JSON.stringify({ userId, holonId, topic, vote })
|
|
640
|
-
});
|
|
641
|
-
alert(await response.text());
|
|
642
|
-
}
|
|
643
|
-
|
|
644
938
|
}
|
|
645
939
|
|
|
646
940
|
export default HoloSphere;
|