holosphere 1.1.21 → 1.3.0-alpha3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/holosphere.js CHANGED
@@ -1,13 +1,12 @@
1
1
  /**
2
2
  * @module holosphere
3
- * @version 1.1.21
3
+ * @version 1.3.0
4
4
  * @description Holonic Geospatial Communication Infrastructure
5
5
  * @author Roberto Valenti
6
6
  * @license GPL-3.0-or-later
7
7
  */
8
8
 
9
9
  import * as h3 from 'h3-js';
10
- import OpenAI from 'openai';
11
10
  import Gun from 'gun'
12
11
  import Ajv2019 from 'ajv/dist/2019.js'
13
12
  import * as Federation from './federation.js';
@@ -19,34 +18,90 @@ import * as HologramOps from './hologram.js';
19
18
  import * as ComputeOps from './compute.js';
20
19
  import * as Utils from './utils.js';
21
20
 
21
+ // Named exports (v2-compatible)
22
+ import { nostrUtils } from './nostr-utils-shim.js';
23
+ import { subscriptions, buildLensPath } from './subscriptions-shim.js';
24
+ import { registry } from './registry-shim.js';
25
+ import * as handshake from './handshake-shim.js';
26
+
22
27
  // Define the version constant
23
- const HOLOSPHERE_VERSION = '1.1.21';
28
+ const HOLOSPHERE_VERSION = '1.3.0';
29
+ const version = HOLOSPHERE_VERSION;
24
30
 
25
31
  class HoloSphere {
26
32
  /**
27
33
  * Initializes a new instance of the HoloSphere class.
28
- * @param {string} appname - The name of the application.
29
- * @param {boolean} [strict=false] - Whether to enforce strict schema validation.
30
- * @param {string|null} [openaikey=null] - The OpenAI API key.
31
- * @param {object} [gunOptions={}] - Optional Gun constructor options (e.g., peers, localStorage, radisk).
32
- */
33
- constructor(appname, strict = false, openaikey = null, gunOptions = {}) {
34
+ * Supports both v1 positional args and v2 config object.
35
+ *
36
+ * v1: new HoloSphere(appname, strict, openaikey, gunOptions)
37
+ * v2: new HoloSphere({ appName, privateKey, backend, nostr: { peers, relays } })
38
+ *
39
+ * @param {string|object} appnameOrConfig - App name string (v1) or config object (v2).
40
+ * @param {boolean} [strict=false] - Whether to enforce strict schema validation (v1 only).
41
+ * @param {string|null} [openaikey=null] - The OpenAI API key (v1 only).
42
+ * @param {object} [gunOptions={}] - Optional Gun constructor options (v1 only).
43
+ */
44
+ constructor(appnameOrConfig, strict = false, openaikey = null, gunOptions = {}) {
45
+ // Detect v2-style config object
46
+ if (typeof appnameOrConfig === 'object' && appnameOrConfig !== null) {
47
+ const config = appnameOrConfig;
48
+ this.config = config;
49
+ this.appname = config.appName || config.appname || 'holosphere';
50
+ this.strict = config.strict || false;
51
+ this._privateKey = config.privateKey || null;
52
+
53
+ // Derive public key from private key
54
+ if (this._privateKey) {
55
+ try {
56
+ const pubHex = nostrUtils.getPublicKeyFromBytes
57
+ ? nostrUtils.getPublicKeyFromBytes(this._privateKey)
58
+ : nostrUtils.getPublicKey(
59
+ typeof this._privateKey === 'string'
60
+ ? this._privateKey
61
+ : nostrUtils.bytesToHex(this._privateKey)
62
+ );
63
+ this.client = { publicKey: pubHex };
64
+ } catch (e) {
65
+ console.warn('Failed to derive public key from private key:', e.message);
66
+ this.client = { publicKey: '' };
67
+ }
68
+ } else {
69
+ this.client = { publicKey: '' };
70
+ }
71
+
72
+ // Map nostr relay/peer config to GunDB peers
73
+ const relays = config.nostr?.relays || config.nostr?.peers || [];
74
+ if (relays.length > 0) {
75
+ const gunPeers = relays.map(r =>
76
+ r.replace('wss://', 'https://').replace('ws://', 'http://') + '/gun'
77
+ );
78
+ gunOptions = { peers: gunPeers, ...gunOptions };
79
+ }
80
+
81
+ openaikey = config.openaiKey || config.openaikey || null;
82
+ } else {
83
+ // v1-style positional args
84
+ this.appname = appnameOrConfig;
85
+ this.config = { appName: appnameOrConfig };
86
+ this.client = { publicKey: '' };
87
+ this.strict = strict;
88
+ this._privateKey = null;
89
+ }
90
+
34
91
  console.log('HoloSphere v' + HOLOSPHERE_VERSION);
35
- this.appname = appname
36
- this.strict = strict;
92
+
37
93
  this.validator = new Ajv2019({
38
94
  allErrors: true,
39
- strict: false, // Keep this false to avoid Ajv strict mode issues
40
- validateSchema: true // Always validate schemas
95
+ strict: false,
96
+ validateSchema: true
41
97
  });
42
98
 
43
-
44
99
  // Define default Gun options with radisk enabled
45
100
  const defaultGunOptions = {
46
101
  peers: ['https://gun.holons.io/gun'],
47
102
  axe: false,
48
- radisk: true, // Enable radisk storage by default
49
- file: './holosphere' // Default directory for radisk storage
103
+ radisk: true,
104
+ file: './holosphere'
50
105
  };
51
106
 
52
107
  // In browser environment, disable localStorage when radisk is enabled
@@ -59,20 +114,28 @@ class HoloSphere {
59
114
  console.log("Initializing Gun with options:", finalGunOptions);
60
115
 
61
116
  // Use provided Gun instance or create new one with final options
62
- this.gun = Gun(finalGunOptions); // Pass the merged options
63
-
117
+ this.gun = Gun(finalGunOptions);
64
118
 
65
- if (openaikey != null) {
66
- this.openai = new OpenAI({
67
- apiKey: openaikey,
68
- });
69
- }
119
+ // OpenAI is optional - callers can set this.openai directly if needed
120
+ this.openai = null;
70
121
 
71
122
  // Initialize subscriptions
72
123
  this.subscriptions = {};
73
-
124
+
74
125
  // Initialize schema cache
75
126
  this.schemaCache = new Map();
127
+
128
+ // Initialize allowed authors set (for canWrite)
129
+ this._allowedAuthors = new Set();
130
+ }
131
+
132
+ /**
133
+ * Waits for the HoloSphere instance to be ready.
134
+ * GunDB connects eagerly, so this resolves immediately.
135
+ * @returns {Promise<HoloSphere>} - The ready instance
136
+ */
137
+ async ready() {
138
+ return this;
76
139
  }
77
140
 
78
141
  getGun() {
@@ -81,37 +144,15 @@ class HoloSphere {
81
144
 
82
145
  // ================================ SCHEMA FUNCTIONS ================================
83
146
 
84
- /**
85
- * Sets the JSON schema for a specific lens.
86
- * @param {string} lens - The lens identifier.
87
- * @param {object} schema - The JSON schema to set.
88
- * @returns {Promise} - Resolves when the schema is set.
89
- */
90
147
  async setSchema(lens, schema) {
91
- // Delegate to the external function
92
148
  return SchemaOps.setSchema(this, lens, schema);
93
149
  }
94
150
 
95
- /**
96
- * Retrieves the JSON schema for a specific lens.
97
- * @param {string} lens - The lens identifier.
98
- * @param {object} [options] - Additional options
99
- * @param {boolean} [options.useCache=true] - Whether to use the schema cache
100
- * @param {number} [options.maxCacheAge=3600000] - Maximum cache age in milliseconds (default: 1 hour)
101
- * @returns {Promise<object|null>} - The retrieved schema or null if not found.
102
- */
103
151
  async getSchema(lens, options = {}) {
104
- // Delegate to the external function
105
152
  return SchemaOps.getSchema(this, lens, options);
106
153
  }
107
154
 
108
- /**
109
- * Clears the schema cache or a specific schema from the cache.
110
- * @param {string} [lens] - Optional lens to clear from cache. If not provided, clears entire cache.
111
- * @returns {boolean} - Returns true if successful
112
- */
113
155
  clearSchemaCache(lens = null) {
114
- // Delegate to the external function
115
156
  return SchemaOps.clearSchemaCache(this, lens);
116
157
  }
117
158
 
@@ -119,458 +160,276 @@ class HoloSphere {
119
160
 
120
161
  /**
121
162
  * Stores content in the specified holon and lens.
122
- * @param {string} holon - The holon identifier.
123
- * @param {string} lens - The lens under which to store the content.
124
- * @param {object} data - The data to store.
125
- * @param {string} [password] - Optional password for private holon.
126
- * @param {object} [options] - Additional options
127
- * @param {boolean} [options.autoPropagate=true] - Whether to automatically propagate to federated holons (default: true)
128
- * @param {object} [options.propagationOptions] - Options to pass to propagate
129
- * @param {boolean} [options.propagationOptions.useReferences=true] - Whether to use references instead of duplicating data
130
- * @returns {Promise<boolean>} - Returns true if successful, false if there was an error
131
- */
132
- async put(holon, lens, data, password = null, options = {}) {
133
- // Delegate to the external function
163
+ * Supports both v1 and v2 calling conventions:
164
+ * v1: put(holon, lens, data, password, options)
165
+ * v2: put(holon, lens, data, { actingAs }) or put(holon, lens, data)
166
+ */
167
+ async put(holon, lens, data, passwordOrOptions = null, options = {}) {
168
+ let password = null;
169
+ if (typeof passwordOrOptions === 'object' && passwordOrOptions !== null) {
170
+ // v2-style: 4th arg is options object (e.g., { actingAs })
171
+ options = passwordOrOptions;
172
+ password = options.password || null;
173
+ } else {
174
+ // v1-style: 4th arg is password string
175
+ password = passwordOrOptions;
176
+ }
134
177
  return ContentOps.put(this, holon, lens, data, password, options);
135
178
  }
136
179
 
137
180
  /**
138
181
  * Retrieves content from the specified holon and lens.
139
- * @param {string} holon - The holon identifier.
140
- * @param {string} lens - The lens from which to retrieve content.
141
- * @param {string} key - The specific key to retrieve.
142
- * @param {string} [password] - Optional password for private holon.
143
- * @param {object} [options] - Additional options
144
- * @param {boolean} [options.resolveReferences=true] - Whether to automatically resolve federation references
145
- * @returns {Promise<object|null>} - The retrieved content or null if not found.
146
- */
147
- async get(holon, lens, key, password = null, options = {}) {
148
- // Delegate to the external function
182
+ * Supports both v1 and v2 calling conventions:
183
+ * v1: get(holon, lens, key, password, options)
184
+ * v2: get(holon, lens) or get(holon, lens, key)
185
+ */
186
+ async get(holon, lens, key = null, password = null, options = {}) {
187
+ if (key === null || key === undefined) {
188
+ // v2-style 2-arg call: get entire lens (return first/only item)
189
+ const items = await ContentOps.getAll(this, holon, lens, null);
190
+ return items && items.length > 0 ? items[0] : null;
191
+ }
149
192
  return ContentOps.get(this, holon, lens, key, password, options);
150
193
  }
151
194
 
152
- /**
153
- * Retrieves all content from the specified holon and lens.
154
- * @param {string} holon - The holon identifier.
155
- * @param {string} lens - The lens from which to retrieve content.
156
- * @param {string} [password] - Optional password for private holon.
157
- * @returns {Promise<Array<object>>} - The retrieved content.
158
- */
159
195
  async getAll(holon, lens, password = null) {
160
- // Delegate to the external function
161
196
  return ContentOps.getAll(this, holon, lens, password);
162
197
  }
163
198
 
164
- /**
165
- * Parses data from GunDB, handling various data formats and references.
166
- * @param {*} data - The data to parse, could be a string, object, or GunDB reference.
167
- * @returns {Promise<object>} - The parsed data.
168
- */
169
199
  async parse(rawData) {
170
- // Delegate to the external function
171
200
  return ContentOps.parse(this, rawData);
172
201
  }
173
202
 
174
- /**
175
- * Deletes a specific key from a given holon and lens.
176
- * @param {string} holon - The holon identifier.
177
- * @param {string} lens - The lens from which to delete the key.
178
- * @param {string} key - The specific key to delete.
179
- * @param {string} [password] - Optional password for private holon.
180
- * @returns {Promise<boolean>} - Returns true if successful
181
- */
182
203
  async delete(holon, lens, key, password = null) {
183
- // Delegate to the external function (renamed to deleteFunc in module)
184
204
  return ContentOps.deleteFunc(this, holon, lens, key, password);
185
205
  }
186
206
 
187
- /**
188
- * Deletes all keys from a given holon and lens.
189
- * @param {string} holon - The holon identifier.
190
- * @param {string} lens - The lens from which to delete all keys.
191
- * @param {string} [password] - Optional password for private holon.
192
- * @returns {Promise<boolean>} - Returns true if successful
193
- */
194
207
  async deleteAll(holon, lens, password = null) {
195
- // Delegate to the external function
196
208
  return ContentOps.deleteAll(this, holon, lens, password);
197
209
  }
198
210
 
199
211
  // ================================ NODE FUNCTIONS ================================
200
212
 
201
-
202
- /**
203
- * Stores a specific gun node in a given holon and lens.
204
- * @param {string} holon - The holon identifier.
205
- * @param {string} lens - The lens under which to store the node.
206
- * @param {object} data - The node to store.
207
- */
208
213
  async putNode(holon, lens, data) {
209
- // Delegate to the external function
210
214
  return NodeOps.putNode(this, holon, lens, data);
211
215
  }
212
216
 
213
- /**
214
- * Retrieves a specific gun node from the specified holon and lens.
215
- * @param {string} holon - The holon identifier.
216
- * @param {string} lens - The lens identifier.
217
- * @param {string} key - The specific key to retrieve.
218
- * @returns {Promise<any>} - The retrieved node or null if not found.
219
- */
220
217
  async getNode(holon, lens, key) {
221
- // Delegate to the external function
222
218
  return NodeOps.getNode(this, holon, lens, key);
223
219
  }
224
220
 
225
- /**
226
- * Retrieves a Gun node reference using its soul path
227
- * @param {string} soul - The soul path of the node
228
- * @returns {Gun.ChainReference} - The Gun node reference
229
- */
230
221
  getNodeRef(soul) {
231
- // Delegate to the external function
232
222
  return NodeOps.getNodeRef(this, soul);
233
223
  }
234
224
 
235
- /**
236
- * Retrieves a node directly using its soul path
237
- * @param {string} soul - The soul path of the node
238
- * @returns {Promise<any>} - The retrieved node or null if not found.
239
- */
240
225
  async getNodeBySoul(soul) {
241
- // Delegate to the external function
242
226
  return NodeOps.getNodeBySoul(this, soul);
243
227
  }
244
228
 
245
- /**
246
- * Deletes a specific gun node from a given holon and lens.
247
- * @param {string} holon - The holon identifier.
248
- * @param {string} lens - The lens identifier.
249
- * @param {string} key - The key of the node to delete.
250
- * @returns {Promise<boolean>} - Returns true if successful
251
- */
252
229
  async deleteNode(holon, lens, key) {
253
- // Delegate to the external function
254
230
  return NodeOps.deleteNode(this, holon, lens, key);
255
231
  }
256
232
 
257
233
  // ================================ GLOBAL FUNCTIONS ================================
258
- /**
259
- * Stores data in a global (non-holon-specific) table.
260
- * @param {string} tableName - The table name to store data in.
261
- * @param {object} data - The data to store. If it has an 'id' field, it will be used as the key.
262
- * @param {string} [password] - Optional password for private holon.
263
- * @returns {Promise<void>}
264
- */
234
+
265
235
  async putGlobal(tableName, data, password = null) {
266
- // Delegate to the external function
267
236
  return GlobalOps.putGlobal(this, tableName, data, password);
268
237
  }
269
238
 
270
239
  /**
271
- * Retrieves a specific key from a global table.
272
- * @param {string} tableName - The table name to retrieve from.
273
- * @param {string} key - The key to retrieve.
274
- * @param {string} [password] - Optional password for private holon.
275
- * @returns {Promise<object|null>} - The parsed data for the key or null if not found.
240
+ * v2-compatible alias for putGlobal (no password param)
276
241
  */
242
+ async writeGlobal(tableName, data) {
243
+ return GlobalOps.putGlobal(this, tableName, data, null);
244
+ }
245
+
277
246
  async getGlobal(tableName, key, password = null) {
278
- // Delegate to the external function
279
247
  return GlobalOps.getGlobal(this, tableName, key, password);
280
248
  }
281
249
 
282
- /**
283
- * Retrieves all data from a global table.
284
- * @param {string} tableName - The table name to retrieve data from.
285
- * @param {string} [password] - Optional password for private holon.
286
- * @returns {Promise<Array<object>>} - The parsed data from the table as an array.
287
- */
288
250
  async getAllGlobal(tableName, password = null) {
289
- // Delegate to the external function
290
251
  return GlobalOps.getAllGlobal(this, tableName, password);
291
252
  }
292
253
 
293
- /**
294
- * Deletes a specific key from a global table.
295
- * @param {string} tableName - The table name to delete from.
296
- * @param {string} key - The key to delete.
297
- * @param {string} [password] - Optional password for private holon.
298
- * @returns {Promise<boolean>}
299
- */
300
254
  async deleteGlobal(tableName, key, password = null) {
301
- // Delegate to the external function
302
255
  return GlobalOps.deleteGlobal(this, tableName, key, password);
303
256
  }
304
257
 
305
- /**
306
- * Deletes an entire global table.
307
- * @param {string} tableName - The table name to delete.
308
- * @param {string} [password] - Optional password for private holon.
309
- * @returns {Promise<boolean>}
310
- */
311
258
  async deleteAllGlobal(tableName, password = null) {
312
- // Delegate to the external function
313
259
  return GlobalOps.deleteAllGlobal(this, tableName, password);
314
260
  }
315
261
 
316
- // ================================ REFERENCE FUNCTIONS ================================
317
-
318
262
  /**
319
- * Creates a soul hologram object for a data item
320
- * @param {string} holon - The holon where the original data is stored
321
- * @param {string} lens - The lens where the original data is stored
322
- * @param {object} data - The data to create a hologram for
323
- * @returns {object} - A hologram object with id and soul
263
+ * Subscribe to real-time changes in a global table.
264
+ * v2-compatible: subscribeGlobal(lens, key, callback, options)
324
265
  */
266
+ async subscribeGlobal(lens, keyOrCallback, callbackOrOptions, options = {}) {
267
+ let key, callback;
268
+ if (typeof keyOrCallback === 'function') {
269
+ callback = keyOrCallback;
270
+ key = null;
271
+ options = callbackOrOptions || {};
272
+ } else {
273
+ key = keyOrCallback;
274
+ callback = callbackOrOptions;
275
+ }
276
+ return GlobalOps.subscribeGlobal(this, lens, key, callback, options);
277
+ }
278
+
279
+ // ================================ REFERENCE FUNCTIONS ================================
280
+
325
281
  createHologram(holon, lens, data) {
326
- // Delegate to the external function
327
282
  return HologramOps.createHologram(this, holon, lens, data);
328
283
  }
329
-
330
- /**
331
- * Parses a soul path into its components
332
- * @param {string} soul - The soul path to parse
333
- * @returns {object|null} - The parsed components or null if invalid format
334
- */
284
+
335
285
  parseSoulPath(soul) {
336
- // Delegate to the external function (doesn't need instance)
337
286
  return HologramOps.parseSoulPath(soul);
338
287
  }
339
-
340
- /**
341
- * Checks if an object is a hologram
342
- * @param {object} data - The data to check
343
- * @returns {boolean} - True if the object is a hologram
344
- */
288
+
345
289
  isHologram(data) {
346
- // Delegate to the external function (doesn't need instance)
347
290
  return HologramOps.isHologram(data);
348
291
  }
349
-
350
- /**
351
- * Resolves a hologram to its actual data
352
- * @param {object} hologram - The hologram to resolve
353
- * @param {object} [options] - Optional parameters
354
- * @param {boolean} [options.followHolograms=true] - Whether to follow nested holograms
355
- * @param {Set<string>} [options.visited] - Internal use: Tracks visited souls to prevent loops
356
- * @returns {Promise<object|null>} - The resolved data, null if resolution failed due to target not found, or the original hologram for circular/invalid cases.
357
- */
292
+
358
293
  async resolveHologram(hologram, options = {}) {
359
- // Delegate to the external function
360
294
  return HologramOps.resolveHologram(this, hologram, options);
361
295
  }
362
296
 
297
+ attachHologramMeta(originalData, hologramSoul) {
298
+ return HologramOps.attachHologramMeta(originalData, hologramSoul);
299
+ }
300
+
363
301
  // ================================ COMPUTE FUNCTIONS ================================
364
- /**
365
- * Computes operations across multiple layers up the hierarchy
366
- * @param {string} holon - Starting holon identifier
367
- * @param {string} lens - The lens to compute
368
- * @param {object} options - Computation options
369
- * @param {number} [maxLevels=15] - Maximum levels to compute up
370
- * @param {string} [password] - Optional password for private holons
371
- */
302
+
372
303
  async computeHierarchy(holon, lens, options, maxLevels = 15, password = null) {
373
- // Delegate to the external function
374
304
  return ComputeOps.computeHierarchy(this, holon, lens, options, maxLevels, password);
375
305
  }
376
306
 
377
- /**
378
- * Computes operations on content within a holon and lens for one layer up.
379
- * @param {string} holon - The holon identifier.
380
- * @param {string} lens - The lens to compute.
381
- * @param {object} options - Computation options
382
- * @param {string} options.operation - The operation to perform ('summarize', 'aggregate', 'concatenate')
383
- * @param {string[]} [options.fields] - Fields to perform operation on
384
- * @param {string} [options.targetField] - Field to store the result in
385
- * @param {string} [password] - Optional password for private holons
386
- * @throws {Error} If parameters are invalid or missing
387
- */
388
307
  async compute(holon, lens, options, password = null) {
389
- // Delegate to the external function
390
308
  return ComputeOps.compute(this, holon, lens, options, password);
391
309
  }
392
310
 
393
- /**
394
- * Summarizes provided history text using OpenAI.
395
- * @param {string} history - The history text to summarize.
396
- * @returns {Promise<string>} - The summarized text.
397
- */
398
311
  async summarize(history) {
399
- // Delegate to the external function
400
312
  return ComputeOps.summarize(this, history);
401
313
  }
402
314
 
403
- /**
404
- * Upcasts content to parent holonagons recursively using references.
405
- * @param {string} holon - The current holon identifier.
406
- * @param {string} lens - The lens under which to upcast.
407
- * @param {object} content - The content to upcast.
408
- * @param {number} [maxLevels=15] - Maximum levels to upcast.
409
- * @returns {Promise<object>} - The original content.
410
- */
411
315
  async upcast(holon, lens, content, maxLevels = 15) {
412
- // Delegate to the external function
413
316
  return ComputeOps.upcast(this, holon, lens, content, maxLevels);
414
317
  }
415
318
 
416
- /**
417
- * Updates the parent holon with a new report.
418
- * @param {string} id - The child holon identifier.
419
- * @param {string} report - The report to update.
420
- * @returns {Promise<object>} - The updated parent information.
421
- */
422
319
  async updateParent(id, report) {
423
- // Delegate to the external function
424
320
  return ComputeOps.updateParent(this, id, report);
425
321
  }
426
322
 
427
- /**
428
- * Propagates data to federated holons
429
- * @param {string} holon - The holon identifier
430
- * @param {string} lens - The lens identifier
431
- * @param {object} data - The data to propagate
432
- * @param {object} [options] - Propagation options
433
- * @returns {Promise<object>} - Result with success count and errors
434
- */
435
323
  async propagate(holon, lens, data, options = {}) {
436
324
  return Federation.propagate(this, holon, lens, data, options);
437
325
  }
438
326
 
439
- /**
440
- * Converts latitude and longitude to a holon identifier.
441
- * @param {number} lat - The latitude.
442
- * @param {number} lng - The longitude.
443
- * @param {number} resolution - The resolution level.
444
- * @returns {Promise<string>} - The resulting holon identifier.
445
- */
446
327
  async getHolon(lat, lng, resolution) {
447
- // Delegate to the external function
448
328
  return Utils.getHolon(lat, lng, resolution);
449
329
  }
450
330
 
451
- /**
452
- * Retrieves all containing holonagons at all scales for given coordinates.
453
- * @param {number} lat - The latitude.
454
- * @param {number} lng - The longitude.
455
- * @returns {Array<string>} - List of holon identifiers.
456
- */
457
331
  getScalespace(lat, lng) {
458
- // Delegate to the external function
459
332
  return Utils.getScalespace(lat, lng);
460
333
  }
461
334
 
462
- /**
463
- * Retrieves all containing holonagons at all scales for a given holon.
464
- * @param {string} holon - The holon identifier.
465
- * @returns {Array<string>} - List of holon identifiers.
466
- */
467
335
  getHolonScalespace(holon) {
468
- // Delegate to the external function
469
336
  return Utils.getHolonScalespace(holon);
470
337
  }
471
338
 
472
- /**
473
- * Subscribes to changes in a specific holon and lens.
474
- * @param {string} holon - The holon identifier.
475
- * @param {string} lens - The lens to subscribe to.
476
- * @param {function} callback - The callback to execute on changes.
477
- * @returns {Promise<object>} - Subscription object with unsubscribe method
478
- */
479
339
  async subscribe(holon, lens, callback) {
480
- // Delegate to the external function
481
340
  return Utils.subscribe(this, holon, lens, callback);
482
341
  }
483
342
 
484
- /**
485
- * Notifies subscribers about data changes
486
- * @param {object} data - The data to notify about
487
- * @private
488
- */
489
343
  notifySubscribers(data) {
490
- // Delegate to the external function
491
344
  return Utils.notifySubscribers(this, data);
492
345
  }
493
346
 
494
- // Add ID generation method
495
347
  generateId() {
496
- // Delegate to the external function
497
348
  return Utils.generateId();
498
349
  }
499
350
 
500
351
  // ================================ FEDERATION FUNCTIONS ================================
501
352
 
502
- /**
503
- * Creates a federation relationship between two holons
504
- * @param {string} holonId1 - The first holon ID
505
- * @param {string} holonId2 - The second holon ID
506
- * @param {string} [password1] - Optional password for the first holon
507
- * @param {string} [password2] - Optional password for the second holon
508
- * @param {boolean} [bidirectional=true] - Whether to set up bidirectional notifications automatically
509
- * @param {object} [lensConfig] - Optional lens-specific configuration
510
- * @param {string[]} [lensConfig.federate] - List of lenses to federate (default: all)
511
- * @param {string[]} [lensConfig.notify] - List of lenses to notify (default: all)
512
- * @returns {Promise<boolean>} - True if federation was created successfully
513
- */
514
353
  async federate(holonId1, holonId2, password1 = null, password2 = null, bidirectional = true, lensConfig = {}) {
515
354
  return Federation.federate(this, holonId1, holonId2, password1, password2, bidirectional, lensConfig);
516
355
  }
517
356
 
518
357
  /**
519
- * Subscribes to federation notifications for a holon
520
- * @param {string} holonId - The holon ID to subscribe to
521
- * @param {string} password - Password for the holon
522
- * @param {function} callback - The callback to execute on notifications
523
- * @param {object} [options] - Subscription options
524
- * @param {string[]} [options.lenses] - Specific lenses to subscribe to (default: all)
525
- * @param {number} [options.throttle] - Throttle notifications in ms (default: 0)
526
- * @returns {Promise<object>} - Subscription object with unsubscribe() method
358
+ * Convenience wrapper around federate() for the common bidirectional case.
359
+ * @param {string} sourceHolon - Source holon ID
360
+ * @param {string} targetHolon - Target holon ID
361
+ * @param {object} [options] - Federation options
362
+ * @param {object} [options.lensConfig] - Lens config from sourceHolon's perspective
363
+ * @param {string[]} [options.lensConfig.inbound] - Lenses sourceHolon receives from targetHolon
364
+ * @param {string[]} [options.lensConfig.outbound] - Lenses sourceHolon sends to targetHolon
365
+ * @param {string} [options.partnerName] - Display name for the partner
366
+ * @returns {Promise<boolean>}
527
367
  */
368
+ async federateHolon(sourceHolon, targetHolon, options = {}) {
369
+ const lensConfig = options.lensConfig || {};
370
+ const inbound = Array.isArray(lensConfig.inbound) ? lensConfig.inbound : [];
371
+ const outbound = Array.isArray(lensConfig.outbound) ? lensConfig.outbound : [];
372
+
373
+ const ok = await Federation.federate(this, sourceHolon, targetHolon, null, null, true, {
374
+ inbound,
375
+ outbound
376
+ });
377
+
378
+ if (ok && options.partnerName) {
379
+ try {
380
+ const fedInfo = await this.getFederation(sourceHolon);
381
+ if (fedInfo) {
382
+ if (!fedInfo.partnerNames) fedInfo.partnerNames = {};
383
+ fedInfo.partnerNames[targetHolon] = options.partnerName;
384
+ await this.putGlobal('federation', fedInfo);
385
+ }
386
+ } catch (e) {
387
+ console.warn('Failed to store partner name:', e.message);
388
+ }
389
+ }
390
+
391
+ return ok;
392
+ }
393
+
394
+ /**
395
+ * v2-compatible federation removal.
396
+ * @param {string} sourceHolon - Source holon ID
397
+ * @param {string} targetHolon - Target holon ID
398
+ * @returns {Promise<boolean>}
399
+ */
400
+ async unfederateHolon(sourceHolon, targetHolon) {
401
+ return Federation.unfederate(this, sourceHolon, targetHolon, null, null);
402
+ }
403
+
528
404
  async subscribeFederation(holonId, password, callback, options = {}) {
529
405
  return Federation.subscribeFederation(this, holonId, password, callback, options);
530
406
  }
531
407
 
532
408
  /**
533
- * Gets federation info for a holon
534
- * @param {string} holonId - The holon ID
535
- * @param {string} [password] - Optional password for the holon
536
- * @returns {Promise<object|null>} - Federation info or null if not found
409
+ * Gets federation info for a holon.
410
+ * Returns v2-compatible shape with `federated`, `lensConfig`, `partnerNames` fields.
537
411
  */
538
412
  async getFederation(holonId, password = null) {
539
- return Federation.getFederation(this, holonId, password);
413
+ const result = await Federation.getFederation(this, holonId, password);
414
+ if (!result) return { federated: [], lensConfig: {}, partnerNames: {} };
415
+
416
+ // Add v2-compatible fields alongside existing v1 fields
417
+ if (!result.federated) result.federated = result.federation || [];
418
+ if (!result.partnerNames) result.partnerNames = {};
419
+ // Ensure lensConfig exists (v1 already stores this)
420
+ if (!result.lensConfig) result.lensConfig = {};
421
+
422
+ return result;
540
423
  }
541
-
542
- /**
543
- * Retrieves the lens-specific configuration for a federation link between two holons.
544
- * @param {string} holonId - The ID of the source holon.
545
- * @param {string} targetHolonId - The ID of the target holon in the federation link.
546
- * @param {string} [password] - Optional password for the source holon.
547
- * @returns {Promise<object|null>} - An object with 'federate' and 'notify' arrays, or null if not found.
548
- */
424
+
549
425
  async getFederatedConfig(holonId, targetHolonId, password = null) {
550
426
  return Federation.getFederatedConfig(this, holonId, targetHolonId, password);
551
427
  }
552
428
 
553
- /**
554
- * Removes a federation relationship between holons
555
- * @param {string} holonId1 - The first holon ID
556
- * @param {string} holonId2 - The second holon ID
557
- * @param {string} password1 - Password for the first holon
558
- * @param {string} [password2] - Optional password for the second holon
559
- * @returns {Promise<boolean>} - True if federation was removed successfully
560
- */
561
429
  async unfederate(holonId1, holonId2, password1, password2 = null) {
562
430
  return await Federation.unfederate(this, holonId1, holonId2, password1, password2);
563
431
  }
564
432
 
565
- /**
566
- * Removes a notification relationship between two spaces
567
- * This removes spaceId2 from the notify list of spaceId1
568
- *
569
- * @param {string} holonId1 - The space to modify (remove from its notify list)
570
- * @param {string} holonId2 - The space to be removed from notifications
571
- * @param {string} [password1] - Optional password for the first space
572
- * @returns {Promise<boolean>} - True if notification was removed successfully
573
- */
574
433
  async removeNotify(holonId1, holonId2, password1 = null) {
575
434
  console.log(`HoloSphere.removeNotify called: ${holonId1}, ${holonId2}`);
576
435
  try {
@@ -583,99 +442,96 @@ class HoloSphere {
583
442
  }
584
443
  }
585
444
 
586
- /**
587
- * Get and aggregate data from federated holons
588
- * @param {string} holon The holon name
589
- * @param {string} lens The lens name
590
- * @param {Object} options Options for retrieval and aggregation
591
- * @returns {Promise<Array>} Combined array of local and federated data
592
- */
593
445
  async getFederated(holon, lens, options = {}) {
594
446
  return Federation.getFederated(this, holon, lens, options);
595
447
  }
596
448
 
597
- /**
598
- * Tracks a federated message across different chats
599
- * @param {string} originalChatId - The ID of the original chat
600
- * @param {string} messageId - The ID of the original message
601
- * @param {string} federatedChatId - The ID of the federated chat
602
- * @param {string} federatedMessageId - The ID of the message in the federated chat
603
- * @param {string} type - The type of message (e.g., 'quest', 'announcement')
604
- * @returns {Promise<void>}
605
- */
606
449
  async federateMessage(originalChatId, messageId, federatedChatId, federatedMessageId, type = 'generic') {
607
450
  return Federation.federateMessage(this, originalChatId, messageId, federatedChatId, federatedMessageId, type);
608
451
  }
609
452
 
610
- /**
611
- * Gets all federated messages for a given original message
612
- * @param {string} originalChatId - The ID of the original chat
613
- * @param {string} messageId - The ID of the original message
614
- * @returns {Promise<Object|null>} The tracking information for the message
615
- */
616
453
  async getFederatedMessages(originalChatId, messageId) {
617
454
  return Federation.getFederatedMessages(this, originalChatId, messageId);
618
455
  }
619
456
 
620
- /**
621
- * Updates a federated message across all federated chats
622
- * @param {string} originalChatId - The ID of the original chat
623
- * @param {string} messageId - The ID of the original message
624
- * @param {Function} updateCallback - Function to update the message in each chat
625
- * @returns {Promise<void>}
626
- */
627
457
  async updateFederatedMessages(originalChatId, messageId, updateCallback) {
628
458
  return Federation.updateFederatedMessages(this, originalChatId, messageId, updateCallback);
629
459
  }
630
460
 
461
+ async resetFederation(holonId, password = null) {
462
+ return Federation.resetFederation(this, holonId, password);
463
+ }
464
+
465
+ // ================================ AUTHORIZATION FUNCTIONS ================================
466
+
631
467
  /**
632
- * Resets the federation settings for a holon
468
+ * Check if a public key can write to a holon/lens.
633
469
  * @param {string} holonId - The holon ID
634
- * @param {string} [password] - Optional password for the holon
635
- * @returns {Promise<boolean>} - True if federation was reset successfully
470
+ * @param {string} lensName - The lens name
471
+ * @param {string} actingAs - The public key attempting to write
472
+ * @param {object} [options] - Additional options
473
+ * @returns {Promise<{ canWrite: boolean, reason: string, accessType: string }>}
636
474
  */
637
- async resetFederation(holonId, password = null) {
638
- return Federation.resetFederation(this, holonId, password);
475
+ async canWrite(holonId, lensName, actingAs, options = {}) {
476
+ // Owner always has access
477
+ if (actingAs === this.client?.publicKey || actingAs === holonId) {
478
+ return { canWrite: true, reason: 'owner', accessType: 'owner' };
479
+ }
480
+
481
+ // Check allowed authors
482
+ if (this._allowedAuthors.has(actingAs)) {
483
+ return { canWrite: true, reason: 'allowed_author', accessType: 'allowed' };
484
+ }
485
+
486
+ // Check federation
487
+ try {
488
+ const fed = await Federation.getFederation(this, holonId);
489
+ if (fed && fed.federation && fed.federation.includes(actingAs)) {
490
+ return { canWrite: true, reason: 'federated', accessType: 'federation' };
491
+ }
492
+ } catch (e) { /* ignore */ }
493
+
494
+ return { canWrite: false, reason: 'not_authorized', accessType: 'none' };
639
495
  }
640
496
 
641
- // ================================ END FEDERATION FUNCTIONS ================================
642
497
  /**
643
- * Closes the HoloSphere instance and cleans up resources.
644
- * @returns {Promise<void>}
498
+ * Add a public key to the allowed authors list.
499
+ * @param {string} pubkey - The public key to allow
645
500
  */
646
- async close() {
647
- // Delegate to the external function
648
- return Utils.close(this);
501
+ addAllowedAuthor(pubkey) {
502
+ this._allowedAuthors.add(pubkey);
649
503
  }
650
504
 
651
505
  /**
652
- * Creates a namespaced username for Gun authentication
653
- * @private
654
- * @param {string} holonId - The holon ID
655
- * @returns {string} - Namespaced username
506
+ * Remove a public key from the allowed authors list.
507
+ * @param {string} pubkey - The public key to remove
656
508
  */
657
- userName(holonId) {
658
- // Delegate to the external function
659
- return Utils.userName(this, holonId);
509
+ removeAllowedAuthor(pubkey) {
510
+ this._allowedAuthors.delete(pubkey);
660
511
  }
661
512
 
662
513
  /**
663
- * Returns the current version of the HoloSphere library.
664
- * @returns {string} The library version.
514
+ * List all allowed authors.
515
+ * @returns {string[]}
665
516
  */
517
+ listAllowedAuthors() {
518
+ return Array.from(this._allowedAuthors);
519
+ }
520
+
521
+ // ================================ END FEDERATION FUNCTIONS ================================
522
+
523
+ async close() {
524
+ return Utils.close(this);
525
+ }
526
+
527
+ userName(holonId) {
528
+ return Utils.userName(this, holonId);
529
+ }
530
+
666
531
  getVersion() {
667
532
  return HOLOSPHERE_VERSION;
668
533
  }
669
534
 
670
- /**
671
- * Configures radisk storage options for GunDB.
672
- * @param {object} options - Radisk configuration options
673
- * @param {string} [options.file='./radata'] - Directory for radisk storage
674
- * @param {boolean} [options.radisk=true] - Whether to enable radisk storage
675
- * @param {number} [options.until] - Timestamp until which to keep data
676
- * @param {number} [options.retry] - Number of retries for failed operations
677
- * @param {number} [options.timeout] - Timeout for operations in milliseconds
678
- */
679
535
  configureRadisk(options = {}) {
680
536
  const defaultOptions = {
681
537
  file: './radata',
@@ -684,9 +540,9 @@ class HoloSphere {
684
540
  retry: 3,
685
541
  timeout: 5000
686
542
  };
687
-
543
+
688
544
  const radiskOptions = { ...defaultOptions, ...options };
689
-
545
+
690
546
  if (this.gun && this.gun._.opt) {
691
547
  Object.assign(this.gun._.opt, radiskOptions);
692
548
  console.log("Radisk configuration updated:", radiskOptions);
@@ -695,15 +551,11 @@ class HoloSphere {
695
551
  }
696
552
  }
697
553
 
698
- /**
699
- * Gets radisk storage statistics and information.
700
- * @returns {object} Radisk statistics including file path, enabled status, and storage info
701
- */
702
554
  getRadiskStats() {
703
555
  if (!this.gun || !this.gun._.opt) {
704
556
  return { error: "Gun instance not available" };
705
557
  }
706
-
558
+
707
559
  const options = this.gun._.opt;
708
560
  return {
709
561
  enabled: options.radisk || false,
@@ -717,4 +569,6 @@ class HoloSphere {
717
569
  }
718
570
  }
719
571
 
572
+ // Default and named exports (v2-compatible)
720
573
  export default HoloSphere;
574
+ export { HoloSphere, handshake, nostrUtils, subscriptions, buildLensPath, registry, version };