holosphere 1.0.8 → 1.1.0
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 +23 -24
- package/holosphere.js +483 -317
- package/package.json +1 -1
- package/test/holosphere.test.js +224 -109
package/holosphere.js
CHANGED
|
@@ -7,22 +7,25 @@ class HoloSphere {
|
|
|
7
7
|
/**
|
|
8
8
|
* Initializes a new instance of the HoloSphere class.
|
|
9
9
|
* @param {string} appname - The name of the application.
|
|
10
|
+
* @param {boolean} strict - Whether to enforce strict schema validation.
|
|
10
11
|
* @param {string|null} openaikey - The OpenAI API key.
|
|
11
12
|
*/
|
|
12
|
-
constructor(appname, openaikey = null) {
|
|
13
|
-
this.
|
|
13
|
+
constructor(appname, strict = false, openaikey = null) {
|
|
14
|
+
this.appname = appname
|
|
15
|
+
this.strict = strict;
|
|
16
|
+
this.validator = new Ajv2019({
|
|
17
|
+
allErrors: true,
|
|
18
|
+
strict: false, // Keep this false to avoid Ajv strict mode issues
|
|
19
|
+
validateSchema: true // Always validate schemas
|
|
20
|
+
});
|
|
14
21
|
this.gun = Gun({
|
|
15
|
-
peers: ['http://gun.holons.io','https://59.src.eco/gun'],
|
|
22
|
+
peers: ['http://gun.holons.io', 'https://59.src.eco/gun'],
|
|
16
23
|
axe: false,
|
|
17
24
|
// uuid: (content) => { // generate a unique id for each node
|
|
18
25
|
// console.log('uuid', content);
|
|
19
26
|
// return content;}
|
|
20
27
|
});
|
|
21
28
|
|
|
22
|
-
this.gun = this.gun.get(appname)
|
|
23
|
-
this.users = {}; // Initialize users
|
|
24
|
-
this.holonagonVotes = {}; // Initialize holonagonVotes
|
|
25
|
-
|
|
26
29
|
if (openaikey != null) {
|
|
27
30
|
this.openai = new OpenAI({
|
|
28
31
|
apiKey: openaikey,
|
|
@@ -30,6 +33,8 @@ class HoloSphere {
|
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
|
|
36
|
+
// ================================ SCHEMA FUNCTIONS ================================
|
|
37
|
+
|
|
33
38
|
/**
|
|
34
39
|
* Sets the JSON schema for a specific lens.
|
|
35
40
|
* @param {string} lens - The lens identifier.
|
|
@@ -37,16 +42,84 @@ class HoloSphere {
|
|
|
37
42
|
* @returns {Promise} - Resolves when the schema is set.
|
|
38
43
|
*/
|
|
39
44
|
async setSchema(lens, schema) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
if (!lens || !schema) {
|
|
46
|
+
console.error('setSchema: Missing required parameters');
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Basic schema validation - check for required fields
|
|
51
|
+
if (!schema.type || typeof schema.type !== 'string') {
|
|
52
|
+
console.error('setSchema: Schema must have a type field');
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (this.strict) {
|
|
57
|
+
try {
|
|
58
|
+
// Validate schema against JSON Schema meta-schema
|
|
59
|
+
const metaSchema = {
|
|
60
|
+
type: 'object',
|
|
61
|
+
required: ['type', 'properties'],
|
|
62
|
+
properties: {
|
|
63
|
+
type: { type: 'string' },
|
|
64
|
+
properties: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
additionalProperties: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
required: ['type'],
|
|
69
|
+
properties: {
|
|
70
|
+
type: { type: 'string' }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
required: {
|
|
75
|
+
type: 'array',
|
|
76
|
+
items: { type: 'string' }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const valid = this.validator.validate(metaSchema, schema);
|
|
82
|
+
if (!valid) {
|
|
83
|
+
console.error('setSchema: Invalid schema structure:', this.validator.errors);
|
|
84
|
+
return false;
|
|
47
85
|
}
|
|
48
|
-
|
|
49
|
-
|
|
86
|
+
|
|
87
|
+
// Additional strict mode checks
|
|
88
|
+
if (!schema.properties || typeof schema.properties !== 'object') {
|
|
89
|
+
console.error('setSchema: Schema must have properties in strict mode');
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!schema.required || !Array.isArray(schema.required) || schema.required.length === 0) {
|
|
94
|
+
console.error('setSchema: Schema must have required fields in strict mode');
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('setSchema: Schema validation error:', error);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
try {
|
|
105
|
+
const schemaString = JSON.stringify(schema);
|
|
106
|
+
this.gun.get(this.appname)
|
|
107
|
+
.get(lens)
|
|
108
|
+
.get('schema')
|
|
109
|
+
.put(schemaString, ack => {
|
|
110
|
+
if (ack.err) {
|
|
111
|
+
console.error('Failed to add schema:', ack.err);
|
|
112
|
+
resolve(false);
|
|
113
|
+
} else {
|
|
114
|
+
console.log('Schema added successfully for lens:', lens);
|
|
115
|
+
resolve(true);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error('setSchema: Error stringifying schema:', error);
|
|
120
|
+
resolve(false);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
50
123
|
}
|
|
51
124
|
|
|
52
125
|
/**
|
|
@@ -55,114 +128,106 @@ class HoloSphere {
|
|
|
55
128
|
* @returns {Promise<object|null>} - The retrieved schema or null if not found.
|
|
56
129
|
*/
|
|
57
130
|
async getSchema(lens) {
|
|
131
|
+
if (!lens) {
|
|
132
|
+
console.error('getSchema: Missing lens parameter');
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
58
136
|
return new Promise((resolve) => {
|
|
59
|
-
this.gun.get(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
137
|
+
this.gun.get(this.appname)
|
|
138
|
+
.get(lens)
|
|
139
|
+
.get('schema')
|
|
140
|
+
.once(data => {
|
|
141
|
+
if (!data) {
|
|
142
|
+
resolve(null);
|
|
143
|
+
return;
|
|
64
144
|
}
|
|
65
|
-
|
|
66
|
-
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
// If data is already a string, parse it
|
|
148
|
+
if (typeof data === 'string') {
|
|
149
|
+
resolve(JSON.parse(data));
|
|
150
|
+
}
|
|
151
|
+
// If data is an object with a string value (GunDB format)
|
|
152
|
+
else if (typeof data === 'object' && data !== null) {
|
|
153
|
+
const schemaStr = Object.values(data).find(v =>
|
|
154
|
+
typeof v === 'string' && v.includes('"type":'));
|
|
155
|
+
if (schemaStr) {
|
|
156
|
+
resolve(JSON.parse(schemaStr));
|
|
157
|
+
} else {
|
|
158
|
+
resolve(null);
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
resolve(null);
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error('getSchema: Error parsing schema:', error);
|
|
165
|
+
resolve(null);
|
|
67
166
|
}
|
|
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)
|
|
167
|
+
});
|
|
168
|
+
});
|
|
82
169
|
}
|
|
83
170
|
|
|
171
|
+
// ================================ CONTENT FUNCTIONS ================================
|
|
172
|
+
|
|
84
173
|
/**
|
|
85
174
|
* Stores content in the specified holon and lens.
|
|
86
175
|
* @param {string} holon - The holon identifier.
|
|
87
176
|
* @param {string} lens - The lens under which to store the content.
|
|
88
|
-
* @param {object}
|
|
177
|
+
* @param {object} data - The data to store.
|
|
178
|
+
* @returns {Promise<boolean>} - Returns true if successful, false if there was an error
|
|
89
179
|
*/
|
|
90
|
-
async put(holon, lens,
|
|
91
|
-
if (!holon || !lens || !
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
let schema = await this.getSchema(lens)
|
|
95
|
-
if (schema) {
|
|
96
|
-
// Validate the content against the schema
|
|
97
|
-
const valid = this.validator.validate(schema, content);
|
|
98
|
-
if (!valid) {
|
|
99
|
-
console.error('Not committing invalid content:', this.validator.errors);
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
180
|
+
async put(holon, lens, data) {
|
|
181
|
+
if (!holon || !lens || !data) {
|
|
182
|
+
console.error('put: Missing required parameters:', { holon, lens, data });
|
|
183
|
+
return false;
|
|
102
184
|
}
|
|
103
185
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
let noderef;
|
|
108
|
-
|
|
109
|
-
if (content.id) { //use the user-defined id. Important to be able to send updates using put
|
|
110
|
-
noderef = this.gun.get(lens).get(content.id).put(payload)
|
|
111
|
-
this.gun.get(holon.toString()).get(lens).get(content.id).put(payload)
|
|
112
|
-
} else { // create a content-addressable reference like IPFS. Note: no updates possible using put
|
|
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)
|
|
186
|
+
if (!data.id) {
|
|
187
|
+
console.error('put: Data must have an id field');
|
|
188
|
+
return false;
|
|
118
189
|
}
|
|
119
190
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
this.gun.get(holon).get(lens).set(node)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async parse(data) {
|
|
127
|
-
let parsed = {};
|
|
128
|
-
|
|
129
|
-
if (typeof data === 'object' && data !== null) {
|
|
130
|
-
if (data._ && data._["#"]) {
|
|
131
|
-
// If the data is a reference, fetch the actual content
|
|
132
|
-
let query = data._['#'].split('/');
|
|
133
|
-
let holon = query[1];
|
|
134
|
-
let lens = query[2];
|
|
135
|
-
let key = query[3];
|
|
136
|
-
parsed = await this.getKey(holon, lens, key);
|
|
137
|
-
} else if (data._ && data._['>']) {
|
|
138
|
-
// This might be a gun node, try to get its value
|
|
139
|
-
const nodeValue = Object.values(data).find(v => typeof v !== 'object' && v !== '_');
|
|
140
|
-
if (nodeValue) {
|
|
141
|
-
try {
|
|
142
|
-
parsed = JSON.parse(nodeValue);
|
|
143
|
-
} catch (e) {
|
|
144
|
-
console.log('Invalid JSON in node value:', nodeValue);
|
|
145
|
-
parsed = nodeValue; // return the raw data
|
|
146
|
-
}
|
|
147
|
-
} else {
|
|
148
|
-
console.log('Unable to parse gun node:', data);
|
|
149
|
-
parsed = data; // return the original data
|
|
150
|
-
}
|
|
151
|
-
} else {
|
|
152
|
-
// Treat it as regular data
|
|
153
|
-
parsed = data;
|
|
154
|
-
}
|
|
155
|
-
} else {
|
|
156
|
-
// If it's not an object, try parsing it as JSON
|
|
191
|
+
// Strict validation of schema and data
|
|
192
|
+
const schema = await this.getSchema(lens);
|
|
193
|
+
if (schema) {
|
|
157
194
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
195
|
+
const valid = this.validator.validate(schema, data);
|
|
196
|
+
if (!valid) {
|
|
197
|
+
const errors = this.validator.errors;
|
|
198
|
+
console.error('put: Schema validation failed:', errors);
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error('put: Schema validation error:', error);
|
|
203
|
+
return false;
|
|
162
204
|
}
|
|
205
|
+
} else if (this.strict) {
|
|
206
|
+
console.error('put: Schema required in strict mode for lens:', lens);
|
|
207
|
+
return false;
|
|
163
208
|
}
|
|
164
209
|
|
|
165
|
-
return
|
|
210
|
+
return new Promise((resolve) => {
|
|
211
|
+
try {
|
|
212
|
+
const payload = JSON.stringify(data);
|
|
213
|
+
|
|
214
|
+
this.gun.get(this.appname)
|
|
215
|
+
.get(holon)
|
|
216
|
+
.get(lens)
|
|
217
|
+
.get(data.id)
|
|
218
|
+
.put(payload, ack => {
|
|
219
|
+
if (ack.err) {
|
|
220
|
+
console.error("Error adding data to GunDB:", ack.err);
|
|
221
|
+
resolve(false);
|
|
222
|
+
} else {
|
|
223
|
+
resolve(true);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.error('Error in put operation:', error);
|
|
228
|
+
resolve(false);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
166
231
|
}
|
|
167
232
|
|
|
168
233
|
/**
|
|
@@ -171,160 +236,324 @@ class HoloSphere {
|
|
|
171
236
|
* @param {string} lens - The lens from which to retrieve content.
|
|
172
237
|
* @returns {Promise<Array<object>>} - The retrieved content.
|
|
173
238
|
*/
|
|
174
|
-
async
|
|
239
|
+
async getAll(holon, lens) {
|
|
175
240
|
if (!holon || !lens) {
|
|
176
|
-
console.
|
|
177
|
-
return;
|
|
241
|
+
console.error('getAll: Missing required parameters:', { holon, lens });
|
|
242
|
+
return [];
|
|
178
243
|
}
|
|
179
|
-
// Wrap the gun operation in a promise
|
|
180
|
-
//retrieve lens schema
|
|
181
|
-
const schema = await this.getSchema(lens);
|
|
182
244
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
245
|
+
const schema = await this.getSchema(lens);
|
|
246
|
+
if (!schema && this.strict) {
|
|
247
|
+
console.error('getAll: Schema required in strict mode for lens:', lens);
|
|
248
|
+
return [];
|
|
186
249
|
}
|
|
187
250
|
|
|
188
|
-
return new Promise(
|
|
189
|
-
let output = []
|
|
190
|
-
let counter = 0
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (itemdata) {
|
|
198
|
-
let parsed = await this.parse (itemdata)
|
|
199
|
-
|
|
251
|
+
return new Promise((resolve) => {
|
|
252
|
+
let output = [];
|
|
253
|
+
let counter = 0;
|
|
254
|
+
|
|
255
|
+
this.gun.get(this.appname).get(holon).get(lens).once((data, key) => {
|
|
256
|
+
if (!data) {
|
|
257
|
+
resolve(output);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
200
260
|
|
|
261
|
+
const mapLength = Object.keys(data).length - 1;
|
|
262
|
+
|
|
263
|
+
this.gun.get(this.appname).get(holon).get(lens).map().once(async (itemdata, key) => {
|
|
264
|
+
counter += 1;
|
|
265
|
+
if (itemdata) {
|
|
266
|
+
try {
|
|
267
|
+
const parsed = JSON.parse(itemdata);
|
|
268
|
+
|
|
201
269
|
if (schema) {
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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);
|
|
207
276
|
} else {
|
|
277
|
+
console.warn('Invalid data found:', key, this.validator.errors);
|
|
208
278
|
output.push(parsed);
|
|
209
279
|
}
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
280
|
+
} else {
|
|
212
281
|
output.push(parsed);
|
|
213
282
|
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.error('Error parsing data:', error);
|
|
285
|
+
if (this.strict) {
|
|
286
|
+
await this.delete(holon, lens, key);
|
|
287
|
+
}
|
|
214
288
|
}
|
|
289
|
+
}
|
|
215
290
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
291
|
+
if (counter === mapLength) {
|
|
292
|
+
resolve(output);
|
|
219
293
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|
|
225
297
|
}
|
|
226
298
|
|
|
227
|
-
|
|
299
|
+
/**
|
|
228
300
|
* Retrieves a specific key from the specified holon and lens.
|
|
229
301
|
* @param {string} holon - The holon identifier.
|
|
230
302
|
* @param {string} lens - The lens from which to retrieve the key.
|
|
231
303
|
* @param {string} key - The specific key to retrieve.
|
|
232
304
|
* @returns {Promise<object|null>} - The retrieved content or null if not found.
|
|
233
305
|
*/
|
|
234
|
-
|
|
306
|
+
async get(holon, lens, key) {
|
|
307
|
+
if (!holon || !lens || !key) {
|
|
308
|
+
console.error('get: Missing required parameters:', { holon, lens, key });
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Get schema for validation
|
|
313
|
+
const schema = await this.getSchema(lens);
|
|
314
|
+
|
|
235
315
|
return new Promise((resolve) => {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
316
|
+
let timeout = setTimeout(() => {
|
|
317
|
+
console.warn('get: Operation timed out');
|
|
318
|
+
resolve(null);
|
|
319
|
+
}, 5000); // 5 second timeout
|
|
320
|
+
|
|
321
|
+
this.gun.get(this.appname)
|
|
322
|
+
.get(holon)
|
|
323
|
+
.get(lens)
|
|
324
|
+
.get(key)
|
|
325
|
+
.once((data) => {
|
|
326
|
+
clearTimeout(timeout);
|
|
327
|
+
|
|
328
|
+
if (!data) {
|
|
329
|
+
resolve(null);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
240
333
|
try {
|
|
241
|
-
|
|
334
|
+
const parsed = JSON.parse(data);
|
|
335
|
+
|
|
336
|
+
// Validate against schema if one exists
|
|
337
|
+
if (schema) {
|
|
338
|
+
const valid = this.validator.validate(schema, parsed);
|
|
339
|
+
if (!valid) {
|
|
340
|
+
console.error('get: Invalid data according to schema:', this.validator.errors);
|
|
341
|
+
if (this.strict) {
|
|
342
|
+
resolve(null);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
242
348
|
resolve(parsed);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
console.error('Error parsing data:', error);
|
|
351
|
+
resolve(null);
|
|
243
352
|
}
|
|
244
|
-
|
|
245
|
-
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Deletes a specific key from a given holon and lens.
|
|
359
|
+
* @param {string} holon - The holon identifier.
|
|
360
|
+
* @param {string} lens - The lens from which to delete the key.
|
|
361
|
+
* @param {string} key - The specific key to delete.
|
|
362
|
+
*/
|
|
363
|
+
async delete (holon, lens, key) {
|
|
364
|
+
return new Promise((resolve, reject) => {
|
|
365
|
+
this.gun.get(this.appname).get(holon).get(lens).get(key).put(null, ack => {
|
|
366
|
+
if (ack.err) {
|
|
367
|
+
resolve(ack.err);
|
|
368
|
+
} else {
|
|
369
|
+
resolve(ack.ok);
|
|
246
370
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Deletes all keys from a given holon and lens.
|
|
377
|
+
* @param {string} holon - The holon identifier.
|
|
378
|
+
* @param {string} lens - The lens from which to delete all keys.
|
|
379
|
+
* @returns {Promise<boolean>} - Returns true if successful, false if there was an error
|
|
380
|
+
*/
|
|
381
|
+
async deleteAll(holon, lens) {
|
|
382
|
+
if (!holon || !lens) {
|
|
383
|
+
console.error('deleteAll: Missing holon or lens parameter');
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return new Promise((resolve) => {
|
|
388
|
+
let deletionPromises = [];
|
|
389
|
+
|
|
390
|
+
// First get all the data to find keys to delete
|
|
391
|
+
this.gun.get(this.appname).get(holon).get(lens).once((data) => {
|
|
392
|
+
if (!data) {
|
|
393
|
+
resolve(true); // Nothing to delete
|
|
394
|
+
return;
|
|
250
395
|
}
|
|
396
|
+
|
|
397
|
+
// Get all keys except Gun's metadata key '_'
|
|
398
|
+
const keys = Object.keys(data).filter(key => key !== '_');
|
|
399
|
+
|
|
400
|
+
// Create deletion promises for each key
|
|
401
|
+
keys.forEach(key => {
|
|
402
|
+
deletionPromises.push(
|
|
403
|
+
new Promise((resolveDelete) => {
|
|
404
|
+
this.gun.get(this.appname).get(holon).get(lens).get(key).put(null, ack => {
|
|
405
|
+
resolveDelete(!!ack.ok); // Convert to boolean
|
|
406
|
+
});
|
|
407
|
+
})
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Wait for all deletions to complete
|
|
412
|
+
Promise.all(deletionPromises)
|
|
413
|
+
.then(results => {
|
|
414
|
+
const allSuccessful = results.every(result => result === true);
|
|
415
|
+
resolve(allSuccessful);
|
|
416
|
+
})
|
|
417
|
+
.catch(error => {
|
|
418
|
+
console.error('Error in deleteAll:', error);
|
|
419
|
+
resolve(false);
|
|
420
|
+
});
|
|
251
421
|
});
|
|
252
422
|
});
|
|
253
|
-
|
|
254
423
|
}
|
|
255
424
|
|
|
425
|
+
// ================================ NODE FUNCTIONS ================================
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Stores a specific gun node in a given holon and lens.
|
|
430
|
+
* @param {string} holon - The holon identifier.
|
|
431
|
+
* @param {string} lens - The lens under which to store the node.
|
|
432
|
+
* @param {object} node - The node to store.
|
|
433
|
+
*/
|
|
434
|
+
async putNode(holon, lens, node) {
|
|
435
|
+
this.gun.get(this.appname).get(holon).get(lens).put(node)
|
|
436
|
+
}
|
|
437
|
+
|
|
256
438
|
/**
|
|
257
439
|
* Retrieves a specific gun node from the specified holon and lens.
|
|
258
440
|
* @param {string} holon - The holon identifier.
|
|
259
|
-
* @param {string} lens - The lens
|
|
441
|
+
* @param {string} lens - The lens identifier.
|
|
260
442
|
* @param {string} key - The specific key to retrieve.
|
|
261
|
-
* @returns {Promise<object|null>} - The retrieved
|
|
443
|
+
* @returns {Promise<object|null>} - The retrieved node or null if not found.
|
|
262
444
|
*/
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
445
|
+
async getNode(holon, lens, key) {
|
|
446
|
+
if (!holon || !lens || !key) {
|
|
447
|
+
console.error('getNode: Missing required parameters');
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
267
450
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
451
|
+
return new Promise((resolve) => {
|
|
452
|
+
let timeout = setTimeout(() => {
|
|
453
|
+
console.warn('getNode: Operation timed out');
|
|
454
|
+
resolve(null);
|
|
455
|
+
}, 5000);
|
|
456
|
+
|
|
457
|
+
this.gun.get(this.appname)
|
|
458
|
+
.get(holon)
|
|
459
|
+
.get(lens)
|
|
460
|
+
.get(key)
|
|
461
|
+
.once((data) => {
|
|
462
|
+
clearTimeout(timeout);
|
|
463
|
+
resolve(data || null);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
271
466
|
}
|
|
272
|
-
|
|
467
|
+
|
|
273
468
|
/**
|
|
274
|
-
*
|
|
275
|
-
* @param {string}
|
|
276
|
-
* @param {
|
|
277
|
-
* @
|
|
469
|
+
* Deletes a specific gun node from a given holon and lens.
|
|
470
|
+
* @param {string} holon - The holon identifier.
|
|
471
|
+
* @param {string} lens - The lens identifier.
|
|
472
|
+
* @param {string} key - The key of the node to delete.
|
|
473
|
+
* @returns {Promise<boolean>} - Returns true if successful
|
|
278
474
|
*/
|
|
279
|
-
async
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
475
|
+
async deleteNode(holon, lens, key) {
|
|
476
|
+
if (!holon || !lens || !key) {
|
|
477
|
+
console.error('deleteNode: Missing required parameters');
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
285
480
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
} else {
|
|
295
|
-
this.gun.get(tableName).put(JSON.stringify(data), ack => {
|
|
481
|
+
return new Promise((resolve) => {
|
|
482
|
+
this.gun.get(this.appname)
|
|
483
|
+
.get(holon)
|
|
484
|
+
.get(lens)
|
|
485
|
+
.get(key)
|
|
486
|
+
.put(null, ack => {
|
|
296
487
|
if (ack.err) {
|
|
297
|
-
|
|
488
|
+
console.error('deleteNode: Error deleting node:', ack.err);
|
|
489
|
+
resolve(false);
|
|
298
490
|
} else {
|
|
299
|
-
resolve();
|
|
491
|
+
resolve(true);
|
|
300
492
|
}
|
|
301
493
|
});
|
|
302
|
-
}
|
|
303
494
|
});
|
|
304
495
|
}
|
|
305
496
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
*
|
|
309
|
-
* @param {string}
|
|
310
|
-
* @
|
|
497
|
+
// ================================ GLOBAL FUNCTIONS ================================
|
|
498
|
+
/**
|
|
499
|
+
* Stores data in a global (non-holon-specific) table.
|
|
500
|
+
* @param {string} tableName - The table name to store data in.
|
|
501
|
+
* @param {object} data - The data to store. If it has an 'id' field, it will be used as the key.
|
|
502
|
+
* @returns {Promise<void>}
|
|
311
503
|
*/
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (!data) {
|
|
316
|
-
|
|
504
|
+
async putGlobal(tableName, data) {
|
|
505
|
+
|
|
506
|
+
return new Promise((resolve, reject) => {
|
|
507
|
+
if (!tableName || !data) {
|
|
508
|
+
reject(new Error('Table name and data are required'));
|
|
317
509
|
return;
|
|
318
510
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
if (data.id) {
|
|
514
|
+
this.gun.get(this.appname).get(tableName).get(data.id).put(JSON.stringify(data), ack => {
|
|
515
|
+
if (ack.err) {
|
|
516
|
+
reject(new Error(ack.err));
|
|
517
|
+
} else {
|
|
518
|
+
resolve();
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
} else {
|
|
522
|
+
this.gun.get(this.appname).get(tableName).put(JSON.stringify(data), ack => {
|
|
523
|
+
if (ack.err) {
|
|
524
|
+
reject(new Error(ack.err));
|
|
525
|
+
} else {
|
|
526
|
+
resolve();
|
|
527
|
+
}
|
|
528
|
+
});
|
|
324
529
|
}
|
|
325
530
|
});
|
|
326
|
-
}
|
|
327
|
-
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Retrieves a specific key from a global table.
|
|
535
|
+
* @param {string} tableName - The table name to retrieve from.
|
|
536
|
+
* @param {string} key - The key to retrieve.
|
|
537
|
+
* @returns {Promise<object|null>} - The parsed data for the key or null if not found.
|
|
538
|
+
*/
|
|
539
|
+
async getGlobal(tableName, key) {
|
|
540
|
+
return new Promise((resolve) => {
|
|
541
|
+
this.gun.get(this.appname).get(tableName).get(key).once((data) => {
|
|
542
|
+
if (!data) {
|
|
543
|
+
resolve(null);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
try {
|
|
547
|
+
const parsed = this.parse(data);
|
|
548
|
+
resolve(parsed);
|
|
549
|
+
} catch (e) {
|
|
550
|
+
resolve(null);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
|
|
328
557
|
|
|
329
558
|
/**
|
|
330
559
|
* Retrieves all data from a global table.
|
|
@@ -332,38 +561,56 @@ class HoloSphere {
|
|
|
332
561
|
* @returns {Promise<object|null>} - The parsed data from the table or null if not found.
|
|
333
562
|
*/
|
|
334
563
|
async getAllGlobal(tableName) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
}
|
|
564
|
+
return new Promise(async (resolve, reject) => {
|
|
565
|
+
let output = []
|
|
566
|
+
let counter = 0
|
|
567
|
+
this.gun.get(tableName.toString()).once((data, key) => {
|
|
568
|
+
if (data) {
|
|
569
|
+
const maplenght = Object.keys(data).length - 1
|
|
570
|
+
this.gun.get(tableName.toString()).map().once(async (itemdata, key) => {
|
|
571
|
+
counter += 1
|
|
572
|
+
if (itemdata) {
|
|
573
|
+
let parsed = await this.parse(itemdata)
|
|
574
|
+
output.push(parsed);
|
|
575
|
+
}
|
|
350
576
|
|
|
351
|
-
|
|
577
|
+
if (counter == maplenght) {
|
|
578
|
+
resolve(output);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
);
|
|
582
|
+
} else resolve(output)
|
|
583
|
+
})
|
|
584
|
+
}
|
|
585
|
+
)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Deletes a specific key from a global table.
|
|
590
|
+
* @param {string} tableName - The table name to delete from.
|
|
591
|
+
* @param {string} key - The key to delete.
|
|
592
|
+
* @returns {Promise<void>}
|
|
593
|
+
*/
|
|
594
|
+
async deleteGlobal(tableName, key) {
|
|
595
|
+
await this.gun.get(this.appname).get(tableName).get(key).put(null)
|
|
596
|
+
}
|
|
352
597
|
|
|
353
598
|
/**
|
|
354
599
|
* Deletes an entire global table.
|
|
355
|
-
* @param {string}
|
|
600
|
+
* @param {string} tableName - The table name to delete.
|
|
356
601
|
* @returns {Promise<void>}
|
|
357
602
|
*/
|
|
358
603
|
async deleteAllGlobal(tableName) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
604
|
+
|
|
605
|
+
return new Promise((resolve) => {
|
|
606
|
+
this.gun.get(this.appname).get(tableName).map().put(null).once(
|
|
607
|
+
(data, key) => this.gun.get(this.appname).get(tableName).get(key).put(null)
|
|
608
|
+
)
|
|
609
|
+
this.gun.get(this.appname).get(tableName).put({}, ack => {
|
|
610
|
+
resolve();
|
|
611
|
+
});
|
|
364
612
|
});
|
|
365
|
-
}
|
|
366
|
-
}
|
|
613
|
+
}
|
|
367
614
|
|
|
368
615
|
// ================================ COMPUTE FUNCTIONS ================================
|
|
369
616
|
/**
|
|
@@ -374,8 +621,8 @@ class HoloSphere {
|
|
|
374
621
|
*/
|
|
375
622
|
async compute(holon, lens, operation) {
|
|
376
623
|
|
|
377
|
-
|
|
378
|
-
|
|
624
|
+
let res = h3.getResolution(holon);
|
|
625
|
+
if(res < 1 || res > 15) return;
|
|
379
626
|
console.log(res)
|
|
380
627
|
let parent = h3.cellToParent(holon, res - 1);
|
|
381
628
|
let siblings = h3.cellToChildren(parent, res);
|
|
@@ -391,7 +638,7 @@ class HoloSphere {
|
|
|
391
638
|
resolve(); // Resolve the promise to prevent it from hanging
|
|
392
639
|
}, 1000); // Timeout of 5 seconds
|
|
393
640
|
|
|
394
|
-
this.gun.get(siblings[i]).get(lens).map().once((data, key) => {
|
|
641
|
+
this.gun.get(this.appname).get(siblings[i]).get(lens).map().once((data, key) => {
|
|
395
642
|
clearTimeout(timeout); // Clear the timeout if data is received
|
|
396
643
|
if (data) {
|
|
397
644
|
content.push(data.content);
|
|
@@ -405,7 +652,7 @@ class HoloSphere {
|
|
|
405
652
|
console.log('Content:', content);
|
|
406
653
|
let computed = await this.summarize(content.join('\n'))
|
|
407
654
|
console.log('Computed:', computed)
|
|
408
|
-
let node = await this.gun.get(parent + '_summary').put({ id: parent + '_summary', content: computed })
|
|
655
|
+
let node = await this.gun.get(this.appname).get(parent + '_summary').put({ id: parent + '_summary', content: computed })
|
|
409
656
|
|
|
410
657
|
this.put(parent, lens, node);
|
|
411
658
|
this.compute(parent, lens, operation)
|
|
@@ -420,10 +667,10 @@ class HoloSphere {
|
|
|
420
667
|
let entities = {};
|
|
421
668
|
|
|
422
669
|
// Get list out of Gun
|
|
423
|
-
this.gun.get(holon).get(lens).map().once((data, key) => {
|
|
670
|
+
this.gun.get(this.appname).get(holon).get(lens).map().once((data, key) => {
|
|
424
671
|
//entities = data;
|
|
425
672
|
//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 })
|
|
673
|
+
this.gun.get(this.appname).get(holon).get(lens).put({ [key]: null })
|
|
427
674
|
})
|
|
428
675
|
}
|
|
429
676
|
|
|
@@ -556,91 +803,10 @@ class HoloSphere {
|
|
|
556
803
|
* @param {function} callback - The callback to execute on changes.
|
|
557
804
|
*/
|
|
558
805
|
subscribe(holon, lens, callback) {
|
|
559
|
-
this.gun.get(holon).get(lens).map().on((data, key) => {
|
|
806
|
+
this.gun.get(this.appname).get(holon).get(lens).map().on((data, key) => {
|
|
560
807
|
callback(data, key)
|
|
561
808
|
})
|
|
562
809
|
}
|
|
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
810
|
}
|
|
645
811
|
|
|
646
812
|
export default HoloSphere;
|