holosphere 2.0.0-alpha2 → 2.0.0-alpha5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/2019-D2OG2idw.js +6680 -0
- package/dist/2019-D2OG2idw.js.map +1 -0
- package/dist/2019-EION3wKo.cjs +8 -0
- package/dist/2019-EION3wKo.cjs.map +1 -0
- package/dist/_commonjsHelpers-C37NGDzP.cjs +2 -0
- package/dist/_commonjsHelpers-C37NGDzP.cjs.map +1 -0
- package/dist/_commonjsHelpers-CUmg6egw.js +7 -0
- package/dist/_commonjsHelpers-CUmg6egw.js.map +1 -0
- package/dist/browser-BSniCNqO.js +3058 -0
- package/dist/browser-BSniCNqO.js.map +1 -0
- package/dist/browser-Cq59Ij19.cjs +2 -0
- package/dist/browser-Cq59Ij19.cjs.map +1 -0
- package/dist/cjs/holosphere.cjs +1 -1
- package/dist/esm/holosphere.js +50 -53
- package/dist/index-BG8FStkt.cjs +12 -0
- package/dist/index-BG8FStkt.cjs.map +1 -0
- package/dist/index-Bbey4GkP.js +37869 -0
- package/dist/index-Bbey4GkP.js.map +1 -0
- package/dist/index-Cp3xctq8.js +15104 -0
- package/dist/index-Cp3xctq8.js.map +1 -0
- package/dist/index-hfVGRwSr.cjs +5 -0
- package/dist/index-hfVGRwSr.cjs.map +1 -0
- package/dist/indexeddb-storage-BD70pN7q.cjs +2 -0
- package/dist/indexeddb-storage-BD70pN7q.cjs.map +1 -0
- package/dist/{indexeddb-storage-CMW4qRQS.js → indexeddb-storage-Bjg84U5R.js} +49 -13
- package/dist/indexeddb-storage-Bjg84U5R.js.map +1 -0
- package/dist/{memory-storage-DQzcAZlf.js → memory-storage-CD0XFayE.js} +6 -2
- package/dist/memory-storage-CD0XFayE.js.map +1 -0
- package/dist/{memory-storage-DmePEP2q.cjs → memory-storage-DmMyJtOo.cjs} +2 -2
- package/dist/memory-storage-DmMyJtOo.cjs.map +1 -0
- package/dist/{secp256k1-vOXp40Fx.js → secp256k1-69sS9O-P.js} +2 -393
- package/dist/secp256k1-69sS9O-P.js.map +1 -0
- package/dist/secp256k1-TcN6vWGh.cjs +12 -0
- package/dist/secp256k1-TcN6vWGh.cjs.map +1 -0
- package/docs/CONTRACTS.md +797 -0
- package/examples/demo.html +47 -0
- package/package.json +10 -5
- package/src/contracts/abis/Appreciative.json +1280 -0
- package/src/contracts/abis/AppreciativeFactory.json +101 -0
- package/src/contracts/abis/Bundle.json +1435 -0
- package/src/contracts/abis/BundleFactory.json +106 -0
- package/src/contracts/abis/Holon.json +881 -0
- package/src/contracts/abis/Holons.json +330 -0
- package/src/contracts/abis/Managed.json +1262 -0
- package/src/contracts/abis/ManagedFactory.json +149 -0
- package/src/contracts/abis/Membrane.json +261 -0
- package/src/contracts/abis/Splitter.json +1624 -0
- package/src/contracts/abis/SplitterFactory.json +220 -0
- package/src/contracts/abis/TestToken.json +321 -0
- package/src/contracts/abis/Zoned.json +1461 -0
- package/src/contracts/abis/ZonedFactory.json +154 -0
- package/src/contracts/chain-manager.js +375 -0
- package/src/contracts/deployer.js +443 -0
- package/src/contracts/event-listener.js +507 -0
- package/src/contracts/holon-contracts.js +344 -0
- package/src/contracts/index.js +83 -0
- package/src/contracts/networks.js +224 -0
- package/src/contracts/operations.js +670 -0
- package/src/contracts/queries.js +589 -0
- package/src/core/holosphere.js +453 -1
- package/src/crypto/nostr-utils.js +263 -0
- package/src/federation/handshake.js +455 -0
- package/src/federation/hologram.js +1 -1
- package/src/hierarchical/upcast.js +6 -5
- package/src/index.js +463 -1939
- package/src/lib/ai-methods.js +308 -0
- package/src/lib/contract-methods.js +293 -0
- package/src/lib/errors.js +23 -0
- package/src/lib/federation-methods.js +238 -0
- package/src/lib/index.js +26 -0
- package/src/spatial/h3-operations.js +2 -2
- package/src/storage/backends/gundb-backend.js +377 -46
- package/src/storage/global-tables.js +28 -1
- package/src/storage/gun-auth.js +303 -0
- package/src/storage/gun-federation.js +776 -0
- package/src/storage/gun-references.js +198 -0
- package/src/storage/gun-schema.js +291 -0
- package/src/storage/gun-wrapper.js +347 -31
- package/src/storage/indexeddb-storage.js +49 -11
- package/src/storage/memory-storage.js +5 -0
- package/src/storage/nostr-async.js +194 -37
- package/src/storage/nostr-client.js +580 -51
- package/src/storage/persistent-storage.js +6 -1
- package/src/storage/unified-storage.js +119 -0
- package/src/subscriptions/manager.js +1 -1
- package/types/index.d.ts +133 -0
- package/dist/index-CDfIuXew.js +0 -15974
- package/dist/index-CDfIuXew.js.map +0 -1
- package/dist/index-ifOgtDvd.cjs +0 -3
- package/dist/index-ifOgtDvd.cjs.map +0 -1
- package/dist/indexeddb-storage-CMW4qRQS.js.map +0 -1
- package/dist/indexeddb-storage-DLZOgetM.cjs +0 -2
- package/dist/indexeddb-storage-DLZOgetM.cjs.map +0 -1
- package/dist/memory-storage-DQzcAZlf.js.map +0 -1
- package/dist/memory-storage-DmePEP2q.cjs.map +0 -1
- package/dist/secp256k1-CP0ZkpAx.cjs +0 -13
- package/dist/secp256k1-CP0ZkpAx.cjs.map +0 -1
- 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/
|
|
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
|
-
*
|
|
54
|
+
* Base HoloSphere class with core data operations
|
|
39
55
|
*/
|
|
40
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
252
|
+
await storage.write(this.client, path, data);
|
|
293
253
|
const endTime = Date.now();
|
|
294
254
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
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
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
410
|
-
|
|
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
|
-
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
hybrid: this.config.hybridMode
|
|
439
|
-
});
|
|
375
|
+
result = await storage.readAll(this.client, path);
|
|
376
|
+
}
|
|
440
377
|
|
|
441
|
-
|
|
442
|
-
|
|
378
|
+
const { resolveHolograms = true } = options;
|
|
379
|
+
if (resolveHolograms) {
|
|
380
|
+
result = await this._resolveHolograms(result);
|
|
381
|
+
}
|
|
443
382
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
|
|
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
|
-
|
|
518
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
540
|
-
|
|
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
|
-
//
|
|
543
|
-
if (!
|
|
466
|
+
// Return false if data doesn't exist
|
|
467
|
+
if (!existingData) {
|
|
544
468
|
return false;
|
|
545
469
|
}
|
|
546
470
|
|
|
547
|
-
// Check authorization
|
|
548
|
-
const
|
|
549
|
-
const
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
565
|
-
throw error;
|
|
483
|
+
throw new AuthorizationError('AuthorizationError: Invalid capability token for delete operation', 'delete');
|
|
566
484
|
}
|
|
567
|
-
} else if (
|
|
568
|
-
//
|
|
569
|
-
const
|
|
570
|
-
|
|
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
|
-
|
|
594
|
-
|
|
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
|
|
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.
|
|
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
|
-
// ===
|
|
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,
|
|
634
|
-
|
|
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
|
-
//
|
|
647
|
-
if (
|
|
648
|
-
|
|
649
|
-
|
|
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:
|
|
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
|
-
|
|
582
|
+
// Returns undefined for contract compliance
|
|
664
583
|
}
|
|
665
584
|
|
|
666
585
|
// === Federation Operations ===
|
|
667
586
|
async federate(sourceHolon, targetHolon, lensName, options = {}) {
|
|
668
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
this.
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
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
|
-
|
|
704
|
-
|
|
705
|
-
|
|
615
|
+
this._metrics.federations = (this._metrics.federations || 0) + 1;
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
706
618
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
-
|
|
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
|
-
|
|
804
|
-
|
|
805
|
-
}
|
|
649
|
+
// Resolve holograms using existing method
|
|
650
|
+
return this._resolveHolograms(data);
|
|
806
651
|
}
|
|
807
652
|
|
|
808
653
|
async unfederate(sourceHolon, targetHolon, lensName) {
|
|
809
|
-
//
|
|
810
|
-
const
|
|
811
|
-
|
|
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
|
-
|
|
865
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1039
|
-
if (
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
|
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.
|
|
795
|
+
const data = await this.readGlobal('federation', holonId);
|
|
1119
796
|
if (!data) return null;
|
|
1120
797
|
|
|
1121
|
-
|
|
1122
|
-
if (!Array.isArray(data.
|
|
1123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1171
|
-
if (!Array.isArray(federationData.
|
|
1172
|
-
|
|
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
|
-
|
|
1247
|
-
|
|
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.
|
|
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
|
|
910
|
+
return hierarchical.upcast(this, holonId, lensName, dataId, options);
|
|
1304
911
|
}
|
|
1305
912
|
|
|
1306
|
-
// ===
|
|
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
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
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
|
|
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
|
|
1426
|
-
|
|
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
|
|
1431
|
-
social.validateActivityPubObject(object, true);
|
|
1003
|
+
// Validate ActivityPub object format
|
|
1004
|
+
social.validateActivityPubObject(object, true); // throwOnError=true
|
|
1432
1005
|
|
|
1433
|
-
const
|
|
1434
|
-
|
|
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
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
if (
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
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
|
-
//
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
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
|
|
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
|
-
|
|
1046
|
+
reads,
|
|
1047
|
+
writes,
|
|
1048
|
+
deletes: this._metrics.deletes || 0,
|
|
1480
1049
|
federations: this._metrics.federations || 0,
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
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
|
-
// ===
|
|
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 (
|
|
1604
|
-
this.
|
|
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
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
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
|
|
2603
|
-
export {
|
|
2604
|
-
|
|
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
|
-
//
|
|
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;
|