holosphere 2.0.0-alpha2 → 2.0.0-alpha4

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.
Files changed (98) hide show
  1. package/dist/2019-D2OG2idw.js +6680 -0
  2. package/dist/2019-D2OG2idw.js.map +1 -0
  3. package/dist/2019-EION3wKo.cjs +8 -0
  4. package/dist/2019-EION3wKo.cjs.map +1 -0
  5. package/dist/_commonjsHelpers-C37NGDzP.cjs +2 -0
  6. package/dist/_commonjsHelpers-C37NGDzP.cjs.map +1 -0
  7. package/dist/_commonjsHelpers-CUmg6egw.js +7 -0
  8. package/dist/_commonjsHelpers-CUmg6egw.js.map +1 -0
  9. package/dist/browser-BSniCNqO.js +3058 -0
  10. package/dist/browser-BSniCNqO.js.map +1 -0
  11. package/dist/browser-Cq59Ij19.cjs +2 -0
  12. package/dist/browser-Cq59Ij19.cjs.map +1 -0
  13. package/dist/cjs/holosphere.cjs +1 -1
  14. package/dist/esm/holosphere.js +50 -53
  15. package/dist/index-BB_vVJgv.cjs +5 -0
  16. package/dist/index-BB_vVJgv.cjs.map +1 -0
  17. package/dist/index-CBitK71M.cjs +12 -0
  18. package/dist/index-CBitK71M.cjs.map +1 -0
  19. package/dist/index-CV0eOogK.js +37423 -0
  20. package/dist/index-CV0eOogK.js.map +1 -0
  21. package/dist/index-Cz-PLCUR.js +15104 -0
  22. package/dist/index-Cz-PLCUR.js.map +1 -0
  23. package/dist/indexeddb-storage-CRsZyB2f.cjs +2 -0
  24. package/dist/indexeddb-storage-CRsZyB2f.cjs.map +1 -0
  25. package/dist/{indexeddb-storage-CMW4qRQS.js → indexeddb-storage-DZaGlY_a.js} +49 -13
  26. package/dist/indexeddb-storage-DZaGlY_a.js.map +1 -0
  27. package/dist/{memory-storage-DQzcAZlf.js → memory-storage-BkUi6sZG.js} +6 -2
  28. package/dist/memory-storage-BkUi6sZG.js.map +1 -0
  29. package/dist/{memory-storage-DmePEP2q.cjs → memory-storage-C0DuUsdY.cjs} +2 -2
  30. package/dist/memory-storage-C0DuUsdY.cjs.map +1 -0
  31. package/dist/secp256k1-0kPdAVkK.cjs +12 -0
  32. package/dist/secp256k1-0kPdAVkK.cjs.map +1 -0
  33. package/dist/{secp256k1-vOXp40Fx.js → secp256k1-DN4FVXcv.js} +2 -393
  34. package/dist/secp256k1-DN4FVXcv.js.map +1 -0
  35. package/docs/CONTRACTS.md +797 -0
  36. package/examples/demo.html +47 -0
  37. package/package.json +10 -5
  38. package/src/contracts/abis/Appreciative.json +1280 -0
  39. package/src/contracts/abis/AppreciativeFactory.json +101 -0
  40. package/src/contracts/abis/Bundle.json +1435 -0
  41. package/src/contracts/abis/BundleFactory.json +106 -0
  42. package/src/contracts/abis/Holon.json +881 -0
  43. package/src/contracts/abis/Holons.json +330 -0
  44. package/src/contracts/abis/Managed.json +1262 -0
  45. package/src/contracts/abis/ManagedFactory.json +149 -0
  46. package/src/contracts/abis/Membrane.json +261 -0
  47. package/src/contracts/abis/Splitter.json +1624 -0
  48. package/src/contracts/abis/SplitterFactory.json +220 -0
  49. package/src/contracts/abis/TestToken.json +321 -0
  50. package/src/contracts/abis/Zoned.json +1461 -0
  51. package/src/contracts/abis/ZonedFactory.json +154 -0
  52. package/src/contracts/chain-manager.js +375 -0
  53. package/src/contracts/deployer.js +443 -0
  54. package/src/contracts/event-listener.js +507 -0
  55. package/src/contracts/holon-contracts.js +344 -0
  56. package/src/contracts/index.js +83 -0
  57. package/src/contracts/networks.js +224 -0
  58. package/src/contracts/operations.js +670 -0
  59. package/src/contracts/queries.js +589 -0
  60. package/src/core/holosphere.js +453 -1
  61. package/src/crypto/nostr-utils.js +263 -0
  62. package/src/federation/handshake.js +455 -0
  63. package/src/federation/hologram.js +1 -1
  64. package/src/hierarchical/upcast.js +6 -5
  65. package/src/index.js +463 -1939
  66. package/src/lib/ai-methods.js +308 -0
  67. package/src/lib/contract-methods.js +293 -0
  68. package/src/lib/errors.js +23 -0
  69. package/src/lib/federation-methods.js +238 -0
  70. package/src/lib/index.js +26 -0
  71. package/src/spatial/h3-operations.js +2 -2
  72. package/src/storage/backends/gundb-backend.js +377 -46
  73. package/src/storage/global-tables.js +28 -1
  74. package/src/storage/gun-auth.js +303 -0
  75. package/src/storage/gun-federation.js +776 -0
  76. package/src/storage/gun-references.js +198 -0
  77. package/src/storage/gun-schema.js +291 -0
  78. package/src/storage/gun-wrapper.js +347 -31
  79. package/src/storage/indexeddb-storage.js +49 -11
  80. package/src/storage/memory-storage.js +5 -0
  81. package/src/storage/nostr-async.js +45 -23
  82. package/src/storage/nostr-client.js +11 -5
  83. package/src/storage/persistent-storage.js +6 -1
  84. package/src/storage/unified-storage.js +119 -0
  85. package/src/subscriptions/manager.js +1 -1
  86. package/types/index.d.ts +133 -0
  87. package/dist/index-CDfIuXew.js +0 -15974
  88. package/dist/index-CDfIuXew.js.map +0 -1
  89. package/dist/index-ifOgtDvd.cjs +0 -3
  90. package/dist/index-ifOgtDvd.cjs.map +0 -1
  91. package/dist/indexeddb-storage-CMW4qRQS.js.map +0 -1
  92. package/dist/indexeddb-storage-DLZOgetM.cjs +0 -2
  93. package/dist/indexeddb-storage-DLZOgetM.cjs.map +0 -1
  94. package/dist/memory-storage-DQzcAZlf.js.map +0 -1
  95. package/dist/memory-storage-DmePEP2q.cjs.map +0 -1
  96. package/dist/secp256k1-CP0ZkpAx.cjs +0 -13
  97. package/dist/secp256k1-CP0ZkpAx.cjs.map +0 -1
  98. package/dist/secp256k1-vOXp40Fx.js.map +0 -1
package/src/index.js CHANGED
@@ -5,18 +5,19 @@
5
5
 
6
6
  import { HoloSphere as HoloSphereCore } from './core/holosphere.js';
7
7
  import * as spatial from './spatial/h3-operations.js';
8
- import * as storage from './storage/nostr-wrapper.js';
8
+ import * as storage from './storage/unified-storage.js';
9
+ import * as nostrStorage from './storage/nostr-wrapper.js';
9
10
  import * as nostrAsync from './storage/nostr-async.js';
10
11
  import * as globalTables from './storage/global-tables.js';
11
12
  import * as schema from './schema/validator.js';
12
13
  import { ValidationError } from './schema/validator.js';
13
14
  import * as federation from './federation/hologram.js';
15
+ import * as handshake from './federation/handshake.js';
14
16
  import * as crypto from './crypto/secp256k1.js';
17
+ import * as nostrUtils from './crypto/nostr-utils.js';
15
18
  import * as social from './content/social-protocols.js';
16
19
  import * as subscriptions from './subscriptions/manager.js';
17
20
  import * as hierarchical from './hierarchical/upcast.js';
18
- import * as registry from './federation/registry.js';
19
- import * as discovery from './federation/discovery.js';
20
21
 
21
22
  // AI Module imports
22
23
  import { LLMService } from './ai/llm-service.js';
@@ -34,13 +35,29 @@ import { RelationshipDiscovery } from './ai/relationships.js';
34
35
  import { TaskBreakdown } from './ai/breakdown.js';
35
36
  import { H3AI } from './ai/h3-ai.js';
36
37
 
38
+ // Contracts Module imports
39
+ import { ChainManager } from './contracts/chain-manager.js';
40
+ import { ContractDeployer, ABIs as ContractABIs } from './contracts/deployer.js';
41
+ import { HolonContracts } from './contracts/holon-contracts.js';
42
+ import { ContractOperations } from './contracts/operations.js';
43
+ import { EventListener, SANKEY_EVENTS } from './contracts/event-listener.js';
44
+ import { ContractQueries } from './contracts/queries.js';
45
+ import * as networks from './contracts/networks.js';
46
+
47
+ // Mixin imports
48
+ import { withAIMethods } from './lib/ai-methods.js';
49
+ import { withContractMethods } from './lib/contract-methods.js';
50
+ import { withFederationMethods } from './lib/federation-methods.js';
51
+ import { AuthorizationError } from './lib/errors.js';
52
+
37
53
  /**
38
- * Main HoloSphere class with all methods
54
+ * Base HoloSphere class with core data operations
39
55
  */
40
- export class HoloSphere extends HoloSphereCore {
56
+ class HoloSphereBase extends HoloSphereCore {
41
57
  constructor(config) {
42
58
  super(config);
43
59
  this.schemas = new Map();
60
+ this._cache = new Map();
44
61
  this.subscriptionRegistry = new subscriptions.SubscriptionRegistry();
45
62
 
46
63
  // Initialize AI services if openaiKey is provided or OPENAI_API_KEY env var is set
@@ -54,12 +71,11 @@ export class HoloSphere extends HoloSphereCore {
54
71
  };
55
72
  this._initializeAI(openaiKey, aiOptions);
56
73
  }
74
+
75
+ // Contracts module (lazy initialized)
76
+ this._contracts = null;
57
77
  }
58
78
 
59
- /**
60
- * Get environment variable (works in Node.js)
61
- * @private
62
- */
63
79
  _getEnv(name) {
64
80
  if (typeof process !== 'undefined' && process.env) {
65
81
  return process.env[name];
@@ -67,39 +83,27 @@ export class HoloSphere extends HoloSphereCore {
67
83
  return undefined;
68
84
  }
69
85
 
70
- /**
71
- * Parse float from string, return undefined if invalid
72
- * @private
73
- */
74
86
  _parseFloat(value) {
75
87
  if (value === undefined || value === null) return undefined;
76
88
  const parsed = parseFloat(value);
77
89
  return isNaN(parsed) ? undefined : parsed;
78
90
  }
79
91
 
80
- /**
81
- * Initialize AI services
82
- * @private
83
- */
84
92
  _initializeAI(apiKey, options = {}) {
85
- // Build LLM options from config
86
93
  const llmOptions = {
87
94
  ...options.llm,
88
95
  model: options.model || options.llm?.model,
89
96
  temperature: options.temperature ?? options.llm?.temperature,
90
97
  };
91
98
 
92
- // Create shared LLM service
93
99
  this._ai = {
94
100
  llm: new LLMService(apiKey, llmOptions),
95
101
  };
96
102
 
97
- // Create OpenAI client for embeddings and TTS
98
103
  const OpenAI = require('openai').default;
99
104
  const openai = new OpenAI({ apiKey });
100
105
  this._ai.openai = openai;
101
106
 
102
- // Create all services
103
107
  this._ai.embeddings = new Embeddings(openai, this);
104
108
  this._ai.schemaExtractor = new SchemaExtractor(this._ai.llm);
105
109
  this._ai.jsonOps = new JSONOps(this._ai.llm);
@@ -115,18 +119,10 @@ export class HoloSphere extends HoloSphereCore {
115
119
  this._ai.h3ai = new H3AI(this._ai.llm, this);
116
120
  }
117
121
 
118
- /**
119
- * Check if AI services are initialized
120
- * @returns {boolean}
121
- */
122
122
  hasAI() {
123
123
  return this._ai !== null;
124
124
  }
125
125
 
126
- /**
127
- * Get AI services object (for advanced usage)
128
- * @returns {Object|null}
129
- */
130
126
  getAIServices() {
131
127
  return this._ai;
132
128
  }
@@ -149,8 +145,8 @@ export class HoloSphere extends HoloSphereCore {
149
145
  }
150
146
 
151
147
  // === Data Operations ===
148
+
152
149
  async write(holonId, lensName, data, options = {}) {
153
- // Validate inputs
154
150
  if (!holonId || typeof holonId !== 'string') {
155
151
  throw new ValidationError('ValidationError: holonId must be a non-empty string');
156
152
  }
@@ -161,60 +157,39 @@ export class HoloSphere extends HoloSphereCore {
161
157
  throw new ValidationError('ValidationError: data must be an object');
162
158
  }
163
159
 
164
- // Check authorization if capability token provided
165
160
  const capToken = options.capabilityToken || options.capability;
166
161
  if (capToken) {
167
- const authorized = await this.verifyCapability(
168
- capToken,
169
- 'write',
170
- { holonId, lensName }
171
- );
162
+ const authorized = await this.verifyCapability(capToken, 'write', { holonId, lensName });
172
163
  if (!authorized) {
173
164
  throw new AuthorizationError('AuthorizationError: Invalid capability token for write operation', 'write');
174
165
  }
175
166
  }
176
167
 
177
- // Auto-generate ID if not provided
178
168
  if (!data.id) {
179
169
  data.id = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
180
170
  }
181
171
 
182
- // Check if we're writing to an existing hologram location
183
172
  const path = storage.buildPath(this.config.appName, holonId, lensName, data.id);
184
173
  const existingData = await storage.read(this.client, path);
185
174
 
186
- // If existing data is a hologram, split the write between local overrides and source
175
+ // Handle hologram writes
187
176
  if (existingData && existingData.hologram === true && existingData.target) {
188
- // Fields that define the hologram structure (never overwrite these)
189
177
  const hologramStructureFields = ['hologram', 'soul', 'target', 'id', '_meta'];
190
- // Fields that exist in the hologram as local overrides
191
178
  const localOverrideFields = Object.keys(existingData).filter(k => !hologramStructureFields.includes(k));
192
179
 
193
- // Split data: local fields stay local, everything else goes to source
194
- const localData = {
195
- ...existingData, // Keep hologram structure
196
- };
180
+ const localData = { ...existingData };
197
181
  const sourceUpdates = {};
198
182
 
199
183
  for (const [key, value] of Object.entries(data)) {
200
- if (hologramStructureFields.includes(key)) {
201
- // Skip - don't overwrite hologram structure
202
- continue;
203
- } else if (key === '_hologram') {
204
- // Skip - this is resolved metadata, not real data
205
- continue;
206
- } else if (localOverrideFields.includes(key)) {
207
- // This field exists in the hologram - update locally
184
+ if (hologramStructureFields.includes(key) || key === '_hologram') continue;
185
+ if (localOverrideFields.includes(key)) {
208
186
  localData[key] = value;
209
187
  } else {
210
- // This field doesn't exist in hologram - update source
211
188
  sourceUpdates[key] = value;
212
189
  }
213
190
  }
214
191
 
215
- // Schema validation for source updates (if schema exists)
216
192
  if (Object.keys(sourceUpdates).length > 0 && options.validate !== false && this.schemas.has(lensName)) {
217
- // Read current source data to merge with updates for validation
218
193
  const sourcePath = storage.buildPath(
219
194
  existingData.target.appname || this.config.appName,
220
195
  existingData.target.holonId,
@@ -232,27 +207,23 @@ export class HoloSphere extends HoloSphereCore {
232
207
  }
233
208
  }
234
209
 
235
- // Update local hologram with its override fields
236
210
  const startTime = Date.now();
237
211
  await storage.write(this.client, path, localData);
238
212
 
239
- // Update source data with non-local fields (partial update, not replace)
240
213
  if (Object.keys(sourceUpdates).length > 0) {
241
- const sourceAppname = existingData.target.appname || this.config.appName;
242
- const sourceHolonId = existingData.target.holonId;
243
- const sourceLensName = existingData.target.lensName;
244
- const sourceDataId = existingData.target.dataId;
245
-
246
- const sourcePath = storage.buildPath(sourceAppname, sourceHolonId, sourceLensName, sourceDataId);
214
+ const sourcePath = storage.buildPath(
215
+ existingData.target.appname || this.config.appName,
216
+ existingData.target.holonId,
217
+ existingData.target.lensName,
218
+ existingData.target.dataId
219
+ );
247
220
  await storage.update(this.client, sourcePath, sourceUpdates);
248
-
249
- // Refresh timestamps on all activeHolograms in source data's _meta
250
221
  await federation.refreshActiveHolograms(
251
222
  this.client,
252
- sourceAppname,
253
- sourceHolonId,
254
- sourceLensName,
255
- sourceDataId
223
+ existingData.target.appname || this.config.appName,
224
+ existingData.target.holonId,
225
+ existingData.target.lensName,
226
+ existingData.target.dataId
256
227
  );
257
228
  }
258
229
 
@@ -264,109 +235,120 @@ export class HoloSphere extends HoloSphereCore {
264
235
  return true;
265
236
  }
266
237
 
267
- // Regular write (not a hologram) - proceed normally
268
- // Add metadata
269
- if (!data._meta) {
270
- data._meta = {};
271
- }
272
- data._meta.timestamp = Date.now();
238
+ // Regular write
239
+ if (!data._meta) data._meta = {};
240
+ data._meta.createdAt = data._meta.createdAt || Date.now();
241
+ data._meta.updatedAt = Date.now();
273
242
 
274
- // Schema validation if exists
275
243
  if (options.validate !== false && this.schemas.has(lensName)) {
276
244
  const schemaObj = this.schemas.get(lensName);
277
245
  const strict = options.strict !== undefined ? options.strict : schemaObj.strict;
278
-
279
- // If schema is a URI reference
280
- if (schemaObj.schema && typeof schemaObj.schema === 'object' && schemaObj.schema.$ref) {
281
- // URI schemas cannot be validated - skip validation in both modes
282
- // In a real implementation, URIs would be resolved first
283
- // For now, we allow writes through (no validation possible)
284
- } else if (schemaObj.schema && typeof schemaObj.schema === 'object') {
285
- // Validate if schema is an actual object
246
+ if (schemaObj.schema && typeof schemaObj.schema === 'object' && !schemaObj.schema.$ref) {
286
247
  schema.validate(data, schemaObj.schema, lensName, strict);
287
248
  }
288
249
  }
289
250
 
290
- // Write to storage
291
251
  const startTime = Date.now();
292
- const success = await storage.write(this.client, path, data);
252
+ await storage.write(this.client, path, data);
293
253
  const endTime = Date.now();
294
254
 
295
- if (success) {
296
- this._metrics.writes++;
297
- // Track average write time
298
- if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
299
- this._metrics.totalWriteTime += (endTime - startTime);
255
+ this._metrics.writes++;
256
+ if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
257
+ this._metrics.totalWriteTime += (endTime - startTime);
300
258
 
301
- // Refresh timestamps on all activeHolograms in the source data's _meta
302
- // This allows interfaces to detect updates automatically
303
- if (data._meta && Array.isArray(data._meta.activeHolograms) && data._meta.activeHolograms.length > 0) {
304
- const result = await federation.refreshActiveHolograms(
305
- this.client,
306
- this.config.appName,
307
- holonId,
308
- lensName,
309
- data.id,
310
- data // Pass the data we already have
311
- );
312
- // Update the written data with refreshed timestamps (already done in-place by refreshActiveHolograms)
313
- if (result.refreshed > 0) {
314
- // Re-write to persist the updated activeHolograms timestamps
315
- await storage.write(this.client, path, result.sourceData);
316
- }
317
- }
259
+ const { autoPropagate = true } = options;
260
+ if (autoPropagate && !data.hologram) {
261
+ await this.propagate(holonId, lensName, data, options.propagationOptions || {});
318
262
  }
319
263
 
320
- return success;
264
+ return true;
321
265
  }
322
266
 
323
- /**
324
- * Resolve holograms recursively
325
- * @private
326
- * @param {*} data - Data that may contain holograms (object, array, or primitive)
327
- * @returns {Promise<*>} Resolved data
328
- */
329
- async _resolveHolograms(data) {
330
- // Handle null/undefined
267
+ async _resolveHolograms(data, visited = new Set()) {
331
268
  if (!data) return data;
332
269
 
333
- // Handle arrays - resolve each item and filter out nulls
334
270
  if (Array.isArray(data)) {
335
271
  const resolved = [];
336
272
  for (const item of data) {
337
- const resolvedItem = await this._resolveHolograms(item);
338
- // Only add non-null items to preserve array integrity
339
- if (resolvedItem != null) {
273
+ const resolvedItem = await this._resolveHolograms(item, new Set());
274
+ if (resolvedItem !== null) {
340
275
  resolved.push(resolvedItem);
341
276
  }
342
277
  }
343
278
  return resolved;
344
279
  }
345
280
 
346
- // Handle objects
347
- if (typeof data === 'object') {
348
- // Check if this is a hologram - ONLY if explicitly marked with hologram:true
349
- // Having a 'soul' property alone does NOT make it a hologram (soul is used for metadata/linking)
350
- const isHologram = data.hologram === true;
281
+ if (data && typeof data === 'object') {
282
+ if (data.hologram === true && data.target) {
283
+ const sourcePath = storage.buildPath(
284
+ data.target.appname || this.config.appName,
285
+ data.target.holonId,
286
+ data.target.lensName,
287
+ data.target.dataId
288
+ );
351
289
 
352
- if (isHologram) {
353
- const resolved = await federation.resolveHologram(this.client, data);
354
- // Recursively resolve the resolved data (in case it contains more holograms)
355
- return resolved ? await this._resolveHolograms(resolved) : null;
356
- }
290
+ // Circular reference detection
291
+ if (visited.has(sourcePath)) {
292
+ console.warn(`Circular hologram reference detected: ${sourcePath}`);
293
+ return null;
294
+ }
295
+ visited.add(sourcePath);
357
296
 
358
- // Not a hologram, but might contain holograms in nested properties
359
- // For now, just return as-is to avoid deep traversal overhead
360
- // If needed, we can add deep resolution later
361
- return data;
362
- }
297
+ let resolveOptions = {};
298
+ if (data.target.authorPubKey) {
299
+ resolveOptions.authors = [data.target.authorPubKey];
300
+ resolveOptions.includeAuthor = true;
301
+ }
302
+
303
+ const sourceData = await storage.read(this.client, sourcePath, resolveOptions);
304
+ if (sourceData) {
305
+ // If source is also a hologram, recursively resolve it
306
+ let resolvedSource = sourceData;
307
+ if (sourceData.hologram === true && sourceData.target) {
308
+ resolvedSource = await this._resolveHolograms(sourceData, visited);
309
+ if (resolvedSource === null) {
310
+ return null; // Circular reference or unresolvable
311
+ }
312
+ }
363
313
 
364
- // Primitive value
314
+ // Get local override fields from the hologram (excluding hologram structure fields)
315
+ const hologramStructureFields = ['hologram', 'soul', 'target', '_meta', 'id', 'capability', 'crossHolosphere'];
316
+ const localOverrides = {};
317
+ for (const [k, v] of Object.entries(data)) {
318
+ if (!hologramStructureFields.includes(k)) {
319
+ localOverrides[k] = v;
320
+ }
321
+ }
322
+
323
+ const merged = {
324
+ ...resolvedSource,
325
+ ...localOverrides,
326
+ _hologram: {
327
+ isHologram: true,
328
+ soul: data.soul,
329
+ sourceHolon: data.target.holonId,
330
+ source: data.target,
331
+ localOverrides: Object.keys(localOverrides),
332
+ crossHolosphere: data.crossHolosphere || false,
333
+ }
334
+ };
335
+
336
+ // Preserve source _meta but add hologram source info
337
+ if (resolvedSource._meta) {
338
+ merged._meta = { ...resolvedSource._meta, source: data.target.holonId };
339
+ } else {
340
+ merged._meta = { source: data.target.holonId };
341
+ }
342
+
343
+ return merged;
344
+ }
345
+ return null; // Source not found
346
+ }
347
+ }
365
348
  return data;
366
349
  }
367
350
 
368
351
  async read(holonId, lensName, dataId = null, options = {}) {
369
- // Validate inputs
370
352
  if (!holonId || typeof holonId !== 'string') {
371
353
  throw new ValidationError('ValidationError: holonId must be a non-empty string');
372
354
  }
@@ -374,227 +356,150 @@ export class HoloSphere extends HoloSphereCore {
374
356
  throw new ValidationError('ValidationError: lensName must be a non-empty string');
375
357
  }
376
358
 
377
- const startTime = Date.now();
378
- this._metrics.reads++;
379
-
380
- // Build the scope for capability lookup
381
- const scope = { holonId, lensName };
382
- if (dataId) scope.dataId = dataId;
383
-
384
- // Get federated authors (partners who granted us read access)
385
- // Default: federated=true unless explicitly disabled
386
- const federated = options.federated !== false;
387
- let authors = [this.client.publicKey]; // Always include our own data
388
-
389
- if (federated) {
390
- try {
391
- const federatedAuthors = await registry.getFederatedAuthorsForScope(
392
- this.client,
393
- this.config.appName,
394
- scope,
395
- 'read'
396
- );
397
- // Add federated author pubKeys
398
- for (const { pubKey } of federatedAuthors) {
399
- if (!authors.includes(pubKey)) {
400
- authors.push(pubKey);
401
- }
402
- }
403
- } catch (error) {
404
- // Silently continue with just our own data if federation lookup fails
405
- console.debug('[read] Federation lookup failed, using local only:', error.message);
359
+ const capToken = options.capabilityToken || options.capability;
360
+ if (capToken) {
361
+ const authorized = await this.verifyCapability(capToken, 'read', { holonId, lensName, dataId });
362
+ if (!authorized) {
363
+ throw new AuthorizationError('AuthorizationError: Invalid capability token for read operation', 'read');
406
364
  }
407
365
  }
408
366
 
409
- // Query options including federated authors
410
- const queryOptions = {
411
- authors,
412
- includeAuthor: true, // Track which author each item came from
413
- };
367
+ const startTime = Date.now();
368
+ let result;
414
369
 
415
370
  if (dataId) {
416
371
  const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
417
- const data = await storage.read(this.client, path, queryOptions);
418
-
419
- // Filter out deleted items
420
- if (data && data._deleted) {
421
- const endTime = Date.now();
422
- if (!this._metrics.totalReadTime) this._metrics.totalReadTime = 0;
423
- this._metrics.totalReadTime += (endTime - startTime);
424
- return null;
425
- }
426
-
427
- // Always resolve holograms
428
- const resolved = await this._resolveHolograms(data);
429
-
430
- const endTime = Date.now();
431
- if (!this._metrics.totalReadTime) this._metrics.totalReadTime = 0;
432
- this._metrics.totalReadTime += (endTime - startTime);
433
- return resolved;
372
+ result = await storage.read(this.client, path);
434
373
  } else {
435
374
  const path = storage.buildPath(this.config.appName, holonId, lensName);
436
- const result = await storage.readAll(this.client, path, {
437
- ...queryOptions,
438
- hybrid: this.config.hybridMode
439
- });
375
+ result = await storage.readAll(this.client, path);
376
+ }
440
377
 
441
- // Always resolve holograms in array results
442
- const resolved = await this._resolveHolograms(result);
378
+ const { resolveHolograms = true } = options;
379
+ if (resolveHolograms) {
380
+ result = await this._resolveHolograms(result);
381
+ }
443
382
 
444
- // Filter out deleted items
445
- const filtered = Array.isArray(resolved)
446
- ? resolved.filter(item => !item || !item._deleted)
447
- : (resolved && resolved._deleted ? null : resolved);
383
+ const endTime = Date.now();
384
+ this._metrics.reads++;
385
+ if (!this._metrics.totalReadTime) this._metrics.totalReadTime = 0;
386
+ this._metrics.totalReadTime += (endTime - startTime);
448
387
 
449
- const endTime = Date.now();
450
- if (!this._metrics.totalReadTime) this._metrics.totalReadTime = 0;
451
- this._metrics.totalReadTime += (endTime - startTime);
452
- return filtered;
453
- }
388
+ return result;
454
389
  }
455
390
 
456
391
  async update(holonId, lensName, dataId, updates, options = {}) {
457
- // Check authorization if capability token provided
392
+ if (!holonId || typeof holonId !== 'string') {
393
+ throw new ValidationError('ValidationError: holonId must be a non-empty string');
394
+ }
395
+ if (!lensName || typeof lensName !== 'string') {
396
+ throw new ValidationError('ValidationError: lensName must be a non-empty string');
397
+ }
398
+ if (!dataId || typeof dataId !== 'string') {
399
+ throw new ValidationError('ValidationError: dataId must be a non-empty string');
400
+ }
401
+ if (!updates || typeof updates !== 'object') {
402
+ throw new ValidationError('ValidationError: updates must be an object');
403
+ }
404
+
458
405
  const capToken = options.capabilityToken || options.capability;
459
406
  if (capToken) {
460
- const authorized = await this.verifyCapability(
461
- capToken,
462
- 'write',
463
- { holonId, lensName }
464
- );
407
+ const authorized = await this.verifyCapability(capToken, 'write', { holonId, lensName, dataId });
465
408
  if (!authorized) {
466
409
  throw new AuthorizationError('AuthorizationError: Invalid capability token for update operation', 'write');
467
410
  }
468
411
  }
469
412
 
470
- // Read raw data (without resolving) to check if it's a hologram
471
413
  const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
472
- const rawData = await storage.read(this.client, path);
473
-
474
- // If this is a hologram, split updates between local overrides and source
475
- if (rawData && rawData.hologram === true && rawData.target) {
476
- // Fields that are stored in the hologram itself (local overrides)
477
- // These are fields that exist in the hologram beyond the core hologram structure
478
- const hologramStructureFields = ['hologram', 'soul', 'target', 'id', '_meta'];
479
- const localOverrideFields = Object.keys(rawData).filter(k => !hologramStructureFields.includes(k));
480
-
481
- // Split updates: local fields stay local, everything else goes to source
482
- const localUpdates = {};
483
- const sourceUpdates = {};
484
-
485
- for (const [key, value] of Object.entries(updates)) {
486
- if (hologramStructureFields.includes(key)) {
487
- // Skip hologram structure fields entirely
488
- continue;
489
- } else if (localOverrideFields.includes(key)) {
490
- // This field exists in the hologram - update locally
491
- localUpdates[key] = value;
492
- } else {
493
- // This field doesn't exist in hologram - update source
494
- sourceUpdates[key] = value;
495
- }
496
- }
497
-
498
- // Update local hologram overrides
499
- if (Object.keys(localUpdates).length > 0) {
500
- await storage.update(this.client, path, localUpdates);
501
- }
414
+ const existingData = await storage.read(this.client, path);
502
415
 
503
- // Update source data
504
- if (Object.keys(sourceUpdates).length > 0) {
505
- const sourcePath = storage.buildPath(
506
- rawData.target.appname || this.config.appName,
507
- rawData.target.holonId,
508
- rawData.target.lensName,
509
- rawData.target.dataId
510
- );
511
- await storage.update(this.client, sourcePath, sourceUpdates);
512
- }
416
+ if (!existingData) {
417
+ return false;
418
+ }
513
419
 
514
- return true;
420
+ // Handle hologram updates
421
+ if (existingData.hologram === true && existingData.target) {
422
+ return this.write(holonId, lensName, { ...existingData, ...updates, id: dataId }, options);
515
423
  }
516
424
 
517
- // Regular data (not a hologram) - proceed with normal update
518
- // Schema validation if exists and updates data
425
+ const mergedData = { ...existingData, ...updates };
426
+ mergedData._meta = mergedData._meta || {};
427
+ mergedData._meta.updatedAt = Date.now();
428
+
519
429
  if (options.validate !== false && this.schemas.has(lensName)) {
520
- const existing = await this.read(holonId, lensName, dataId);
521
- const merged = { ...existing, ...updates };
522
430
  const schemaObj = this.schemas.get(lensName);
523
431
  const strict = options.strict !== undefined ? options.strict : schemaObj.strict;
524
-
525
- // If schema is a URI reference
526
- if (schemaObj.schema && typeof schemaObj.schema === 'object' && schemaObj.schema.$ref) {
527
- // URI schemas cannot be validated - skip validation in both modes
528
- // In a real implementation, URIs would be resolved first
529
- // For now, we allow updates through (no validation possible)
530
- } else if (schemaObj.schema && typeof schemaObj.schema === 'object') {
531
- schema.validate(merged, schemaObj.schema, lensName, strict);
432
+ if (schemaObj.schema && typeof schemaObj.schema === 'object' && !schemaObj.schema.$ref) {
433
+ schema.validate(mergedData, schemaObj.schema, lensName, strict);
532
434
  }
533
435
  }
534
436
 
535
- return storage.update(this.client, path, updates);
437
+ const startTime = Date.now();
438
+ await storage.write(this.client, path, mergedData);
439
+ const endTime = Date.now();
440
+
441
+ this._metrics.writes++;
442
+ if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
443
+ this._metrics.totalWriteTime += (endTime - startTime);
444
+
445
+ if (existingData._meta && existingData._meta.activeHolograms) {
446
+ await federation.refreshActiveHolograms(this.client, this.config.appName, holonId, lensName, dataId);
447
+ }
448
+
449
+ return true;
536
450
  }
537
451
 
538
452
  async delete(holonId, lensName, dataId, options = {}) {
539
- // Read existing data to check ownership and get activeHolograms
540
- const existing = await this.read(holonId, lensName, dataId);
453
+ if (!holonId || typeof holonId !== 'string') {
454
+ throw new ValidationError('ValidationError: holonId must be a non-empty string');
455
+ }
456
+ if (!lensName || typeof lensName !== 'string') {
457
+ throw new ValidationError('ValidationError: lensName must be a non-empty string');
458
+ }
459
+ if (!dataId || typeof dataId !== 'string') {
460
+ throw new ValidationError('ValidationError: dataId must be a non-empty string');
461
+ }
462
+
463
+ const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
464
+ const existingData = await storage.read(this.client, path);
541
465
 
542
- // If data doesn't exist, return false
543
- if (!existing) {
466
+ // Return false if data doesn't exist
467
+ if (!existingData) {
544
468
  return false;
545
469
  }
546
470
 
547
- // Check authorization
548
- const capToken = options.capabilityToken || options.capability;
549
- const creator = existing._creator || existing.owner;
550
- const currentUser = this.client.publicKey;
551
-
552
- // Authorization logic:
553
- // 1. If capability token provided, verify it
554
- // 2. Else if data has a creator/owner AND it's not the current user, deny
555
- // 3. Else allow (no owner or current user is owner)
471
+ // Check authorization: owner can delete, others need capability token
472
+ const dataOwner = existingData.owner || existingData._creator;
473
+ const isOwner = !dataOwner || dataOwner === this.client.publicKey;
556
474
 
557
- if (capToken) {
558
- const authorized = await this.verifyCapability(
559
- capToken,
560
- 'delete',
561
- { holonId, lensName }
562
- );
475
+ const capToken = options.capabilityToken || options.capability;
476
+ if (!isOwner) {
477
+ // Non-owner must provide a valid capability token
478
+ if (!capToken) {
479
+ throw new AuthorizationError('AuthorizationError: Capability token required for delete operation', 'delete');
480
+ }
481
+ const authorized = await this.verifyCapability(capToken, 'delete', { holonId, lensName, dataId });
563
482
  if (!authorized) {
564
- const error = new AuthorizationError('AuthorizationError: Invalid capability token for delete operation', 'delete');
565
- throw error;
483
+ throw new AuthorizationError('AuthorizationError: Invalid capability token for delete operation', 'delete');
566
484
  }
567
- } else if (creator && creator !== currentUser) {
568
- // Data has an owner and it's not the current user - require authorization
569
- const error = new AuthorizationError('AuthorizationError: Delete operation requires capability token', 'delete');
570
- throw error;
571
- }
572
- // else: no owner or current user is owner - allow deletion
573
-
574
- // Delete all holograms that point to this data
575
- if (existing._meta?.activeHolograms && Array.isArray(existing._meta.activeHolograms)) {
576
- console.info(`🗑️ Deleting ${existing._meta.activeHolograms.length} holograms for ${holonId}/${lensName}/${dataId}`);
577
- for (const hologramEntry of existing._meta.activeHolograms) {
578
- try {
579
- const hologramPath = storage.buildPath(
580
- this.config.appName,
581
- hologramEntry.targetHolon,
582
- lensName,
583
- dataId
584
- );
585
- await storage.deleteData(this.client, hologramPath);
586
- console.info(` ✓ Deleted hologram in ${hologramEntry.targetHolon}`);
587
- } catch (err) {
588
- console.warn(` ⚠️ Failed to delete hologram in ${hologramEntry.targetHolon}:`, err.message);
589
- }
485
+ } else if (capToken) {
486
+ // Owner provided a token - validate it anyway
487
+ const authorized = await this.verifyCapability(capToken, 'delete', { holonId, lensName, dataId });
488
+ if (!authorized) {
489
+ throw new AuthorizationError('AuthorizationError: Invalid capability token for delete operation', 'delete');
590
490
  }
591
491
  }
592
492
 
593
- const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
594
- return storage.deleteData(this.client, path);
493
+ if (existingData.hologram === true) {
494
+ return this.deleteHologram(holonId, lensName, dataId, options);
495
+ }
496
+
497
+ await storage.deleteData(this.client, path);
498
+ this._metrics.deletes++;
499
+ return true;
595
500
  }
596
501
 
597
- // === Global Data Operations ===
502
+ // === Global Tables ===
598
503
  async writeGlobal(table, data) {
599
504
  return globalTables.writeGlobal(this.client, this.config.appName, table, data);
600
505
  }
@@ -612,16 +517,16 @@ export class HoloSphere extends HoloSphereCore {
612
517
  }
613
518
 
614
519
  async getAllGlobal(table) {
615
- return globalTables.readGlobal(this.client, this.config.appName, table);
520
+ return globalTables.getAllGlobal(this.client, this.config.appName, table);
616
521
  }
617
522
 
618
523
  async deleteAllGlobal(table) {
619
524
  return globalTables.deleteAllGlobal(this.client, this.config.appName, table);
620
525
  }
621
526
 
622
- // === Holon Data Operations ===
527
+ // === Batch Operations ===
623
528
  async getAll(holonId, lensName) {
624
- return this.read(holonId, lensName);
529
+ return this.read(holonId, lensName, null);
625
530
  }
626
531
 
627
532
  async deleteAll(holonId, lensName) {
@@ -630,27 +535,41 @@ export class HoloSphere extends HoloSphereCore {
630
535
  }
631
536
 
632
537
  // === Schema Operations ===
633
- async setSchema(lensName, schemaObj, strict = false) {
634
- let resolvedSchema = schemaObj;
635
-
636
- // If schemaObj is a string (URI), store as reference
637
- if (typeof schemaObj === 'string') {
638
- // Store as URI reference - validation will happen when used
639
- resolvedSchema = { $ref: schemaObj };
640
- }
641
-
642
- if (!resolvedSchema || (typeof resolvedSchema !== 'object' && typeof resolvedSchema !== 'boolean')) {
643
- throw new ValidationError('Schema must be a valid JSON Schema object or boolean');
538
+ async setSchema(lensName, schemaInput, strict = false) {
539
+ if (!lensName || typeof lensName !== 'string') {
540
+ throw new ValidationError('ValidationError: lensName must be a non-empty string');
644
541
  }
645
542
 
646
- // Validate that it's a valid JSON Schema (basic check) - only for actual schemas, not refs
647
- if (typeof resolvedSchema === 'object' && !resolvedSchema.$ref) {
648
- if (!resolvedSchema.type && !resolvedSchema.properties && !resolvedSchema.items && resolvedSchema !== true && resolvedSchema !== false) {
649
- throw new ValidationError('ValidationError: Invalid JSON Schema format - must have type, properties, or items');
543
+ // Handle null/undefined
544
+ if (schemaInput == null) {
545
+ throw new ValidationError('ValidationError: schema cannot be null or undefined');
546
+ }
547
+
548
+ let schemaObj;
549
+
550
+ // Handle URI string - store as $ref
551
+ if (typeof schemaInput === 'string') {
552
+ schemaObj = { $ref: schemaInput };
553
+ } else if (typeof schemaInput === 'object') {
554
+ // Validate it looks like a JSON Schema (must have type or $ref or properties or items)
555
+ const isValidSchema = schemaInput.$ref ||
556
+ schemaInput.type ||
557
+ schemaInput.properties ||
558
+ schemaInput.items ||
559
+ schemaInput.allOf ||
560
+ schemaInput.anyOf ||
561
+ schemaInput.oneOf ||
562
+ schemaInput.$schema;
563
+
564
+ if (!isValidSchema) {
565
+ throw new ValidationError('ValidationError: Invalid JSON Schema format');
650
566
  }
567
+ schemaObj = schemaInput;
568
+ } else {
569
+ throw new ValidationError('ValidationError: schema must be an object or URI string');
651
570
  }
652
571
 
653
- this.schemas.set(lensName, { schema: resolvedSchema, strict });
572
+ this.schemas.set(lensName, { schema: schemaObj, strict, timestamp: Date.now() });
654
573
  }
655
574
 
656
575
  async getSchema(lensName) {
@@ -660,30 +579,23 @@ export class HoloSphere extends HoloSphereCore {
660
579
 
661
580
  async clearSchema(lensName) {
662
581
  this.schemas.delete(lensName);
663
- schema.clearSchemaCache(lensName);
582
+ // Returns undefined for contract compliance
664
583
  }
665
584
 
666
585
  // === Federation Operations ===
667
586
  async federate(sourceHolon, targetHolon, lensName, options = {}) {
668
- // Validate self-federation
587
+ const { direction = 'outbound', mode = 'reference', filter = null } = options;
588
+
589
+ // Validation
669
590
  if (sourceHolon === targetHolon) {
670
591
  throw new Error('Cannot federate a holon with itself');
671
592
  }
672
-
673
- // Validate direction (if provided)
674
- if (options.direction && !['inbound', 'outbound', 'bidirectional'].includes(options.direction)) {
675
- throw new Error('Invalid federation direction - must be inbound, outbound, or bidirectional');
676
- }
677
-
678
- // Handle bidirectional as both inbound and outbound
679
- if (options.direction === 'bidirectional') {
680
- const { direction, ...restOptions } = options;
681
- const outboundResult = await this.federate(sourceHolon, targetHolon, lensName, { ...restOptions, direction: 'outbound' });
682
- const inboundResult = await this.federate(sourceHolon, targetHolon, lensName, { ...restOptions, direction: 'inbound' });
683
- return outboundResult && inboundResult;
593
+ if (!['inbound', 'outbound', 'bidirectional'].includes(direction)) {
594
+ throw new Error(`Invalid direction: ${direction}. Must be 'inbound', 'outbound', or 'bidirectional'`);
684
595
  }
685
596
 
686
- const result = await federation.setupFederation(
597
+ // Store federation config
598
+ await federation.setupFederation(
687
599
  this.client,
688
600
  this.config.appName,
689
601
  sourceHolon,
@@ -692,134 +604,63 @@ export class HoloSphere extends HoloSphereCore {
692
604
  options
693
605
  );
694
606
 
695
- if (result) {
696
- if (!this._metrics.federations) this._metrics.federations = 0;
697
- this._metrics.federations++;
698
-
699
- // By default, propagate existing data when federation is set up
700
- // Set propagateExisting: false to only federate future writes
701
- const propagateExisting = options.propagateExisting !== false;
607
+ // Actually propagate existing data based on direction
608
+ if (direction === 'outbound' || direction === 'bidirectional') {
609
+ await this._propagateExistingData(sourceHolon, targetHolon, lensName, { mode, filter });
610
+ }
611
+ if (direction === 'inbound' || direction === 'bidirectional') {
612
+ await this._propagateExistingData(targetHolon, sourceHolon, lensName, { mode, filter });
613
+ }
702
614
 
703
- if (!propagateExisting) {
704
- return result;
705
- }
615
+ this._metrics.federations = (this._metrics.federations || 0) + 1;
616
+ return true;
617
+ }
706
618
 
707
- // Propagate existing data based on direction
708
- // Default to 'outbound' (source -> target) if no direction specified
709
- const direction = options.direction || 'outbound';
710
- const mode = options.mode || 'reference';
711
- const filter = options.filter;
712
-
713
- if (direction === 'outbound') {
714
- // Source -> Target: propagate data from source to target
715
- // Check if receiver accepts this lens in their inbound
716
- const targetFederation = await this.getFederation(targetHolon);
717
- const receiverLensConfig = targetFederation?.lensConfig?.[sourceHolon];
718
- if (receiverLensConfig && receiverLensConfig.inbound && !receiverLensConfig.inbound.includes(lensName)) {
719
- console.log(`Skipping outbound propagation - ${targetHolon} doesn't accept lens '${lensName}' in inbound from ${sourceHolon}`);
720
- } else {
721
- // Read raw data to avoid resolving holograms
722
- const path = storage.buildPath(this.config.appName, sourceHolon, lensName);
723
- const sourceData = await storage.readAll(this.client, path);
724
- const sourceArray = Array.isArray(sourceData) ? sourceData : (sourceData ? [sourceData] : []);
725
-
726
- for (const item of sourceArray) {
727
- if (item.id === '_federation') continue; // Skip federation config
728
- // Skip items that are already holograms or from another source
729
- if (item.hologram === true) continue;
730
- if (item._meta && item._meta.source && item._meta.source !== sourceHolon) continue;
731
- if (filter && !filter(item)) continue; // Apply filter if provided
732
-
733
- await federation.propagateData(
734
- this.client,
735
- this.config.appName,
736
- item,
737
- sourceHolon,
738
- targetHolon,
739
- lensName,
740
- mode
741
- );
742
- }
743
- }
744
- }
619
+ async _propagateExistingData(fromHolon, toHolon, lensName, options = {}) {
620
+ const { mode = 'reference', filter = null } = options;
621
+ const existingData = await this.read(fromHolon, lensName, null, { resolveHolograms: false });
622
+ if (!existingData) return;
745
623
 
746
- if (direction === 'inbound') {
747
- // Target -> Source: propagate data from target to source
748
- // Check if receiver (source) accepts this lens in their inbound
749
- const sourceFederation = await this.getFederation(sourceHolon);
750
- const receiverLensConfig = sourceFederation?.lensConfig?.[targetHolon];
751
- if (receiverLensConfig && receiverLensConfig.inbound && !receiverLensConfig.inbound.includes(lensName)) {
752
- console.log(`Skipping inbound propagation - ${sourceHolon} doesn't accept lens '${lensName}' in inbound from ${targetHolon}`);
753
- } else {
754
- // Read raw data to avoid resolving holograms
755
- const path = storage.buildPath(this.config.appName, targetHolon, lensName);
756
- const targetData = await storage.readAll(this.client, path);
757
- const targetArray = Array.isArray(targetData) ? targetData : (targetData ? [targetData] : []);
758
-
759
- for (const item of targetArray) {
760
- if (item.id === '_federation') continue; // Skip federation config
761
- // Skip items that are already holograms or from another source
762
- if (item.hologram === true) continue;
763
- if (item._meta && item._meta.source && item._meta.source !== targetHolon) continue;
764
- if (filter && !filter(item)) continue; // Apply filter if provided
765
-
766
- await federation.propagateData(
767
- this.client,
768
- this.config.appName,
769
- item,
770
- targetHolon,
771
- sourceHolon,
772
- lensName,
773
- mode
774
- );
775
- }
776
- }
777
- }
624
+ const items = Array.isArray(existingData) ? existingData : [existingData];
625
+ for (const item of items) {
626
+ // Skip holograms to avoid circular propagation
627
+ if (item.hologram === true) continue;
628
+ // Apply filter if provided
629
+ if (filter && !filter(item)) continue;
630
+ await federation.propagateData(
631
+ this.client,
632
+ this.config.appName,
633
+ item,
634
+ fromHolon,
635
+ toHolon,
636
+ lensName,
637
+ mode
638
+ );
778
639
  }
779
-
780
- return result;
781
640
  }
782
641
 
783
642
  async getFederatedData(holonId, lensName, options = {}) {
784
643
  const { resolveHolograms = true } = options;
644
+ const path = storage.buildPath(this.config.appName, holonId, lensName);
645
+ const data = await storage.readAll(this.client, path);
785
646
 
786
- // For getFederatedData, we just return the local data
787
- // The federation setup already propagates data (as holograms or copies)
788
- // So all federated data should already be accessible locally
789
-
790
- if (resolveHolograms) {
791
- // Use normal read which auto-resolves holograms
792
- const localData = await this.read(holonId, lensName);
793
- const localArray = Array.isArray(localData) ? localData : (localData ? [localData] : []);
794
-
795
- // Filter out federation config and nulls
796
- return localArray.filter(item => item != null && item.id !== '_federation');
797
- } else {
798
- // Read without resolving holograms - need to read raw data
799
- const path = storage.buildPath(this.config.appName, holonId, lensName);
800
- const rawData = await storage.readAll(this.client, path);
801
- const rawArray = Array.isArray(rawData) ? rawData : (rawData ? [rawData] : []);
647
+ if (!data || !resolveHolograms) return data;
802
648
 
803
- // Filter out federation config
804
- return rawArray.filter(item => item != null && item.id !== '_federation');
805
- }
649
+ // Resolve holograms using existing method
650
+ return this._resolveHolograms(data);
806
651
  }
807
652
 
808
653
  async unfederate(sourceHolon, targetHolon, lensName) {
809
- // Delete federation config (idempotent - always returns true)
810
- const path = storage.buildPath(this.config.appName, sourceHolon, lensName, '_federation');
811
- await storage.deleteData(this.client, path);
654
+ // Remove federation config for this relationship - idempotent
655
+ const configPath = storage.buildPath(this.config.appName, sourceHolon, lensName, '_federation');
656
+ try {
657
+ await storage.deleteData(this.client, configPath);
658
+ } catch (e) {
659
+ // Ignore errors - already unfederated or doesn't exist
660
+ }
812
661
  return true;
813
662
  }
814
663
 
815
- /**
816
- * Update local overrides on a hologram (e.g., position, pinned status)
817
- * @param {string} holonId - Holon where the hologram lives
818
- * @param {string} lensName - Lens name (e.g., 'quests')
819
- * @param {string} dataId - Data ID of the hologram
820
- * @param {Object} overrides - Local fields to update (e.g., { x: 100, y: 200 })
821
- * @returns {Promise<boolean>} Success indicator
822
- */
823
664
  async updateHologramOverrides(holonId, lensName, dataId, overrides) {
824
665
  return federation.updateHologramOverrides(
825
666
  this.client,
@@ -831,18 +672,7 @@ export class HoloSphere extends HoloSphereCore {
831
672
  );
832
673
  }
833
674
 
834
- /**
835
- * Create a hologram (lightweight reference) for federation
836
- * @param {string} sourceHolon - Source holon ID where the original data lives
837
- * @param {string} lensName - Lens name (e.g., 'quests')
838
- * @param {Object} data - Data object (must have an 'id' property)
839
- * @param {string} [targetHolon] - Optional target holon ID (defaults to sourceHolon)
840
- * @returns {Object} Hologram object ready to be written to a holon
841
- */
842
675
  createHologram(sourceHolon, lensName, data, targetHolon = null) {
843
- if (!data || !data.id) {
844
- throw new Error('createHologram: data must have an id property');
845
- }
846
676
  return federation.createHologram(
847
677
  sourceHolon,
848
678
  targetHolon || sourceHolon,
@@ -852,31 +682,16 @@ export class HoloSphere extends HoloSphereCore {
852
682
  );
853
683
  }
854
684
 
855
- /**
856
- * Get active holograms for a specific data item
857
- * Returns the activeHolograms array from the source data's _meta
858
- * @param {string} holonId - Source holon ID
859
- * @param {string} lensName - Lens name (e.g., 'quests')
860
- * @param {string} dataId - Data ID to get holograms for
861
- * @returns {Promise<Object[]>} Array of hologram info objects with timestamps
862
- */
863
685
  async getActiveHolograms(holonId, lensName, dataId) {
864
- const data = await this.read(holonId, lensName, dataId);
865
- if (!data || !data._meta || !Array.isArray(data._meta.activeHolograms)) {
686
+ // Read the source data and return its activeHolograms list
687
+ const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
688
+ const sourceData = await storage.read(this.client, path);
689
+ if (!sourceData || !sourceData._meta || !sourceData._meta.activeHolograms) {
866
690
  return [];
867
691
  }
868
- return data._meta.activeHolograms;
869
- }
870
-
871
- /**
872
- * Remove a hologram reference from source data's _meta.activeHolograms
873
- * Call this when a hologram is deleted
874
- * @param {string} sourceHolon - Source holon ID
875
- * @param {string} lensName - Lens name
876
- * @param {string} dataId - Data ID
877
- * @param {string} targetHolon - Target holon where hologram lives
878
- * @returns {Promise<boolean>} Success indicator
879
- */
692
+ return sourceData._meta.activeHolograms;
693
+ }
694
+
880
695
  async removeActiveHologram(sourceHolon, lensName, dataId, targetHolon) {
881
696
  return federation.removeActiveHologram(
882
697
  this.client,
@@ -888,14 +703,6 @@ export class HoloSphere extends HoloSphereCore {
888
703
  );
889
704
  }
890
705
 
891
- /**
892
- * Delete a hologram and clean up activeHolograms on the original source
893
- * @param {string} holonId - Holon where the hologram lives
894
- * @param {string} lensName - Lens name
895
- * @param {string} dataId - Data ID of the hologram
896
- * @param {Object} options - Optional settings
897
- * @returns {Promise<Object>} Result with deletion info
898
- */
899
706
  async deleteHologram(holonId, lensName, dataId, options = {}) {
900
707
  return federation.deleteHologram(
901
708
  this.client,
@@ -907,19 +714,9 @@ export class HoloSphere extends HoloSphereCore {
907
714
  );
908
715
  }
909
716
 
910
- /**
911
- * Propagate data to a specific target holon (creates hologram or copy)
912
- * Use this for direct propagation; use propagate() for federation-based propagation
913
- * @param {Object} data - Data to propagate
914
- * @param {string} sourceHolon - Source holon ID
915
- * @param {string} targetHolon - Target holon ID
916
- * @param {string} lensName - Lens name
917
- * @param {Object} options - Options
918
- * @param {string} options.mode - 'reference' (creates hologram) or 'copy' (copies data)
919
- * @returns {Promise<boolean>} Success indicator
920
- */
921
717
  async propagateData(data, sourceHolon, targetHolon, lensName, options = {}) {
922
- const { mode = 'reference' } = options;
718
+ // Extract mode from options, default to 'reference' for hologram creation
719
+ const mode = options.mode || 'reference';
923
720
  return federation.propagateData(
924
721
  this.client,
925
722
  this.config.appName,
@@ -931,51 +728,22 @@ export class HoloSphere extends HoloSphereCore {
931
728
  );
932
729
  }
933
730
 
934
- /**
935
- * Resolve a hologram to its actual data, merging local overrides
936
- * @param {Object} hologram - Hologram object to resolve
937
- * @returns {Promise<Object|null>} Resolved data or null
938
- */
939
- async resolveHologram(hologram) {
940
- return federation.resolveHologram(this.client, hologram);
731
+ async resolveHologram(hologram, options = {}) {
732
+ return federation.resolveHologram(this.client, hologram, new Set(), [], { ...options, appname: this.config.appName });
941
733
  }
942
734
 
943
- /**
944
- * Check if data is a hologram (unresolved reference)
945
- * @param {Object} data - Data to check
946
- * @returns {boolean} True if data is a hologram
947
- */
948
735
  isHologram(data) {
949
736
  return federation.isHologram(data);
950
737
  }
951
738
 
952
- /**
953
- * Check if data was resolved from a hologram
954
- * @param {Object} data - Data to check
955
- * @returns {boolean} True if data was resolved from a hologram
956
- */
957
739
  isResolvedHologram(data) {
958
740
  return federation.isResolvedHologram(data);
959
741
  }
960
742
 
961
- /**
962
- * Get hologram source information from resolved data
963
- * @param {Object} data - Resolved data
964
- * @returns {Object|null} Source info { soul, sourceHolon, localOverrides } or null
965
- */
966
743
  getHologramSource(data) {
967
744
  return federation.getHologramSource(data);
968
745
  }
969
746
 
970
- /**
971
- * Cleanup circular holograms in a holon's lens
972
- * Detects and removes holograms that create circular references (A→B→A)
973
- * @param {string} holonId - Holon to clean up
974
- * @param {string} lensName - Lens name to check
975
- * @param {Object} options - Optional settings
976
- * @param {boolean} options.dryRun - If true, only report what would be deleted
977
- * @returns {Promise<Object>} Result with cleanup info
978
- */
979
747
  async cleanupCircularHolograms(holonId, lensName, options = {}) {
980
748
  return federation.cleanupCircularHolograms(
981
749
  this.client,
@@ -986,16 +754,6 @@ export class HoloSphere extends HoloSphereCore {
986
754
  );
987
755
  }
988
756
 
989
- /**
990
- * Cleanup circular holograms for a specific list of IDs
991
- * Use this when you don't have an index but know which IDs to check
992
- * @param {string} holonId - Holon to clean up
993
- * @param {string} lensName - Lens name
994
- * @param {string[]} dataIds - Array of data IDs to check
995
- * @param {Object} options - Optional settings
996
- * @param {boolean} options.dryRun - If true, only report what would be deleted
997
- * @returns {Promise<Object>} Result with cleanup info
998
- */
999
757
  async cleanupCircularHologramsByIds(holonId, lensName, dataIds, options = {}) {
1000
758
  return federation.cleanupCircularHologramsByIds(
1001
759
  this.client,
@@ -1007,131 +765,41 @@ export class HoloSphere extends HoloSphereCore {
1007
765
  );
1008
766
  }
1009
767
 
1010
- /**
1011
- * Propagate data to federated holons and optionally to parent holons
1012
- * @param {string} holonId - Source holon ID
1013
- * @param {string} lensName - Lens name (e.g., 'quests')
1014
- * @param {Object} data - Data or hologram to propagate
1015
- * @param {Object} options - Propagation options
1016
- * @param {boolean} options.useHolograms - Use holograms instead of copies (default: true)
1017
- * @param {boolean} options.propagateToParents - Propagate to parent holons for H3 holons (default: false)
1018
- * @param {number} options.maxParentLevels - Maximum parent levels to propagate (default: 3)
1019
- * @returns {Promise<Object>} Result with success/failure counts
1020
- */
1021
768
  async propagate(holonId, lensName, data, options = {}) {
1022
- const {
1023
- useHolograms = true,
1024
- propagateToParents = false,
1025
- maxParentLevels = 3
1026
- } = options;
1027
-
1028
- const result = {
1029
- success: 0,
1030
- failed: 0,
1031
- targets: [],
1032
- parentPropagation: null
1033
- };
1034
-
1035
- // Get federation config for this holon
1036
769
  const federationData = await this.getFederation(holonId);
1037
-
1038
- // Propagate to federated holons (outbound)
1039
- if (federationData && federationData.outbound) {
1040
- for (const targetHolon of federationData.outbound) {
1041
- // Check if sender has this lens configured for outbound to this target
1042
- const senderLensConfig = federationData.lensConfig?.[targetHolon];
1043
- if (senderLensConfig && senderLensConfig.outbound && !senderLensConfig.outbound.includes(lensName)) {
1044
- continue; // Skip - sender hasn't configured this lens for outbound to this target
1045
- }
1046
-
1047
- // Check if receiver accepts this lens in their inbound from sender
1048
- try {
1049
- const targetFederation = await this.getFederation(targetHolon);
1050
- const receiverLensConfig = targetFederation?.lensConfig?.[holonId];
1051
- if (receiverLensConfig && receiverLensConfig.inbound && !receiverLensConfig.inbound.includes(lensName)) {
1052
- console.log(`Skipping propagation to ${targetHolon} - lens '${lensName}' not in their inbound config for ${holonId}`);
1053
- continue; // Skip - receiver doesn't accept this lens from sender
1054
- }
1055
- } catch (error) {
1056
- console.warn(`Could not verify receiver's inbound config for ${targetHolon}, proceeding anyway:`, error);
1057
- }
1058
-
1059
- try {
1060
- const mode = useHolograms ? 'reference' : 'copy';
1061
- await federation.propagateData(
1062
- this.client,
1063
- this.config.appName,
1064
- data,
1065
- holonId,
1066
- targetHolon,
1067
- lensName,
1068
- mode
1069
- );
1070
- result.success++;
1071
- result.targets.push(targetHolon);
1072
- } catch (error) {
1073
- console.error(`Failed to propagate to ${targetHolon}:`, error);
1074
- result.failed++;
1075
- }
1076
- }
1077
- }
1078
-
1079
- // Propagate to parent holons (for H3/geographic holons)
1080
- if (propagateToParents && spatial.isValidH3(holonId)) {
1081
- const parents = spatial.getParents(holonId);
1082
- const targetParents = parents.slice(0, maxParentLevels);
1083
-
1084
- result.parentPropagation = {
1085
- success: 0,
1086
- failed: 0,
1087
- targets: []
1088
- };
1089
-
1090
- for (const parentHolon of targetParents) {
1091
- try {
1092
- const mode = useHolograms ? 'reference' : 'copy';
1093
- await federation.propagateData(
1094
- this.client,
1095
- this.config.appName,
1096
- data,
1097
- holonId,
1098
- parentHolon,
1099
- lensName,
1100
- mode
1101
- );
1102
- result.parentPropagation.success++;
1103
- result.parentPropagation.targets.push(parentHolon);
1104
- } catch (error) {
1105
- console.error(`Failed to propagate to parent ${parentHolon}:`, error);
1106
- result.parentPropagation.failed++;
1107
- }
1108
- }
770
+ // getFederation returns an object with federated array, not an array directly
771
+ const targets = federationData?.federated || federationData?.outbound || [];
772
+ if (!targets || targets.length === 0) return;
773
+
774
+ const { useHolograms = true, resolveExisting = true } = options;
775
+ const results = [];
776
+
777
+ for (const targetHolonId of targets) {
778
+ const result = await this.propagateData(
779
+ data,
780
+ holonId,
781
+ targetHolonId,
782
+ lensName,
783
+ { useHolograms, resolveExisting }
784
+ );
785
+ results.push({ parent: targetHolonId, ...result });
1109
786
  }
1110
787
 
1111
- return result;
788
+ return results;
1112
789
  }
1113
790
 
1114
791
  async getFederation(holonId) {
1115
792
  if (!holonId) {
1116
793
  throw new Error('getFederation: Missing holon ID');
1117
794
  }
1118
- const data = await this.getGlobal('federation', holonId);
795
+ const data = await this.readGlobal('federation', holonId);
1119
796
  if (!data) return null;
1120
797
 
1121
- // Ensure arrays exist (in case data from storage is malformed)
1122
- if (!Array.isArray(data.inbound)) {
1123
- data.inbound = [];
1124
- }
1125
- if (!Array.isArray(data.outbound)) {
1126
- data.outbound = [];
1127
- }
1128
- if (!data.lensConfig || typeof data.lensConfig !== 'object') {
1129
- data.lensConfig = {};
1130
- }
798
+ if (!Array.isArray(data.inbound)) data.inbound = [];
799
+ if (!Array.isArray(data.outbound)) data.outbound = [];
800
+ if (!data.lensConfig || typeof data.lensConfig !== 'object') data.lensConfig = {};
1131
801
 
1132
- // Backwards compatibility: populate federated from inbound/outbound if it doesn't exist
1133
802
  if (!Array.isArray(data.federated)) {
1134
- // Combine inbound and outbound into a unique set
1135
803
  const allFederated = new Set([...data.inbound, ...data.outbound]);
1136
804
  data.federated = Array.from(allFederated);
1137
805
  }
@@ -1139,25 +807,14 @@ export class HoloSphere extends HoloSphereCore {
1139
807
  return data;
1140
808
  }
1141
809
 
1142
- /**
1143
- * Federate two holons at the holon level (manages multiple lenses)
1144
- * This is a higher-level method that manages federation metadata and lens-specific federations
1145
- * @param {string} sourceHolon - Source holon ID
1146
- * @param {string} targetHolon - Target holon ID
1147
- * @param {Object} options - Federation options
1148
- * @param {Object} options.lensConfig - Lens configuration {inbound: [], outbound: []}
1149
- * @returns {Promise<boolean>} Success indicator
1150
- */
1151
810
  async federateHolon(sourceHolon, targetHolon, options = {}) {
1152
811
  const { lensConfig = { inbound: [], outbound: [] } } = options;
1153
812
 
1154
- // Validate self-federation
1155
813
  if (sourceHolon === targetHolon) {
1156
814
  throw new Error('Cannot federate a holon with itself');
1157
815
  }
1158
816
 
1159
- // Get or create federation record for source holon
1160
- let federationData = await this.getGlobal('federation', sourceHolon) || {
817
+ let federationData = await this.readGlobal('federation', sourceHolon) || {
1161
818
  id: sourceHolon,
1162
819
  name: sourceHolon,
1163
820
  federated: [],
@@ -1167,67 +824,44 @@ export class HoloSphere extends HoloSphereCore {
1167
824
  timestamp: Date.now()
1168
825
  };
1169
826
 
1170
- // Ensure arrays exist (in case data from storage is malformed)
1171
- if (!Array.isArray(federationData.federated)) {
1172
- federationData.federated = [];
1173
- }
1174
- if (!Array.isArray(federationData.inbound)) {
1175
- federationData.inbound = [];
1176
- }
1177
- if (!Array.isArray(federationData.outbound)) {
1178
- federationData.outbound = [];
1179
- }
1180
- if (!federationData.lensConfig || typeof federationData.lensConfig !== 'object') {
1181
- federationData.lensConfig = {};
1182
- }
827
+ if (!Array.isArray(federationData.federated)) federationData.federated = [];
828
+ if (!Array.isArray(federationData.inbound)) federationData.inbound = [];
829
+ if (!Array.isArray(federationData.outbound)) federationData.outbound = [];
830
+ if (!federationData.lensConfig || typeof federationData.lensConfig !== 'object') federationData.lensConfig = {};
1183
831
 
1184
- // Always add target to federated list (tracks all federation links)
1185
832
  if (!federationData.federated.includes(targetHolon)) {
1186
833
  federationData.federated.push(targetHolon);
1187
834
  }
1188
835
 
1189
- // Add target to inbound/outbound lists based on lensConfig
1190
- // If there are outbound lenses, add to outbound list
1191
836
  if (lensConfig.outbound && lensConfig.outbound.length > 0) {
1192
837
  if (!federationData.outbound.includes(targetHolon)) {
1193
838
  federationData.outbound.push(targetHolon);
1194
839
  }
1195
840
  } else {
1196
- // Remove from outbound if no outbound lenses
1197
841
  federationData.outbound = federationData.outbound.filter(id => id !== targetHolon);
1198
842
  }
1199
843
 
1200
- // If there are inbound lenses, add to inbound list
1201
844
  if (lensConfig.inbound && lensConfig.inbound.length > 0) {
1202
845
  if (!federationData.inbound.includes(targetHolon)) {
1203
846
  federationData.inbound.push(targetHolon);
1204
847
  }
1205
848
  } else {
1206
- // Remove from inbound if no inbound lenses
1207
849
  federationData.inbound = federationData.inbound.filter(id => id !== targetHolon);
1208
850
  }
1209
851
 
1210
- // Store lens configuration for this target holon
1211
852
  federationData.lensConfig[targetHolon] = {
1212
853
  inbound: lensConfig.inbound || [],
1213
854
  outbound: lensConfig.outbound || [],
1214
855
  timestamp: Date.now()
1215
856
  };
1216
857
 
1217
- // Save federation metadata
1218
858
  const success = await this.writeGlobal('federation', federationData);
1219
-
1220
- // Clear cache so getFederation returns fresh data immediately
1221
859
  this.clearCache('federation');
1222
860
 
1223
- // Setup lens-specific federations
1224
861
  if (success) {
1225
- // Federate each lens specified in inbound array (receive from target)
1226
862
  for (const lens of (lensConfig.inbound || [])) {
1227
863
  await this.federate(targetHolon, sourceHolon, lens, { direction: 'outbound', mode: 'reference' });
1228
864
  }
1229
-
1230
- // Federate each lens specified in outbound array (send to target)
1231
865
  for (const lens of (lensConfig.outbound || [])) {
1232
866
  await this.federate(sourceHolon, targetHolon, lens, { direction: 'outbound', mode: 'reference' });
1233
867
  }
@@ -1236,46 +870,27 @@ export class HoloSphere extends HoloSphereCore {
1236
870
  return success;
1237
871
  }
1238
872
 
1239
- /**
1240
- * Unfederate two holons at the holon level
1241
- * @param {string} sourceHolon - Source holon ID
1242
- * @param {string} targetHolon - Target holon ID
1243
- * @returns {Promise<boolean>} Success indicator
1244
- */
1245
873
  async unfederateHolon(sourceHolon, targetHolon) {
1246
- // Get federation record
1247
- const federationData = await this.getGlobal('federation', sourceHolon);
1248
- if (!federationData) {
1249
- return false;
1250
- }
874
+ const federationData = await this.readGlobal('federation', sourceHolon);
875
+ if (!federationData) return false;
1251
876
 
1252
- // Get lens config for this target before removing
1253
877
  const lensConfig = federationData.lensConfig?.[targetHolon];
1254
878
 
1255
- // Remove target from federation lists
1256
879
  federationData.federated = (federationData.federated || []).filter(id => id !== targetHolon);
1257
880
  federationData.inbound = (federationData.inbound || []).filter(id => id !== targetHolon);
1258
881
  federationData.outbound = (federationData.outbound || []).filter(id => id !== targetHolon);
1259
882
 
1260
- // Remove lens config for this target
1261
883
  if (federationData.lensConfig) {
1262
884
  delete federationData.lensConfig[targetHolon];
1263
885
  }
1264
886
 
1265
- // Update federation metadata
1266
887
  const success = await this.writeGlobal('federation', federationData);
1267
-
1268
- // Clear cache so getFederation returns fresh data immediately
1269
888
  this.clearCache('federation');
1270
889
 
1271
- // Unfederate lens-specific federations
1272
890
  if (success && lensConfig) {
1273
- // Unfederate each lens that was in inbound array
1274
891
  for (const lens of (lensConfig.inbound || [])) {
1275
892
  await this.unfederate(targetHolon, sourceHolon, lens);
1276
893
  }
1277
-
1278
- // Unfederate each lens that was in outbound array
1279
894
  for (const lens of (lensConfig.outbound || [])) {
1280
895
  await this.unfederate(sourceHolon, targetHolon, lens);
1281
896
  }
@@ -1284,48 +899,31 @@ export class HoloSphere extends HoloSphereCore {
1284
899
  return success;
1285
900
  }
1286
901
 
1287
- /**
1288
- * Get the lens configuration for a specific federated holon
1289
- * @param {string} sourceHolon - Source holon ID
1290
- * @param {string} targetHolon - Target holon ID
1291
- * @returns {Promise<Object|null>} Lens config {inbound: [], outbound: []} or null
1292
- */
1293
902
  async getFederatedConfig(sourceHolon, targetHolon) {
1294
- const federationData = await this.getGlobal('federation', sourceHolon);
1295
- if (!federationData || !federationData.lensConfig) {
1296
- return null;
1297
- }
903
+ const federationData = await this.readGlobal('federation', sourceHolon);
904
+ if (!federationData || !federationData.lensConfig) return null;
1298
905
  return federationData.lensConfig[targetHolon] || null;
1299
906
  }
1300
907
 
1301
908
  // === Hierarchical Operations ===
1302
909
  async upcast(holonId, lensName, dataId, options = {}) {
1303
- return hierarchical.upcast(this.client, this.config.appName, holonId, lensName, dataId, options);
910
+ return hierarchical.upcast(this, holonId, lensName, dataId, options);
1304
911
  }
1305
912
 
1306
- // === Subscription Operations ===
913
+ // === Subscriptions ===
1307
914
  subscribe(holonId, lensName, callback, options = {}) {
1308
915
  if (typeof callback !== 'function') {
1309
916
  throw new TypeError('callback must be a function');
1310
917
  }
1311
-
1312
918
  const path = storage.buildPath(this.config.appName, holonId, lensName);
1313
-
1314
- // Default to real-time only mode (only receive new events, not historical)
1315
- const subscriptionOptions = {
1316
- realtimeOnly: true, // Only get events from subscription time forward
1317
- ...options
1318
- };
1319
-
919
+ const subscriptionOptions = { realtimeOnly: true, ...options };
1320
920
  const subscriptionId = `${holonId}-${lensName}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1321
921
  let innerSubscription = null;
1322
922
  let unsubscribeCalled = false;
1323
923
 
1324
- // Start async subscription setup in background
1325
924
  subscriptions.createSubscription(this.client, path, callback, subscriptionOptions)
1326
925
  .then(subscription => {
1327
926
  innerSubscription = subscription;
1328
- // If unsubscribe was called before setup completed, clean up immediately
1329
927
  if (unsubscribeCalled) {
1330
928
  subscription.unsubscribe();
1331
929
  } else {
@@ -1336,52 +934,26 @@ export class HoloSphere extends HoloSphereCore {
1336
934
  this._log('ERROR', 'Subscription setup failed', { path, error: err.message });
1337
935
  });
1338
936
 
1339
- this._metrics.subscriptions++;
1340
-
1341
- // Return synchronous subscription object
937
+ this._metrics.subscriptions = (this._metrics.subscriptions || 0) + 1;
1342
938
  return {
1343
939
  unsubscribe: () => {
1344
940
  unsubscribeCalled = true;
1345
941
  if (innerSubscription) {
1346
942
  this.subscriptionRegistry.unregister(subscriptionId);
1347
943
  }
1348
- },
944
+ }
1349
945
  };
1350
946
  }
1351
947
 
1352
- /**
1353
- * Subscribe to global table changes
1354
- * @param {string} table - The global table name (e.g., 'federation')
1355
- * @param {string} key - The specific key within the table (e.g., holonId)
1356
- * @param {Function} callback - Callback function called on data changes
1357
- * @param {Object} options - Subscription options
1358
- * @returns {Object} Subscription object with unsubscribe method
1359
- */
1360
948
  async subscribeGlobal(table, key, callback, options = {}) {
1361
- if (typeof callback !== 'function') {
1362
- throw new TypeError('callback must be a function');
1363
- }
1364
-
1365
- // Build global table path: appname/table/key
1366
- const path = `${this.config.appName}/${table}/${key}`;
1367
-
1368
- // Default to real-time only mode
1369
- const subscriptionOptions = {
1370
- realtimeOnly: true,
1371
- ...options
1372
- };
1373
-
1374
- const subscription = await subscriptions.createSubscription(this.client, path, callback, subscriptionOptions);
1375
-
1376
- const subscriptionId = `global-${table}-${key}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1377
- this.subscriptionRegistry.register(subscriptionId, subscription);
1378
- this._metrics.subscriptions++;
1379
-
1380
- return {
1381
- unsubscribe: () => {
1382
- this.subscriptionRegistry.unregister(subscriptionId);
1383
- },
1384
- };
949
+ return globalTables.subscribeGlobal(
950
+ this.client,
951
+ this.config.appName,
952
+ table,
953
+ key,
954
+ callback,
955
+ options
956
+ );
1385
957
  }
1386
958
 
1387
959
  // === Crypto Operations ===
@@ -1407,1242 +979,161 @@ export class HoloSphere extends HoloSphereCore {
1407
979
 
1408
980
  // === Social Protocol Operations ===
1409
981
  async publishNostr(event, holonId, lensName = 'social') {
1410
- // Validate with throwing error (partial=true allows missing id/pubkey/sig)
1411
- social.validateNostrEvent(event, true, true);
1412
-
1413
- // Sign the event if not already signed
1414
- if (!event.id || !event.pubkey || !event.sig) {
1415
- // Use nostr-tools to properly sign the event
1416
- const { finalizeEvent } = await import('nostr-tools');
1417
- const signedEvent = finalizeEvent(event, this.client.privateKey);
1418
-
1419
- // Update event with signature fields
1420
- event.id = signedEvent.id;
1421
- event.pubkey = signedEvent.pubkey;
1422
- event.sig = signedEvent.sig;
1423
- }
982
+ // Validate Nostr event format
983
+ social.validateNostrEvent(event, true, true); // partial=true, throwOnError=true
1424
984
 
1425
- const transformed = social.transformNostrEvent(event);
1426
- return this.write(holonId, lensName, transformed);
985
+ const enrichedEvent = {
986
+ ...event,
987
+ tags: [...(event.tags || []), ['h', holonId], ['l', lensName]],
988
+ };
989
+ const signedEvent = nostrUtils.signEvent(enrichedEvent, this.client.privateKey);
990
+
991
+ // Add protocol field for querySocial filtering
992
+ const eventWithProtocol = {
993
+ ...signedEvent,
994
+ protocol: 'nostr',
995
+ };
996
+
997
+ const path = storage.buildPath(this.config.appName, holonId, lensName, signedEvent.id);
998
+ await storage.write(this.client, path, eventWithProtocol);
999
+ return true;
1427
1000
  }
1428
1001
 
1429
1002
  async publishActivityPub(object, holonId, lensName = 'social') {
1430
- // Validate with throwing error
1431
- social.validateActivityPubObject(object, true);
1003
+ // Validate ActivityPub object format
1004
+ social.validateActivityPubObject(object, true); // throwOnError=true
1432
1005
 
1433
- const transformed = social.transformActivityPubObject(object);
1434
- return this.write(holonId, lensName, transformed);
1006
+ const activity = social.transformActivityPubObject({
1007
+ ...object,
1008
+ actor: object.actor || this.client.publicKey,
1009
+ });
1010
+ return this.write(holonId, lensName, activity);
1435
1011
  }
1436
1012
 
1437
1013
  async querySocial(holonId, options = {}) {
1438
- const { protocol, accessLevel } = options;
1439
- let data = await this.read(holonId, 'social');
1440
-
1441
- if (protocol) {
1442
- data = social.filterByProtocol(data, protocol);
1443
- }
1444
-
1445
- if (accessLevel) {
1446
- data = social.filterByAccessLevel(data, accessLevel);
1447
- }
1448
-
1449
- // Parse tags back to array for Nostr events (stored as JSON string)
1450
- if (Array.isArray(data)) {
1451
- data = data.map(item => {
1452
- if (item.protocol === 'nostr' && typeof item.tags === 'string') {
1453
- return { ...item, tags: JSON.parse(item.tags) };
1454
- }
1455
- return item;
1456
- });
1457
- }
1458
-
1459
- return data;
1014
+ const lensName = options.lens || 'social';
1015
+ const data = await this.read(holonId, lensName);
1016
+
1017
+ if (!data) return [];
1018
+ const items = Array.isArray(data) ? data : [data];
1019
+
1020
+ return items.filter(item => {
1021
+ // 'all' means don't filter by protocol
1022
+ if (options.protocol && options.protocol !== 'all' && item.protocol !== options.protocol) return false;
1023
+ if (options.type && item.type !== options.type) return false;
1024
+ if (options.since && item.created_at < options.since) return false;
1025
+ if (options.until && item.created_at > options.until) return false;
1026
+ return true;
1027
+ });
1460
1028
  }
1461
1029
 
1462
1030
  async verifyNostrEvent(event) {
1463
- // Verify a Nostr event signature
1464
- const { verifyEvent } = await import('nostr-tools');
1465
-
1466
- // Parse tags if they're stored as a string
1467
- const eventToVerify = {
1468
- ...event,
1469
- tags: typeof event.tags === 'string' ? JSON.parse(event.tags) : event.tags
1470
- };
1471
-
1472
- return verifyEvent(eventToVerify);
1031
+ // Extract only standard Nostr event fields for verification
1032
+ // (nostr-tools can't serialize events with extra properties like 'protocol')
1033
+ const { id, pubkey, created_at, kind, tags, content, sig } = event;
1034
+ const standardEvent = { id, pubkey, created_at, kind, tags, content, sig };
1035
+ return nostrUtils.verifyEvent(standardEvent);
1473
1036
  }
1474
1037
 
1475
1038
  // === Metrics ===
1476
1039
  metrics() {
1477
- const baseMetrics = super.metrics();
1040
+ const reads = this._metrics.reads || 0;
1041
+ const writes = this._metrics.writes || 0;
1042
+ const totalReadTime = this._metrics.totalReadTime || 0;
1043
+ const totalWriteTime = this._metrics.totalWriteTime || 0;
1044
+
1478
1045
  return {
1479
- ...baseMetrics,
1046
+ reads,
1047
+ writes,
1048
+ deletes: this._metrics.deletes || 0,
1480
1049
  federations: this._metrics.federations || 0,
1481
- avgWriteTime: this._metrics.writes > 0
1482
- ? (this._metrics.totalWriteTime || 0) / this._metrics.writes
1483
- : 0,
1484
- avgReadTime: this._metrics.reads > 0
1485
- ? (this._metrics.totalReadTime || 0) / this._metrics.reads
1486
- : 0,
1050
+ subscriptions: this._metrics.subscriptions || 0,
1051
+ avgReadTime: reads > 0 ? totalReadTime / reads : 0,
1052
+ avgWriteTime: writes > 0 ? totalWriteTime / writes : 0,
1487
1053
  };
1488
1054
  }
1489
1055
 
1490
- // === Functional Aliases ===
1491
- // Common CRUD-style aliases for data operations
1492
-
1493
- /**
1494
- * Alias for write() - stores data in a holon/lens
1495
- * @alias write
1496
- */
1056
+ // === Aliases ===
1497
1057
  async put(holonId, lensName, data, options = {}) {
1498
1058
  return this.write(holonId, lensName, data, options);
1499
1059
  }
1500
1060
 
1501
- /**
1502
- * Alias for read() - retrieves data from a holon/lens
1503
- * @alias read
1504
- */
1505
1061
  async get(holonId, lensName, dataId = null, options = {}) {
1506
1062
  return this.read(holonId, lensName, dataId, options);
1507
1063
  }
1508
1064
 
1509
- /**
1510
- * Alias for delete() - removes data from a holon/lens
1511
- * @alias delete
1512
- */
1513
1065
  async remove(holonId, lensName, dataId, options = {}) {
1514
1066
  return this.delete(holonId, lensName, dataId, options);
1515
1067
  }
1516
1068
 
1517
- /**
1518
- * Alias for writeGlobal() - stores data in global table
1519
- * @alias writeGlobal
1520
- */
1521
1069
  async putGlobal(table, data) {
1522
1070
  return this.writeGlobal(table, data);
1523
1071
  }
1524
1072
 
1525
- /**
1526
- * Alias for readGlobal() - retrieves data from global table
1527
- * @alias readGlobal
1528
- */
1529
1073
  async getGlobal(table, key = null) {
1530
1074
  return this.readGlobal(table, key);
1531
1075
  }
1532
1076
 
1533
- /**
1534
- * Alias for deleteGlobal() - removes data from global table
1535
- * @alias deleteGlobal
1536
- */
1537
1077
  async removeGlobal(table, key) {
1538
1078
  return this.deleteGlobal(table, key);
1539
1079
  }
1540
1080
 
1541
- /**
1542
- * Alias for setSchema() - defines a schema for a lens
1543
- * @alias setSchema
1544
- */
1545
1081
  async defineSchema(lensName, schemaObj, strict = false) {
1546
1082
  return this.setSchema(lensName, schemaObj, strict);
1547
1083
  }
1548
1084
 
1549
- /**
1550
- * Alias for getSchema() - retrieves schema for a lens
1551
- * @alias getSchema
1552
- */
1553
1085
  async fetchSchema(lensName) {
1554
1086
  return this.getSchema(lensName);
1555
1087
  }
1556
1088
 
1557
- /**
1558
- * Alias for clearSchema() - removes schema for a lens
1559
- * @alias clearSchema
1560
- */
1561
1089
  async removeSchema(lensName) {
1562
1090
  return this.clearSchema(lensName);
1563
1091
  }
1564
1092
 
1565
- /**
1566
- * Store data - more semantic alias for write
1567
- * @alias write
1568
- */
1569
1093
  async store(holonId, lensName, data, options = {}) {
1570
1094
  return this.write(holonId, lensName, data, options);
1571
1095
  }
1572
1096
 
1573
- /**
1574
- * Fetch data - more semantic alias for read
1575
- * @alias read
1576
- */
1577
1097
  async fetch(holonId, lensName, dataId = null, options = {}) {
1578
1098
  return this.read(holonId, lensName, dataId, options);
1579
1099
  }
1580
1100
 
1581
- /**
1582
- * Save data - alternative alias for write
1583
- * @alias write
1584
- */
1585
1101
  async save(holonId, lensName, data, options = {}) {
1586
1102
  return this.write(holonId, lensName, data, options);
1587
1103
  }
1588
1104
 
1589
- /**
1590
- * Load data - alternative alias for read
1591
- * @alias read
1592
- */
1593
1105
  async load(holonId, lensName, dataId = null, options = {}) {
1594
1106
  return this.read(holonId, lensName, dataId, options);
1595
1107
  }
1596
1108
 
1597
- /**
1598
- * Clear the internal cache (or specific entries matching a pattern)
1599
- * Useful after write operations when you need to immediately read fresh data
1600
- * @param {string} [pattern] - Optional pattern to match cache keys (clears all if not provided)
1601
- */
1109
+ // === Cache ===
1602
1110
  clearCache(pattern = null) {
1603
- if (this.client && this.client.clearCache) {
1604
- this.client.clearCache(pattern);
1605
- }
1606
- }
1607
-
1608
- // === AI Operations ===
1609
-
1610
- /**
1611
- * Check if AI is available and throw if not
1612
- * @private
1613
- */
1614
- _requireAI() {
1615
- if (!this._ai) {
1616
- throw new Error('AI services not initialized. Provide openaiKey in config.');
1617
- }
1618
- }
1619
-
1620
- // --- Core LLM Methods ---
1621
-
1622
- /**
1623
- * Summarize text using AI
1624
- * @param {string} text - Text to summarize
1625
- * @param {Object} options - Options for summarization
1626
- * @returns {Promise<string>} Summary
1627
- */
1628
- async summarize(text, options = {}) {
1629
- this._requireAI();
1630
- return this._ai.llm.summarize(text, options);
1631
- }
1632
-
1633
- /**
1634
- * Analyze text from a specific perspective
1635
- * @param {string} text - Text to analyze
1636
- * @param {string} aspect - Perspective to analyze from
1637
- * @param {Object} options - Options
1638
- * @returns {Promise<string>} Analysis
1639
- */
1640
- async analyze(text, aspect, options = {}) {
1641
- this._requireAI();
1642
- return this._ai.llm.analyze(text, aspect, options);
1643
- }
1644
-
1645
- /**
1646
- * Extract keywords from text
1647
- * @param {string} text - Text to extract keywords from
1648
- * @param {Object} options - Options
1649
- * @returns {Promise<string[]>} Keywords
1650
- */
1651
- async extractKeywords(text, options = {}) {
1652
- this._requireAI();
1653
- return this._ai.llm.extractKeywords(text, options);
1654
- }
1655
-
1656
- /**
1657
- * Categorize text into one of the provided categories
1658
- * @param {string} text - Text to categorize
1659
- * @param {string[]} categories - Available categories
1660
- * @param {Object} options - Options
1661
- * @returns {Promise<string>} Selected category
1662
- */
1663
- async categorize(text, categories, options = {}) {
1664
- this._requireAI();
1665
- return this._ai.llm.categorize(text, categories, options);
1666
- }
1667
-
1668
- /**
1669
- * Translate text to a target language
1670
- * @param {string} text - Text to translate
1671
- * @param {string} targetLanguage - Target language
1672
- * @param {Object} options - Options
1673
- * @returns {Promise<string>} Translated text
1674
- */
1675
- async translate(text, targetLanguage, options = {}) {
1676
- this._requireAI();
1677
- return this._ai.llm.translate(text, targetLanguage, options);
1678
- }
1679
-
1680
- /**
1681
- * Generate questions based on text
1682
- * @param {string} text - Text to generate questions from
1683
- * @param {Object} options - Options
1684
- * @returns {Promise<string[]>} Generated questions
1685
- */
1686
- async generateQuestions(text, options = {}) {
1687
- this._requireAI();
1688
- return this._ai.llm.generateQuestions(text, options);
1689
- }
1690
-
1691
- // --- Schema Extraction ---
1692
-
1693
- /**
1694
- * Extract structured data from text based on a lens schema
1695
- * @param {string} text - Natural language text
1696
- * @param {string} lensName - Name of the lens to get schema from
1697
- * @param {Object} options - Options
1698
- * @returns {Promise<Object>} Extracted structured data
1699
- */
1700
- async extractToSchema(text, lensName, options = {}) {
1701
- this._requireAI();
1702
- const schemaObj = await this.getSchema(lensName);
1703
- if (!schemaObj) {
1704
- throw new Error(`No schema found for lens: ${lensName}`);
1705
- }
1706
- return this._ai.schemaExtractor.extractToSchema(text, schemaObj, options);
1707
- }
1708
-
1709
- // --- Fuzzy JSON Operations ---
1710
-
1711
- /**
1712
- * Semantically merge two JSON objects
1713
- * @param {Object} obj1 - First object
1714
- * @param {Object} obj2 - Second object
1715
- * @param {Object} options - Options
1716
- * @returns {Promise<Object>} Merged object
1717
- */
1718
- async jsonAdd(obj1, obj2, options = {}) {
1719
- this._requireAI();
1720
- return this._ai.jsonOps.add(obj1, obj2, options);
1721
- }
1722
-
1723
- /**
1724
- * Remove concepts from obj1 based on obj2
1725
- * @param {Object} obj1 - Base object
1726
- * @param {Object} obj2 - Object with concepts to remove
1727
- * @param {Object} options - Options
1728
- * @returns {Promise<Object>} Result
1729
- */
1730
- async jsonSubtract(obj1, obj2, options = {}) {
1731
- this._requireAI();
1732
- return this._ai.jsonOps.subtract(obj1, obj2, options);
1733
- }
1734
-
1735
- /**
1736
- * Union two objects with intelligent deduplication
1737
- * @param {Object} obj1 - First object
1738
- * @param {Object} obj2 - Second object
1739
- * @param {Object} options - Options
1740
- * @returns {Promise<Object>} United object
1741
- */
1742
- async jsonUnion(obj1, obj2, options = {}) {
1743
- this._requireAI();
1744
- return this._ai.jsonOps.union(obj1, obj2, options);
1745
- }
1746
-
1747
- /**
1748
- * Find semantic differences between two objects
1749
- * @param {Object} obj1 - First object
1750
- * @param {Object} obj2 - Second object
1751
- * @param {Object} options - Options
1752
- * @returns {Promise<Object>} Differences
1753
- */
1754
- async jsonDifference(obj1, obj2, options = {}) {
1755
- this._requireAI();
1756
- return this._ai.jsonOps.difference(obj1, obj2, options);
1757
- }
1758
-
1759
- /**
1760
- * Semantically concatenate objects/arrays
1761
- * @param {Object} obj1 - First object
1762
- * @param {Object} obj2 - Second object
1763
- * @param {Object} options - Options
1764
- * @returns {Promise<Object>} Concatenated result
1765
- */
1766
- async jsonConcatenate(obj1, obj2, options = {}) {
1767
- this._requireAI();
1768
- return this._ai.jsonOps.concatenate(obj1, obj2, options);
1769
- }
1770
-
1771
- // --- Embeddings & Semantic Search ---
1772
-
1773
- /**
1774
- * Generate embedding vector for text
1775
- * @param {string} text - Text to embed
1776
- * @returns {Promise<number[]>} Embedding vector
1777
- */
1778
- async embed(text) {
1779
- this._requireAI();
1780
- return this._ai.embeddings.embed(text);
1781
- }
1782
-
1783
- /**
1784
- * Perform semantic search in a holon/lens
1785
- * @param {string} query - Search query
1786
- * @param {string} holonId - Holon to search in
1787
- * @param {string} lensName - Lens to search in
1788
- * @param {Object} options - Search options
1789
- * @returns {Promise<Array>} Search results
1790
- */
1791
- async semanticSearch(query, holonId, lensName, options = {}) {
1792
- this._requireAI();
1793
- return this._ai.embeddings.semanticSearch(query, holonId, lensName, options);
1794
- }
1795
-
1796
- /**
1797
- * Store data with embedding for later semantic search
1798
- * @param {string} holonId - Holon ID
1799
- * @param {string} lensName - Lens name
1800
- * @param {Object} data - Data to store
1801
- * @param {string} textField - Field to generate embedding from
1802
- * @returns {Promise<boolean>} Success
1803
- */
1804
- async storeWithEmbedding(holonId, lensName, data, textField = 'description') {
1805
- this._requireAI();
1806
- return this._ai.embeddings.storeWithEmbedding(holonId, lensName, data, textField);
1807
- }
1808
-
1809
- // --- Multi-Perspective Council ---
1810
-
1811
- /**
1812
- * Ask the council of perspectives for wisdom
1813
- * @param {string} question - Question to ask
1814
- * @param {Object} options - Options
1815
- * @returns {Promise<Object>} Council responses
1816
- */
1817
- async askCouncil(question, options = {}) {
1818
- this._requireAI();
1819
- return this._ai.council.ask(question, options);
1820
- }
1821
-
1822
- /**
1823
- * Ask council with custom perspectives
1824
- * @param {string} question - Question to ask
1825
- * @param {string[]} perspectives - Custom perspectives
1826
- * @param {Object} options - Options
1827
- * @returns {Promise<Object>} Council responses
1828
- */
1829
- async askCouncilCustom(question, perspectives, options = {}) {
1830
- this._requireAI();
1831
- return this._ai.council.askCustom(question, perspectives, options);
1832
- }
1833
-
1834
- // --- Text-to-Speech ---
1835
-
1836
- /**
1837
- * Convert text to speech
1838
- * @param {string} text - Text to speak
1839
- * @param {string} voice - Voice to use
1840
- * @param {Object} options - TTS options
1841
- * @returns {Promise<Buffer>} Audio buffer
1842
- */
1843
- async textToSpeech(text, voice = 'nova', options = {}) {
1844
- this._requireAI();
1845
- return this._ai.tts.speak(text, voice, options);
1846
- }
1847
-
1848
- /**
1849
- * Convert text to speech as base64
1850
- * @param {string} text - Text to speak
1851
- * @param {string} voice - Voice to use
1852
- * @param {Object} options - TTS options
1853
- * @returns {Promise<string>} Base64 audio
1854
- */
1855
- async textToSpeechBase64(text, voice = 'nova', options = {}) {
1856
- this._requireAI();
1857
- return this._ai.tts.speakBase64(text, voice, options);
1858
- }
1859
-
1860
- // --- Natural Language Queries ---
1861
-
1862
- /**
1863
- * Query data using natural language
1864
- * @param {string} query - Natural language query
1865
- * @param {string} holonId - Holon to query (optional)
1866
- * @param {string} lensName - Lens to query (optional)
1867
- * @param {Object} options - Options
1868
- * @returns {Promise<Array>} Query results
1869
- */
1870
- async nlQuery(query, holonId = null, lensName = null, options = {}) {
1871
- this._requireAI();
1872
- return this._ai.nlQuery.execute(query, holonId, lensName, options);
1873
- }
1874
-
1875
- /**
1876
- * Parse natural language query to structured query
1877
- * @param {string} query - Natural language query
1878
- * @returns {Promise<Object>} Parsed query structure
1879
- */
1880
- async parseNLQuery(query) {
1881
- this._requireAI();
1882
- return this._ai.nlQuery.parse(query);
1883
- }
1884
-
1885
- // --- Auto-Classification ---
1886
-
1887
- /**
1888
- * Classify content to suggest the best lens
1889
- * @param {Object} content - Content to classify
1890
- * @param {Object} options - Options
1891
- * @returns {Promise<Object>} Classification result
1892
- */
1893
- async classifyToLens(content, options = {}) {
1894
- this._requireAI();
1895
- return this._ai.classifier.classifyToLens(content, options);
1896
- }
1897
-
1898
- /**
1899
- * Automatically store content in the best lens
1900
- * @param {string} holonId - Holon ID
1901
- * @param {Object} content - Content to store
1902
- * @param {Object} options - Options
1903
- * @returns {Promise<Object>} Storage result
1904
- */
1905
- async autoStore(holonId, content, options = {}) {
1906
- this._requireAI();
1907
- return this._ai.classifier.autoStore(holonId, content, options);
1908
- }
1909
-
1910
- /**
1911
- * Register a lens with the classifier
1912
- * @param {string} lensName - Lens name
1913
- * @param {string} description - Lens description
1914
- * @param {string[]} keywords - Keywords for this lens
1915
- */
1916
- registerLensForClassification(lensName, description, keywords = []) {
1917
- this._requireAI();
1918
- this._ai.classifier.registerLens(lensName, description, keywords);
1919
- }
1920
-
1921
- // --- Spatial Analysis ---
1922
-
1923
- /**
1924
- * AI-powered analysis of a geographic region
1925
- * @param {string} holonId - Holon to analyze
1926
- * @param {string} lensName - Lens to analyze (optional)
1927
- * @param {string} aspect - Aspect to focus on (optional)
1928
- * @param {Object} options - Options
1929
- * @returns {Promise<Object>} Analysis result
1930
- */
1931
- async analyzeRegion(holonId, lensName = null, aspect = null, options = {}) {
1932
- this._requireAI();
1933
- return this._ai.spatial.analyzeRegion(holonId, lensName, aspect, options);
1934
- }
1935
-
1936
- /**
1937
- * Compare data between two regions
1938
- * @param {string} holon1 - First holon
1939
- * @param {string} holon2 - Second holon
1940
- * @param {string} lensName - Lens to compare (optional)
1941
- * @returns {Promise<Object>} Comparison result
1942
- */
1943
- async compareRegions(holon1, holon2, lensName = null) {
1944
- this._requireAI();
1945
- return this._ai.spatial.compareRegions(holon1, holon2, lensName);
1946
- }
1947
-
1948
- /**
1949
- * Identify trends over time/space
1950
- * @param {string} holonId - Holon to analyze
1951
- * @param {string} lensName - Lens
1952
- * @param {Object} timeRange - Time range
1953
- * @returns {Promise<Object>} Trends
1954
- */
1955
- async spatialTrends(holonId, lensName, timeRange = null) {
1956
- this._requireAI();
1957
- return this._ai.spatial.spatialTrends(holonId, lensName, timeRange);
1958
- }
1959
-
1960
- // --- Smart Aggregation ---
1961
-
1962
- /**
1963
- * AI-summarized hierarchical aggregation up the holon tree
1964
- * @param {string} holonId - Starting holon
1965
- * @param {string} lensName - Lens to aggregate
1966
- * @param {Object} options - Options
1967
- * @returns {Promise<Object>} Aggregation result
1968
- */
1969
- async smartUpcast(holonId, lensName, options = {}) {
1970
- this._requireAI();
1971
- return this._ai.aggregation.smartUpcast(holonId, lensName, options);
1972
- }
1973
-
1974
- /**
1975
- * Generate comprehensive holon summary
1976
- * @param {string} holonId - Holon to summarize
1977
- * @returns {Promise<Object>} Summary
1978
- */
1979
- async generateHolonSummary(holonId) {
1980
- this._requireAI();
1981
- return this._ai.aggregation.generateHolonSummary(holonId);
1982
- }
1983
-
1984
- // --- Federation Advisor ---
1985
-
1986
- /**
1987
- * Get AI suggestions for federation partners
1988
- * @param {string} holonId - Holon to find partners for
1989
- * @param {Object} options - Options including candidateHolons
1990
- * @returns {Promise<Object>} Federation suggestions
1991
- */
1992
- async suggestFederations(holonId, options = {}) {
1993
- this._requireAI();
1994
- return this._ai.federationAdvisor.suggestFederations(holonId, options);
1995
- }
1996
-
1997
- /**
1998
- * Analyze federation health
1999
- * @param {string} holonId - Holon to analyze
2000
- * @returns {Promise<Object>} Health analysis
2001
- */
2002
- async analyzeFederationHealth(holonId) {
2003
- this._requireAI();
2004
- return this._ai.federationAdvisor.analyzeFederationHealth(holonId);
2005
- }
2006
-
2007
- /**
2008
- * Get federation optimization suggestions
2009
- * @param {string} holonId - Holon to optimize
2010
- * @returns {Promise<Object>} Optimization suggestions
2011
- */
2012
- async optimizeFederation(holonId) {
2013
- this._requireAI();
2014
- return this._ai.federationAdvisor.optimizeFederation(holonId);
2015
- }
2016
-
2017
- // --- Relationship Discovery ---
2018
-
2019
- /**
2020
- * Discover hidden relationships in data
2021
- * @param {string} holonId - Holon to analyze
2022
- * @param {string} lensName - Lens (optional)
2023
- * @returns {Promise<Object>} Discovered relationships
2024
- */
2025
- async discoverRelationships(holonId, lensName = null) {
2026
- this._requireAI();
2027
- return this._ai.relationships.discoverRelationships(holonId, lensName);
2028
- }
2029
-
2030
- /**
2031
- * Find semantically similar items
2032
- * @param {Object} item - Item to find similar items for
2033
- * @param {string} holonId - Holon to search in
2034
- * @param {string} lensName - Lens to search in (optional)
2035
- * @param {Object} options - Options
2036
- * @returns {Promise<Array>} Similar items
2037
- */
2038
- async findSimilar(item, holonId, lensName = null, options = {}) {
2039
- this._requireAI();
2040
- return this._ai.relationships.findSimilar(item, holonId, lensName, options);
2041
- }
2042
-
2043
- /**
2044
- * Build relationship graph
2045
- * @param {string} holonId - Holon
2046
- * @param {string} lensName - Lens
2047
- * @returns {Promise<Object>} Graph structure
2048
- */
2049
- async buildRelationshipGraph(holonId, lensName) {
2050
- this._requireAI();
2051
- return this._ai.relationships.buildGraph(holonId, lensName);
2052
- }
2053
-
2054
- /**
2055
- * Suggest connections for an item
2056
- * @param {Object} item - Item to find connections for
2057
- * @param {string} holonId - Holon
2058
- * @param {string} lensName - Lens
2059
- * @returns {Promise<Object>} Suggested connections
2060
- */
2061
- async suggestConnections(item, holonId, lensName) {
2062
- this._requireAI();
2063
- return this._ai.relationships.suggestConnections(item, holonId, lensName);
2064
- }
2065
-
2066
- // --- Task Breakdown ---
2067
-
2068
- /**
2069
- * Recursively break down a task/quest into subtasks
2070
- * @param {Object} item - The item to break down (must have id)
2071
- * @param {string} holonId - Holon where the item lives
2072
- * @param {string} lensName - Lens name (e.g., 'quests', 'tasks')
2073
- * @param {Object} options - Breakdown options
2074
- * @param {number} options.depth - Maximum recursion depth (default: 2)
2075
- * @param {number|Object} options.stepsPerLevel - Subtasks per level (default: {min:3, max:5})
2076
- * @param {boolean} options.useContext - Look at other items for context (default: true)
2077
- * @param {boolean} options.storeResults - Store generated subtasks (default: true)
2078
- * @returns {Promise<Object>} Breakdown result with tree structure
2079
- */
2080
- async breakdown(item, holonId, lensName, options = {}) {
2081
- this._requireAI();
2082
- return this._ai.taskBreakdown.breakdown(item, holonId, lensName, options);
2083
- }
2084
-
2085
- /**
2086
- * Suggest breakdown strategy for a task
2087
- * @param {Object} item - Item to analyze
2088
- * @returns {Promise<Object>} Suggested breakdown strategy
2089
- */
2090
- async suggestBreakdownStrategy(item) {
2091
- this._requireAI();
2092
- return this._ai.taskBreakdown.suggestStrategy(item);
2093
- }
2094
-
2095
- /**
2096
- * Flatten a breakdown tree into a list
2097
- * @param {Object} breakdownResult - Result from breakdown()
2098
- * @returns {Object[]} Flat list of all items
2099
- */
2100
- flattenBreakdown(breakdownResult) {
2101
- this._requireAI();
2102
- return this._ai.taskBreakdown.flatten(breakdownResult);
2103
- }
2104
-
2105
- /**
2106
- * Get items in dependency order for execution
2107
- * @param {Object} breakdownResult - Result from breakdown()
2108
- * @returns {Object[]} Items in dependency order
2109
- */
2110
- getBreakdownDependencyOrder(breakdownResult) {
2111
- this._requireAI();
2112
- return this._ai.taskBreakdown.getDependencyOrder(breakdownResult);
2113
- }
2114
-
2115
- /**
2116
- * Get progress on a breakdown tree
2117
- * @param {string} holonId - Holon ID
2118
- * @param {string} lensName - Lens name
2119
- * @param {string} itemId - Root item ID
2120
- * @returns {Promise<Object>} Progress summary
2121
- */
2122
- async getBreakdownProgress(holonId, lensName, itemId) {
2123
- this._requireAI();
2124
- return this._ai.taskBreakdown.getProgress(holonId, lensName, itemId);
2125
- }
2126
-
2127
- // --- H3 Geospatial AI ---
2128
-
2129
- /**
2130
- * Suggest optimal H3 resolution for a task/project
2131
- * @param {Object} item - Item to analyze
2132
- * @param {Object} options - Options including currentResolution
2133
- * @returns {Promise<Object>} Resolution recommendation
2134
- */
2135
- async suggestH3Resolution(item, options = {}) {
2136
- this._requireAI();
2137
- return this._ai.h3ai.suggestResolution(item, options);
2138
- }
2139
-
2140
- /**
2141
- * Analyze geographic distribution of data in a region
2142
- * @param {string} holonId - Parent holon to analyze
2143
- * @param {string} lensName - Lens to analyze
2144
- * @param {Object} options - Options
2145
- * @returns {Promise<Object>} Distribution analysis
2146
- */
2147
- async analyzeH3Distribution(holonId, lensName, options = {}) {
2148
- this._requireAI();
2149
- return this._ai.h3ai.analyzeDistribution(holonId, lensName, options);
2150
- }
2151
-
2152
- /**
2153
- * Find relevant data from neighboring H3 cells
2154
- * @param {string} holonId - Center holon
2155
- * @param {string} lensName - Lens to search
2156
- * @param {Object} options - Options including ringSize
2157
- * @returns {Promise<Object>} Neighbor analysis
2158
- */
2159
- async findH3NeighborRelevance(holonId, lensName, options = {}) {
2160
- this._requireAI();
2161
- return this._ai.h3ai.findNeighborRelevance(holonId, lensName, options);
2162
- }
2163
-
2164
- /**
2165
- * Suggest geographic expansion or contraction for an item
2166
- * @param {Object} item - Item to analyze
2167
- * @param {string} holonId - Current holon
2168
- * @param {string} lensName - Lens
2169
- * @param {Object} options - Options
2170
- * @returns {Promise<Object>} Scope suggestions
2171
- */
2172
- async suggestGeographicScope(item, holonId, lensName, options = {}) {
2173
- this._requireAI();
2174
- return this._ai.h3ai.suggestGeographicScope(item, holonId, lensName, options);
2175
- }
2176
-
2177
- /**
2178
- * Analyze coverage gaps in a region
2179
- * @param {string} holonId - Region to analyze
2180
- * @param {string} lensName - Lens
2181
- * @param {Object} options - Options including targetResolution
2182
- * @returns {Promise<Object>} Coverage analysis
2183
- */
2184
- async analyzeH3Coverage(holonId, lensName, options = {}) {
2185
- this._requireAI();
2186
- return this._ai.h3ai.analyzeCoverage(holonId, lensName, options);
2187
- }
2188
-
2189
- /**
2190
- * Find patterns across multiple H3 resolutions
2191
- * @param {string} holonId - Starting holon
2192
- * @param {string} lensName - Lens
2193
- * @param {Object} options - Options including levels
2194
- * @returns {Promise<Object>} Cross-resolution insights
2195
- */
2196
- async crossH3ResolutionInsights(holonId, lensName, options = {}) {
2197
- this._requireAI();
2198
- return this._ai.h3ai.crossResolutionInsights(holonId, lensName, options);
2199
- }
2200
-
2201
- /**
2202
- * Suggest item migration between holons based on geographic fit
2203
- * @param {Object} item - Item to analyze
2204
- * @param {string} holonId - Current location
2205
- * @param {string} lensName - Lens
2206
- * @param {Object} options - Options including searchRadius
2207
- * @returns {Promise<Object>} Migration suggestions
2208
- */
2209
- async suggestH3Migration(item, holonId, lensName, options = {}) {
2210
- this._requireAI();
2211
- return this._ai.h3ai.suggestMigration(item, holonId, lensName, options);
2212
- }
2213
-
2214
- /**
2215
- * Generate a geographic activity report for a region
2216
- * @param {string} holonId - Region to report on
2217
- * @param {Object} options - Options including lenses array
2218
- * @returns {Promise<Object>} Geographic report
2219
- */
2220
- async generateH3Report(holonId, options = {}) {
2221
- this._requireAI();
2222
- return this._ai.h3ai.generateGeographicReport(holonId, options);
2223
- }
2224
-
2225
- /**
2226
- * Find geographic clusters of related items
2227
- * @param {string} holonId - Region to analyze
2228
- * @param {string} lensName - Lens
2229
- * @param {Object} options - Options including clusterResolution
2230
- * @returns {Promise<Object>} Geographic clusters
2231
- */
2232
- async findH3Clusters(holonId, lensName, options = {}) {
2233
- this._requireAI();
2234
- return this._ai.h3ai.findGeographicClusters(holonId, lensName, options);
2235
- }
2236
-
2237
- /**
2238
- * Analyze geographic impact of an item
2239
- * @param {Object} item - Item to analyze
2240
- * @param {string} holonId - Item's holon
2241
- * @param {string} lensName - Lens
2242
- * @param {Object} options - Options
2243
- * @returns {Promise<Object>} Impact analysis
2244
- */
2245
- async analyzeH3Impact(item, holonId, lensName, options = {}) {
2246
- this._requireAI();
2247
- return this._ai.h3ai.analyzeGeographicImpact(item, holonId, lensName, options);
2248
- }
2249
-
2250
- // === Cross-Holosphere Federation ===
2251
-
2252
- /**
2253
- * Add a federated holosphere partner (manual key exchange)
2254
- * @param {string} pubKey - Partner's public key
2255
- * @param {Object} options - Federation options
2256
- * @param {string} options.alias - Human-readable name for this partner
2257
- * @param {Object[]} options.inboundCapabilities - Capabilities they've granted us
2258
- * @returns {Promise<boolean>} Success indicator
2259
- */
2260
- async addFederatedHolosphere(pubKey, options = {}) {
2261
- if (!pubKey || typeof pubKey !== 'string') {
2262
- throw new ValidationError('pubKey must be a valid public key string');
2263
- }
2264
-
2265
- return registry.addFederatedPartner(
2266
- this.client,
2267
- this.config.appName,
2268
- pubKey,
2269
- options
2270
- );
2271
- }
2272
-
2273
- /**
2274
- * Remove a federated holosphere partner
2275
- * @param {string} pubKey - Partner's public key
2276
- * @returns {Promise<boolean>} Success indicator
2277
- */
2278
- async removeFederatedHolosphere(pubKey) {
2279
- return registry.removeFederatedPartner(
2280
- this.client,
2281
- this.config.appName,
2282
- pubKey
2283
- );
2284
- }
2285
-
2286
- /**
2287
- * Get all federated holosphere public keys
2288
- * @returns {Promise<string[]>} Array of federated public keys
2289
- */
2290
- async getFederatedHolospheres() {
2291
- return registry.getFederatedAuthors(
2292
- this.client,
2293
- this.config.appName
2294
- );
2295
- }
2296
-
2297
- /**
2298
- * Get the full federation registry for this holosphere
2299
- * @returns {Promise<Object>} Federation registry
2300
- */
2301
- async getFederationRegistry() {
2302
- return registry.getFederationRegistry(
2303
- this.client,
2304
- this.config.appName
2305
- );
2306
- }
2307
-
2308
- /**
2309
- * Store a capability token received from another holosphere
2310
- * @param {string} partnerPubKey - Partner's public key
2311
- * @param {Object} capabilityInfo - Capability information
2312
- * @param {string} capabilityInfo.token - The capability token
2313
- * @param {Object} capabilityInfo.scope - Token scope
2314
- * @param {string[]} capabilityInfo.permissions - Granted permissions
2315
- * @param {number} capabilityInfo.expires - Expiration timestamp
2316
- * @returns {Promise<boolean>} Success indicator
2317
- */
2318
- async storeInboundCapability(partnerPubKey, capabilityInfo) {
2319
- return registry.storeInboundCapability(
2320
- this.client,
2321
- this.config.appName,
2322
- partnerPubKey,
2323
- capabilityInfo
2324
- );
2325
- }
2326
-
2327
- /**
2328
- * Read data from a federated source (cross-holosphere)
2329
- * @param {string} sourcePubKey - Source holosphere's public key
2330
- * @param {string} holonId - Holon ID
2331
- * @param {string} lensName - Lens name
2332
- * @param {string} dataId - Optional specific data ID
2333
- * @returns {Promise<Object|Object[]|null>} Data from federated source
2334
- */
2335
- async readFromFederatedSource(sourcePubKey, holonId, lensName, dataId = null) {
2336
- // Get capability for this source
2337
- const capabilityEntry = await registry.getCapabilityForAuthor(
2338
- this.client,
2339
- this.config.appName,
2340
- sourcePubKey,
2341
- { holonId, lensName, dataId }
2342
- );
2343
-
2344
- if (!capabilityEntry) {
2345
- throw new AuthorizationError('No valid capability for federated source', 'read');
2346
- }
2347
-
2348
- // Verify capability is still valid
2349
- const isValid = await this.verifyCapability(
2350
- capabilityEntry.token,
2351
- 'read',
2352
- { holonId, lensName, dataId }
2353
- );
2354
-
2355
- if (!isValid) {
2356
- throw new AuthorizationError('Capability verification failed', 'read');
2357
- }
2358
-
2359
- // Read with specific author
2360
- if (dataId) {
2361
- const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
2362
- return nostrAsync.nostrGet(this.client, path, 30000, {
2363
- authors: [sourcePubKey],
2364
- includeAuthor: true,
2365
- });
2366
- } else {
2367
- const path = storage.buildPath(this.config.appName, holonId, lensName);
2368
- return nostrAsync.nostrGetAll(this.client, path, 30000, {
2369
- authors: [sourcePubKey],
2370
- includeAuthor: true,
2371
- });
2372
- }
2373
- }
2374
-
2375
- /**
2376
- * Create a cross-holosphere hologram with embedded capability
2377
- * @param {string} sourcePubKey - Source holosphere's public key
2378
- * @param {string} sourceHolon - Source holon ID
2379
- * @param {string} lensName - Lens name
2380
- * @param {string} dataId - Data ID
2381
- * @param {string} targetHolon - Target holon ID (where hologram will live)
2382
- * @param {Object} options - Creation options
2383
- * @param {boolean} options.embedCapability - Whether to embed capability in hologram (default: true)
2384
- * @returns {Promise<Object>} Hologram object
2385
- */
2386
- async createCrossHolosphereHologram(sourcePubKey, sourceHolon, lensName, dataId, targetHolon, options = {}) {
2387
- const { embedCapability = true } = options;
2388
-
2389
- // Get capability for this source
2390
- const capabilityEntry = await registry.getCapabilityForAuthor(
2391
- this.client,
2392
- this.config.appName,
2393
- sourcePubKey,
2394
- { holonId: sourceHolon, lensName, dataId }
2395
- );
2396
-
2397
- if (!capabilityEntry) {
2398
- throw new AuthorizationError('No valid capability for cross-holosphere hologram', 'read');
2399
- }
2400
-
2401
- // Create hologram with embedded capability
2402
- const hologram = federation.createHologram(
2403
- sourceHolon,
2404
- targetHolon,
2405
- lensName,
2406
- dataId,
2407
- this.config.appName,
2408
- {
2409
- authorPubKey: sourcePubKey,
2410
- capability: embedCapability ? capabilityEntry.token : null,
2411
- }
2412
- );
2413
-
2414
- // Write hologram to target holon
2415
- const targetPath = storage.buildPath(this.config.appName, targetHolon, lensName, dataId);
2416
- await storage.write(this.client, targetPath, hologram);
2417
-
2418
- return hologram;
2419
- }
2420
-
2421
- /**
2422
- * Issue a capability token for federation with another holosphere
2423
- * @param {string} targetPubKey - Recipient's public key
2424
- * @param {Object} scope - Capability scope { holonId, lensName, dataId? }
2425
- * @param {string[]} permissions - Array of permissions ['read', 'write', 'delete']
2426
- * @param {Object} options - Additional options
2427
- * @param {number} options.expiresIn - Expiration in ms (default: 1 hour)
2428
- * @param {boolean} options.trackInRegistry - Store in registry (default: true)
2429
- * @returns {Promise<string>} Capability token
2430
- */
2431
- async issueCapabilityForFederation(targetPubKey, scope, permissions, options = {}) {
2432
- const { expiresIn = 3600000, trackInRegistry = true } = options;
2433
-
2434
- const token = await crypto.issueCapability(
2435
- permissions,
2436
- scope,
2437
- targetPubKey,
2438
- {
2439
- expiresIn,
2440
- issuerKey: this.client.privateKey,
2441
- }
2442
- );
2443
-
2444
- // Track in registry
2445
- if (trackInRegistry) {
2446
- // Ensure partner exists in registry
2447
- const partner = await registry.getFederatedPartner(
2448
- this.client,
2449
- this.config.appName,
2450
- targetPubKey
2451
- );
2452
-
2453
- if (!partner) {
2454
- await registry.addFederatedPartner(
2455
- this.client,
2456
- this.config.appName,
2457
- targetPubKey,
2458
- { addedVia: 'capability_issued' }
2459
- );
2460
- }
2461
-
2462
- await registry.storeOutboundCapability(
2463
- this.client,
2464
- this.config.appName,
2465
- targetPubKey,
2466
- {
2467
- tokenHash: await this._hashToken(token),
2468
- scope,
2469
- permissions,
2470
- expires: Date.now() + expiresIn,
1111
+ if (pattern) {
1112
+ for (const key of this._cache.keys()) {
1113
+ if (key.includes(pattern)) {
1114
+ this._cache.delete(key);
2471
1115
  }
2472
- );
1116
+ }
1117
+ } else {
1118
+ this._cache.clear();
2473
1119
  }
2474
-
2475
- return token;
2476
- }
2477
-
2478
- /**
2479
- * Hash a token for storage (don't store full token)
2480
- * @private
2481
- */
2482
- async _hashToken(token) {
2483
- const { sha256 } = await import('@noble/hashes/sha256');
2484
- const { bytesToHex } = await import('@noble/hashes/utils');
2485
- const encoder = new TextEncoder();
2486
- return bytesToHex(sha256(encoder.encode(token)));
2487
- }
2488
-
2489
- // === Nostr Discovery Protocol (Optional) ===
2490
-
2491
- /**
2492
- * Send a federation request via Nostr
2493
- * @param {string} targetPubKey - Target holosphere's public key
2494
- * @param {Object} options - Request options
2495
- * @param {Object} options.scope - Requested scope { holonId, lensName }
2496
- * @param {string[]} options.permissions - Requested permissions
2497
- * @param {string} options.message - Optional message
2498
- * @returns {Promise<Object>} Published event info
2499
- */
2500
- async sendFederationRequest(targetPubKey, options = {}) {
2501
- return discovery.sendFederationRequest(
2502
- this.client,
2503
- this.config.appName,
2504
- targetPubKey,
2505
- options
2506
- );
2507
- }
2508
-
2509
- /**
2510
- * Subscribe to incoming federation requests
2511
- * @param {Function} callback - Called with each request
2512
- * @returns {Promise<Object>} Subscription with unsubscribe method
2513
- */
2514
- async subscribeFederationRequests(callback) {
2515
- return discovery.subscribeFederationRequests(this.client, callback);
2516
- }
2517
-
2518
- /**
2519
- * Accept a federation request
2520
- * @param {Object} request - Request object from subscription
2521
- * @param {Object} options - Acceptance options
2522
- * @returns {Promise<Object>} Published acceptance event info
2523
- */
2524
- async acceptFederationRequest(request, options = {}) {
2525
- return discovery.acceptFederationRequest(
2526
- this.client,
2527
- this.config.appName,
2528
- request,
2529
- options
2530
- );
2531
- }
2532
-
2533
- /**
2534
- * Decline a federation request
2535
- * @param {Object} request - Request object from subscription
2536
- * @param {string} reason - Optional decline reason
2537
- * @returns {Promise<Object>} Published decline event info
2538
- */
2539
- async declineFederationRequest(request, reason = '') {
2540
- return discovery.declineFederationRequest(
2541
- this.client,
2542
- this.config.appName,
2543
- request,
2544
- reason
2545
- );
2546
- }
2547
-
2548
- /**
2549
- * Subscribe to federation acceptances (responses to our requests)
2550
- * @param {Function} callback - Called with each acceptance
2551
- * @returns {Promise<Object>} Subscription with unsubscribe method
2552
- */
2553
- async subscribeFederationAcceptances(callback) {
2554
- return discovery.subscribeFederationAcceptances(
2555
- this.client,
2556
- this.config.appName,
2557
- callback
2558
- );
2559
- }
2560
-
2561
- /**
2562
- * Subscribe to federation declines
2563
- * @param {Function} callback - Called with each decline
2564
- * @returns {Promise<Object>} Subscription with unsubscribe method
2565
- */
2566
- async subscribeFederationDeclines(callback) {
2567
- return discovery.subscribeFederationDeclines(this.client, callback);
2568
- }
2569
-
2570
- /**
2571
- * Get pending federation requests (requests we've sent that haven't been answered)
2572
- * @param {Object} options - Query options
2573
- * @returns {Promise<Object[]>} Array of pending requests
2574
- */
2575
- async getPendingFederationRequests(options = {}) {
2576
- return discovery.getPendingFederationRequests(this.client, options);
2577
- }
2578
- }
2579
-
2580
- // Export error classes
2581
- export class AuthorizationError extends Error {
2582
- constructor(message, requiredPermission = null) {
2583
- super(message);
2584
- this.name = 'AuthorizationError';
2585
- this.requiredPermission = requiredPermission;
2586
- }
2587
- }
2588
-
2589
- export class HolosphereError extends Error {
2590
- constructor(message) {
2591
- super(message);
2592
- this.name = 'HolosphereError';
2593
1120
  }
2594
1121
  }
2595
1122
 
2596
- // Re-export ValidationError from validator module
2597
- export { ValidationError } from './schema/validator.js';
2598
-
2599
- // Export utilities
2600
- export { nostrAsync };
1123
+ /**
1124
+ * Main HoloSphere class - composed from base + all mixins
1125
+ */
1126
+ export const HoloSphere = withContractMethods(
1127
+ withFederationMethods(
1128
+ withAIMethods(HoloSphereBase)
1129
+ )
1130
+ );
2601
1131
 
2602
- // Export hologram helper functions
2603
- export {
2604
- isHologram,
2605
- isResolvedHologram,
2606
- getHologramSource,
2607
- updateHologramOverrides,
2608
- addActiveHologram,
2609
- removeActiveHologram,
2610
- refreshActiveHolograms,
2611
- deleteHologram,
2612
- propagateData,
2613
- createHologram,
2614
- resolveHologram,
2615
- cleanupCircularHolograms,
2616
- cleanupCircularHologramsByIds
2617
- } from './federation/hologram.js';
2618
-
2619
- // Export federation registry functions
2620
- export {
2621
- getFederationRegistry,
2622
- addFederatedPartner,
2623
- removeFederatedPartner,
2624
- getFederatedAuthors,
2625
- getCapabilityForAuthor,
2626
- storeInboundCapability,
2627
- storeOutboundCapability,
2628
- cleanupExpiredCapabilities
2629
- } from './federation/registry.js';
2630
-
2631
- // Export Nostr discovery protocol functions
2632
- export {
2633
- sendFederationRequest,
2634
- subscribeFederationRequests,
2635
- acceptFederationRequest,
2636
- declineFederationRequest,
2637
- subscribeFederationAcceptances,
2638
- subscribeFederationDeclines,
2639
- getPendingFederationRequests
2640
- } from './federation/discovery.js';
2641
-
2642
- // Export scope matching utility
2643
- export { matchScope } from './crypto/secp256k1.js';
1132
+ // Export error classes
1133
+ export { AuthorizationError };
1134
+ export { ValidationError };
2644
1135
 
2645
- // Export AI modules for standalone use
1136
+ // Re-export AI module classes
2646
1137
  export {
2647
1138
  LLMService,
2648
1139
  SchemaExtractor,
@@ -2659,11 +1150,44 @@ export {
2659
1150
  FederationAdvisor,
2660
1151
  RelationshipDiscovery,
2661
1152
  TaskBreakdown,
2662
- H3AI
1153
+ H3AI,
2663
1154
  };
2664
1155
 
1156
+ // Re-export types and utilities
1157
+ export { spatial, storage, schema, federation, handshake, crypto, nostrUtils, social, subscriptions, hierarchical };
1158
+
1159
+ // Re-export specific utilities used in tests
1160
+ export { matchScope } from './crypto/secp256k1.js';
1161
+ export { createHologram } from './federation/hologram.js';
1162
+
2665
1163
  // Export AI factory function
2666
1164
  export { createAIServices } from './ai/index.js';
2667
1165
 
1166
+ // Export Contracts module classes for standalone use
1167
+ export {
1168
+ ChainManager,
1169
+ ContractDeployer,
1170
+ HolonContracts,
1171
+ ContractOperations,
1172
+ ContractABIs,
1173
+ EventListener,
1174
+ ContractQueries,
1175
+ SANKEY_EVENTS
1176
+ };
1177
+
1178
+ // Export network utilities
1179
+ export {
1180
+ NETWORKS,
1181
+ getNetwork,
1182
+ getNetworksByType,
1183
+ listNetworks,
1184
+ isNetworkSupported,
1185
+ getTxUrl,
1186
+ getAddressUrl
1187
+ } from './contracts/networks.js';
1188
+
1189
+ // Export contracts namespace
1190
+ export { networks };
1191
+
2668
1192
  // Default export for backward compatibility
2669
1193
  export default HoloSphere;