holosphere 2.0.0-alpha10 → 2.0.0-alpha12
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 → 2019-DQdDE6DG.js} +2 -2
- package/dist/{2019-D2OG2idw.js.map → 2019-DQdDE6DG.js.map} +1 -1
- package/dist/{2019-EION3wKo.cjs → 2019-Ew-DTDlI.cjs} +2 -2
- package/dist/{2019-EION3wKo.cjs.map → 2019-Ew-DTDlI.cjs.map} +1 -1
- package/dist/{browser-BSniCNqO.js → browser-CckFyRI9.js} +2 -2
- package/dist/{browser-BSniCNqO.js.map → browser-CckFyRI9.js.map} +1 -1
- package/dist/browser-D2qtVhH5.cjs +2 -0
- package/dist/{browser-Cq59Ij19.cjs.map → browser-D2qtVhH5.cjs.map} +1 -1
- package/dist/cjs/holosphere.cjs +1 -1
- package/dist/esm/holosphere.js +67 -50
- package/dist/index-9sqetkAn.cjs +29 -0
- package/dist/index-9sqetkAn.cjs.map +1 -0
- package/dist/index-C0ITDyFo.cjs +13 -0
- package/dist/index-C0ITDyFo.cjs.map +1 -0
- package/dist/{index-DeZ1xz_s.js → index-DrYM1LOY.js} +2 -2
- package/dist/{index-DeZ1xz_s.js.map → index-DrYM1LOY.js.map} +1 -1
- package/dist/{index-DJXftyvB.js → index-TDDyakLc.js} +20345 -805
- package/dist/index-TDDyakLc.js.map +1 -0
- package/dist/{index-DDGt_V9o.cjs → index-kyf1sjaC.cjs} +3 -3
- package/dist/{index-DDGt_V9o.cjs.map → index-kyf1sjaC.cjs.map} +1 -1
- package/dist/index-lbSQUoRz.js +11663 -0
- package/dist/index-lbSQUoRz.js.map +1 -0
- package/dist/{indexeddb-storage-BFt6hMeF.js → indexeddb-storage-CXhjqwhA.js} +2 -2
- package/dist/{indexeddb-storage-BFt6hMeF.js.map → indexeddb-storage-CXhjqwhA.js.map} +1 -1
- package/dist/{indexeddb-storage-BK5tv4Sh.cjs → indexeddb-storage-DFESDYIj.cjs} +2 -2
- package/dist/{indexeddb-storage-BK5tv4Sh.cjs.map → indexeddb-storage-DFESDYIj.cjs.map} +1 -1
- package/dist/{memory-storage-Dao7jfYG.cjs → memory-storage-D1tc1bjk.cjs} +2 -2
- package/dist/{memory-storage-Dao7jfYG.cjs.map → memory-storage-D1tc1bjk.cjs.map} +1 -1
- package/dist/{memory-storage-C9HuoL2E.js → memory-storage-DkewsdcM.js} +2 -2
- package/dist/{memory-storage-C9HuoL2E.js.map → memory-storage-DkewsdcM.js.map} +1 -1
- package/docs/FEDERATION.md +474 -0
- package/package.json +3 -1
- package/src/crypto/nostr-utils.js +7 -0
- package/src/crypto/secp256k1.js +46 -29
- package/src/federation/capabilities.js +162 -0
- package/src/federation/card-storage.js +304 -0
- package/src/federation/handshake.js +204 -7
- package/src/federation/hologram.js +167 -58
- package/src/federation/holon-registry.js +166 -0
- package/src/federation/index.js +68 -0
- package/src/federation/registry.js +110 -2
- package/src/federation/request-card.js +373 -0
- package/src/hierarchical/upcast.js +19 -3
- package/src/index.js +224 -95
- package/src/lib/federation-methods.js +527 -5
- package/src/storage/nostr-async.js +2 -2
- package/src/storage/nostr-client.js +370 -155
- package/src/storage/nostr-wrapper.js +1 -1
- package/dist/_commonjsHelpers-C37NGDzP.cjs +0 -2
- package/dist/_commonjsHelpers-C37NGDzP.cjs.map +0 -1
- package/dist/_commonjsHelpers-CUmg6egw.js +0 -7
- package/dist/_commonjsHelpers-CUmg6egw.js.map +0 -1
- package/dist/browser-Cq59Ij19.cjs +0 -2
- package/dist/cdn/holosphere.min.js +0 -55
- package/dist/cdn/holosphere.min.js.map +0 -1
- package/dist/index-DJXftyvB.js.map +0 -1
- package/dist/index-DMbdcMtK.cjs +0 -18
- package/dist/index-DMbdcMtK.cjs.map +0 -1
- package/dist/secp256k1-BbKzbLtD.cjs +0 -12
- package/dist/secp256k1-BbKzbLtD.cjs.map +0 -1
- package/dist/secp256k1-CreY7Pcl.js +0 -1890
- package/dist/secp256k1-CreY7Pcl.js.map +0 -1
package/src/index.js
CHANGED
|
@@ -21,6 +21,11 @@ import * as schema from './schema/validator.js';
|
|
|
21
21
|
import { ValidationError } from './schema/validator.js';
|
|
22
22
|
import * as federation from './federation/hologram.js';
|
|
23
23
|
import * as handshake from './federation/handshake.js';
|
|
24
|
+
import * as holonRegistry from './federation/holon-registry.js';
|
|
25
|
+
import * as registry from './federation/registry.js';
|
|
26
|
+
import * as capabilities from './federation/capabilities.js';
|
|
27
|
+
import * as requestCard from './federation/request-card.js';
|
|
28
|
+
import * as cardStorage from './federation/card-storage.js';
|
|
24
29
|
import * as crypto from './crypto/secp256k1.js';
|
|
25
30
|
import * as nostrUtils from './crypto/nostr-utils.js';
|
|
26
31
|
import * as social from './content/social-protocols.js';
|
|
@@ -254,6 +259,51 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
254
259
|
return spatial.isValidH3(holonId);
|
|
255
260
|
}
|
|
256
261
|
|
|
262
|
+
// === Holon Registry Operations ===
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Register a holon with a public key.
|
|
266
|
+
* Maps a holonId (H3, chatId, concept) to a keypair owner.
|
|
267
|
+
*
|
|
268
|
+
* @param {string} holonId - Holon identifier to register
|
|
269
|
+
* @param {string} publicKey - 64-char hex public key (owner)
|
|
270
|
+
* @param {Object} [options={}] - Registration options
|
|
271
|
+
* @param {string} [options.alias] - Human-readable name
|
|
272
|
+
* @returns {Promise<boolean>} Success indicator
|
|
273
|
+
*/
|
|
274
|
+
async registerHolon(holonId, publicKey, options = {}) {
|
|
275
|
+
return holonRegistry.registerHolon(this.client, this.config.appName, holonId, publicKey, options);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Look up a holon's registration.
|
|
280
|
+
*
|
|
281
|
+
* @param {string} holonId - Holon identifier
|
|
282
|
+
* @returns {Promise<Object|null>} Registry entry { holonId, publicKey, alias } or null
|
|
283
|
+
*/
|
|
284
|
+
async lookupHolon(holonId) {
|
|
285
|
+
return holonRegistry.lookupHolon(this.client, this.config.appName, holonId);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Unregister a holon.
|
|
290
|
+
*
|
|
291
|
+
* @param {string} holonId - Holon identifier
|
|
292
|
+
* @returns {Promise<boolean>} Success indicator
|
|
293
|
+
*/
|
|
294
|
+
async unregisterHolon(holonId) {
|
|
295
|
+
return holonRegistry.unregisterHolon(this.client, this.config.appName, holonId);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* List all registered holons.
|
|
300
|
+
*
|
|
301
|
+
* @returns {Promise<Object[]>} Array of registry entries
|
|
302
|
+
*/
|
|
303
|
+
async getRegisteredHolons() {
|
|
304
|
+
return holonRegistry.listRegisteredHolons(this.client, this.config.appName);
|
|
305
|
+
}
|
|
306
|
+
|
|
257
307
|
// === Data Operations ===
|
|
258
308
|
|
|
259
309
|
/**
|
|
@@ -276,10 +326,14 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
276
326
|
* @throws {AuthorizationError} If capability token is invalid
|
|
277
327
|
*/
|
|
278
328
|
async write(holonId, lensName, data, options = {}) {
|
|
279
|
-
|
|
329
|
+
// Auto-convert to strings for convenience
|
|
330
|
+
if (holonId != null) holonId = String(holonId);
|
|
331
|
+
if (lensName != null) lensName = String(lensName);
|
|
332
|
+
|
|
333
|
+
if (!holonId) {
|
|
280
334
|
throw new ValidationError('ValidationError: holonId must be a non-empty string');
|
|
281
335
|
}
|
|
282
|
-
if (!lensName
|
|
336
|
+
if (!lensName) {
|
|
283
337
|
throw new ValidationError('ValidationError: lensName must be a non-empty string');
|
|
284
338
|
}
|
|
285
339
|
if (!data || typeof data !== 'object') {
|
|
@@ -296,6 +350,9 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
296
350
|
|
|
297
351
|
if (!data.id) {
|
|
298
352
|
data.id = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
353
|
+
} else {
|
|
354
|
+
// Auto-convert data.id to string for convenience
|
|
355
|
+
data.id = String(data.id);
|
|
299
356
|
}
|
|
300
357
|
|
|
301
358
|
// Add metadata
|
|
@@ -318,7 +375,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
318
375
|
// OPTIMISTIC UPDATE: Store in write cache and clear from delete cache
|
|
319
376
|
this._writeCache.set(path, { data: { ...data }, timestamp: Date.now() });
|
|
320
377
|
this._deleteCache.delete(path);
|
|
321
|
-
this._log('
|
|
378
|
+
this._log('DEBUG', '📝 CACHE WRITE', {
|
|
322
379
|
path,
|
|
323
380
|
dataId: data.id,
|
|
324
381
|
cacheSize: this._writeCache.size,
|
|
@@ -353,7 +410,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
353
410
|
*/
|
|
354
411
|
async _syncWriteToRelay(holonId, lensName, data, path, options) {
|
|
355
412
|
const startTime = Date.now();
|
|
356
|
-
this._log('
|
|
413
|
+
this._log('DEBUG', '🔄 RELAY SYNC START', {
|
|
357
414
|
path,
|
|
358
415
|
dataId: data.id,
|
|
359
416
|
relays: this.client?.relays?.map(r => r.url) || ['local-only']
|
|
@@ -378,7 +435,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
378
435
|
if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
|
|
379
436
|
this._metrics.totalWriteTime += duration;
|
|
380
437
|
|
|
381
|
-
this._log('
|
|
438
|
+
this._log('DEBUG', '✅ RELAY SYNC SUCCESS', {
|
|
382
439
|
path,
|
|
383
440
|
dataId: data.id,
|
|
384
441
|
duration: `${duration}ms`,
|
|
@@ -479,6 +536,31 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
479
536
|
return true;
|
|
480
537
|
}
|
|
481
538
|
|
|
539
|
+
/**
|
|
540
|
+
* Resolve holonId to public key.
|
|
541
|
+
* If holonId is already a 64-char hex pubkey, returns it directly.
|
|
542
|
+
* Otherwise, looks up the holon registry.
|
|
543
|
+
*
|
|
544
|
+
* @private
|
|
545
|
+
* @param {string} holonId - Holon identifier
|
|
546
|
+
* @returns {Promise<string|null>} Public key or null if not found
|
|
547
|
+
*/
|
|
548
|
+
async _resolveHolonToPubkey(holonId) {
|
|
549
|
+
return holonRegistry.resolveHolonToPubkey(this.client, this.config.appName, holonId);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Check if we have a stored capability for a given author and scope.
|
|
554
|
+
*
|
|
555
|
+
* @private
|
|
556
|
+
* @param {string} authorPubKey - Author's public key
|
|
557
|
+
* @param {Object} scope - Scope to check (holonId, lensName, dataId)
|
|
558
|
+
* @returns {Promise<Object|null>} Capability entry or null
|
|
559
|
+
*/
|
|
560
|
+
async _getCapabilityForAuthor(authorPubKey, scope) {
|
|
561
|
+
return registry.getCapabilityForAuthor(this.client, this.config.appName, authorPubKey, scope);
|
|
562
|
+
}
|
|
563
|
+
|
|
482
564
|
/**
|
|
483
565
|
* Recursively resolves holograms (references) to their source data.
|
|
484
566
|
* Handles circular reference detection and local overrides.
|
|
@@ -594,19 +676,66 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
594
676
|
* @throws {AuthorizationError} If capability token is invalid
|
|
595
677
|
*/
|
|
596
678
|
async read(holonId, lensName, dataId = null, options = {}) {
|
|
597
|
-
|
|
679
|
+
// Auto-convert to strings for convenience
|
|
680
|
+
if (holonId != null) holonId = String(holonId);
|
|
681
|
+
if (lensName != null) lensName = String(lensName);
|
|
682
|
+
if (dataId != null) dataId = String(dataId);
|
|
683
|
+
|
|
684
|
+
if (!holonId) {
|
|
598
685
|
throw new ValidationError('ValidationError: holonId must be a non-empty string');
|
|
599
686
|
}
|
|
600
|
-
if (!lensName
|
|
687
|
+
if (!lensName) {
|
|
601
688
|
throw new ValidationError('ValidationError: lensName must be a non-empty string');
|
|
602
689
|
}
|
|
603
690
|
|
|
691
|
+
// Resolve holonId to public key (if registered)
|
|
692
|
+
const targetPubkey = await this._resolveHolonToPubkey(holonId);
|
|
693
|
+
|
|
694
|
+
// Determine if reading another author's data
|
|
695
|
+
// If holonId can't be resolved, treat as own data (backwards compatible for H3 holons)
|
|
696
|
+
const isOtherAuthor = targetPubkey && targetPubkey !== this.client.publicKey;
|
|
697
|
+
let readOptions = {};
|
|
698
|
+
|
|
699
|
+
// Explicit capability token takes precedence
|
|
604
700
|
const capToken = options.capabilityToken || options.capability;
|
|
605
701
|
if (capToken) {
|
|
606
702
|
const authorized = await this.verifyCapability(capToken, 'read', { holonId, lensName, dataId });
|
|
607
703
|
if (!authorized) {
|
|
608
704
|
throw new AuthorizationError('AuthorizationError: Invalid capability token for read operation', 'read');
|
|
609
705
|
}
|
|
706
|
+
// Use target author for reading
|
|
707
|
+
if (isOtherAuthor) {
|
|
708
|
+
readOptions.authors = [targetPubkey];
|
|
709
|
+
}
|
|
710
|
+
} else if (isOtherAuthor) {
|
|
711
|
+
// Auto-check capability for other author's data
|
|
712
|
+
const capability = await this._getCapabilityForAuthor(targetPubkey, { holonId, lensName, dataId });
|
|
713
|
+
if (!capability) {
|
|
714
|
+
this._log('DEBUG', 'No capability for author', { holonId, targetPubkey });
|
|
715
|
+
return dataId ? null : [];
|
|
716
|
+
}
|
|
717
|
+
readOptions.authors = [targetPubkey];
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Include federated authors to see holograms written by federation partners
|
|
721
|
+
// This enables visibility of holograms created during federation
|
|
722
|
+
if (!isOtherAuthor) {
|
|
723
|
+
try {
|
|
724
|
+
const federatedAuthors = await registry.getFederatedAuthorsForScope(
|
|
725
|
+
this.client,
|
|
726
|
+
this.config.appName,
|
|
727
|
+
{ holonId, lensName, dataId },
|
|
728
|
+
'read'
|
|
729
|
+
);
|
|
730
|
+
if (federatedAuthors.length > 0) {
|
|
731
|
+
const partnerPubkeys = federatedAuthors.map(f => f.pubKey);
|
|
732
|
+
// Include self + all federated partners
|
|
733
|
+
readOptions.authors = [this.client.publicKey, ...partnerPubkeys];
|
|
734
|
+
this._log('DEBUG', 'Including federated authors', { count: partnerPubkeys.length });
|
|
735
|
+
}
|
|
736
|
+
} catch (err) {
|
|
737
|
+
this._log('WARN', 'Failed to get federated authors', { error: err.message });
|
|
738
|
+
}
|
|
610
739
|
}
|
|
611
740
|
|
|
612
741
|
const startTime = Date.now();
|
|
@@ -617,18 +746,18 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
617
746
|
|
|
618
747
|
// Check delete cache first - if deleted, return null immediately
|
|
619
748
|
if (this._deleteCache.has(path)) {
|
|
620
|
-
this._log('
|
|
749
|
+
this._log('DEBUG', '🗑️ CACHE READ: Deleted item', {
|
|
621
750
|
path,
|
|
622
751
|
source: 'delete-cache',
|
|
623
752
|
deleteCacheSize: this._deleteCache.size
|
|
624
753
|
});
|
|
625
754
|
result = null;
|
|
626
755
|
} else {
|
|
627
|
-
// Check write cache for optimistic reads
|
|
628
|
-
const cached = this._writeCache.get(path);
|
|
756
|
+
// Check write cache for optimistic reads (only for own data)
|
|
757
|
+
const cached = !isOtherAuthor ? this._writeCache.get(path) : null;
|
|
629
758
|
if (cached) {
|
|
630
759
|
const cacheAge = Date.now() - cached.timestamp;
|
|
631
|
-
this._log('
|
|
760
|
+
this._log('DEBUG', '⚡ CACHE HIT: Write cache', {
|
|
632
761
|
path,
|
|
633
762
|
source: 'write-cache',
|
|
634
763
|
cacheAge: `${cacheAge}ms`,
|
|
@@ -636,9 +765,9 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
636
765
|
});
|
|
637
766
|
result = cached.data;
|
|
638
767
|
} else {
|
|
639
|
-
this._log('DEBUG', '📖 CACHE MISS: Reading from storage', { path });
|
|
640
|
-
result = await storage.read(this.client, path);
|
|
641
|
-
this._log('
|
|
768
|
+
this._log('DEBUG', '📖 CACHE MISS: Reading from storage', { path, authors: readOptions.authors });
|
|
769
|
+
result = await storage.read(this.client, path, readOptions);
|
|
770
|
+
this._log('DEBUG', '💾 STORAGE READ', {
|
|
642
771
|
path,
|
|
643
772
|
source: 'storage',
|
|
644
773
|
found: !!result,
|
|
@@ -648,11 +777,11 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
648
777
|
}
|
|
649
778
|
} else {
|
|
650
779
|
const path = storage.buildPath(this.config.appName, holonId, lensName);
|
|
651
|
-
this._log('DEBUG', 'readAll', { holonId, lensName, path });
|
|
780
|
+
this._log('DEBUG', 'readAll', { holonId, lensName, path, authors: readOptions.authors });
|
|
652
781
|
|
|
653
782
|
// For readAll, merge cached writes with storage results and filter deleted
|
|
654
|
-
const storageResult = await storage.readAll(this.client, path);
|
|
655
|
-
result = this._mergeWithWriteCache(storageResult, path);
|
|
783
|
+
const storageResult = await storage.readAll(this.client, path, readOptions);
|
|
784
|
+
result = isOtherAuthor ? storageResult : this._mergeWithWriteCache(storageResult, path);
|
|
656
785
|
this._log('DEBUG', 'readAll result', { count: Array.isArray(result) ? result.length : 0 });
|
|
657
786
|
}
|
|
658
787
|
|
|
@@ -782,7 +911,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
782
911
|
// OPTIMISTIC UPDATE: Store in write cache and clear from delete cache
|
|
783
912
|
this._writeCache.set(path, { data: { ...mergedData }, timestamp: Date.now() });
|
|
784
913
|
this._deleteCache.delete(path);
|
|
785
|
-
this._log('
|
|
914
|
+
this._log('DEBUG', '✏️ CACHE UPDATE', {
|
|
786
915
|
path,
|
|
787
916
|
dataId,
|
|
788
917
|
updatedFields: Object.keys(updates),
|
|
@@ -808,7 +937,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
808
937
|
*/
|
|
809
938
|
async _syncUpdateToRelay(holonId, lensName, dataId, mergedData, existingData, path) {
|
|
810
939
|
const startTime = Date.now();
|
|
811
|
-
this._log('
|
|
940
|
+
this._log('DEBUG', '🔄 UPDATE SYNC START', {
|
|
812
941
|
path,
|
|
813
942
|
dataId,
|
|
814
943
|
relays: this.client?.relays?.map(r => r.url) || ['local-only']
|
|
@@ -822,7 +951,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
822
951
|
if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
|
|
823
952
|
this._metrics.totalWriteTime += duration;
|
|
824
953
|
|
|
825
|
-
this._log('
|
|
954
|
+
this._log('DEBUG', '✅ UPDATE SYNC SUCCESS', {
|
|
826
955
|
path,
|
|
827
956
|
dataId,
|
|
828
957
|
duration: `${duration}ms`,
|
|
@@ -865,13 +994,18 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
865
994
|
* @throws {AuthorizationError} If not owner and no valid capability token
|
|
866
995
|
*/
|
|
867
996
|
async delete(holonId, lensName, dataId, options = {}) {
|
|
868
|
-
|
|
997
|
+
// Auto-convert to strings for convenience
|
|
998
|
+
if (holonId != null) holonId = String(holonId);
|
|
999
|
+
if (lensName != null) lensName = String(lensName);
|
|
1000
|
+
if (dataId != null) dataId = String(dataId);
|
|
1001
|
+
|
|
1002
|
+
if (!holonId) {
|
|
869
1003
|
throw new ValidationError('ValidationError: holonId must be a non-empty string');
|
|
870
1004
|
}
|
|
871
|
-
if (!lensName
|
|
1005
|
+
if (!lensName) {
|
|
872
1006
|
throw new ValidationError('ValidationError: lensName must be a non-empty string');
|
|
873
1007
|
}
|
|
874
|
-
if (!dataId
|
|
1008
|
+
if (!dataId) {
|
|
875
1009
|
throw new ValidationError('ValidationError: dataId must be a non-empty string');
|
|
876
1010
|
}
|
|
877
1011
|
|
|
@@ -920,7 +1054,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
920
1054
|
// OPTIMISTIC DELETE: Remove from write cache and add to delete cache
|
|
921
1055
|
this._writeCache.delete(path);
|
|
922
1056
|
this._deleteCache.add(path);
|
|
923
|
-
this._log('
|
|
1057
|
+
this._log('DEBUG', '🗑️ CACHE DELETE', {
|
|
924
1058
|
path,
|
|
925
1059
|
dataId,
|
|
926
1060
|
deleteCacheSize: this._deleteCache.size,
|
|
@@ -935,14 +1069,14 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
935
1069
|
|
|
936
1070
|
// Delete from storage (background unless blocking)
|
|
937
1071
|
const startTime = Date.now();
|
|
938
|
-
this._log('
|
|
1072
|
+
this._log('DEBUG', '🔄 DELETE SYNC START', { path, dataId });
|
|
939
1073
|
|
|
940
1074
|
const deletePromise = storage.deleteData(this.client, path).then(() => {
|
|
941
1075
|
const duration = Date.now() - startTime;
|
|
942
1076
|
this._metrics.deletes++;
|
|
943
1077
|
// Clear from delete cache after successful sync
|
|
944
1078
|
this._deleteCache.delete(path);
|
|
945
|
-
this._log('
|
|
1079
|
+
this._log('DEBUG', '✅ DELETE SYNC SUCCESS', {
|
|
946
1080
|
path,
|
|
947
1081
|
dataId,
|
|
948
1082
|
duration: `${duration}ms`,
|
|
@@ -983,6 +1117,10 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
983
1117
|
* @returns {Promise<boolean>} True if write succeeded
|
|
984
1118
|
*/
|
|
985
1119
|
async writeGlobal(table, data) {
|
|
1120
|
+
// Auto-convert to strings for convenience
|
|
1121
|
+
if (table != null) table = String(table);
|
|
1122
|
+
if (data?.id != null) data.id = String(data.id);
|
|
1123
|
+
|
|
986
1124
|
return globalTables.writeGlobal(this.client, this.config.appName, table, data);
|
|
987
1125
|
}
|
|
988
1126
|
|
|
@@ -994,6 +1132,10 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
994
1132
|
* @returns {Promise<Object|Array|null>} Data object, array of objects, or null
|
|
995
1133
|
*/
|
|
996
1134
|
async readGlobal(table, key = null) {
|
|
1135
|
+
// Auto-convert to strings for convenience
|
|
1136
|
+
if (table != null) table = String(table);
|
|
1137
|
+
if (key != null) key = String(key);
|
|
1138
|
+
|
|
997
1139
|
return globalTables.readGlobal(this.client, this.config.appName, table, key);
|
|
998
1140
|
}
|
|
999
1141
|
|
|
@@ -1006,6 +1148,10 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
1006
1148
|
* @returns {Promise<boolean>} True if update succeeded
|
|
1007
1149
|
*/
|
|
1008
1150
|
async updateGlobal(table, key, updates) {
|
|
1151
|
+
// Auto-convert to strings for convenience
|
|
1152
|
+
if (table != null) table = String(table);
|
|
1153
|
+
if (key != null) key = String(key);
|
|
1154
|
+
|
|
1009
1155
|
return globalTables.updateGlobal(this.client, this.config.appName, table, key, updates);
|
|
1010
1156
|
}
|
|
1011
1157
|
|
|
@@ -1017,6 +1163,10 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
1017
1163
|
* @returns {Promise<boolean>} True if deletion succeeded
|
|
1018
1164
|
*/
|
|
1019
1165
|
async deleteGlobal(table, key) {
|
|
1166
|
+
// Auto-convert to strings for convenience
|
|
1167
|
+
if (table != null) table = String(table);
|
|
1168
|
+
if (key != null) key = String(key);
|
|
1169
|
+
|
|
1020
1170
|
return globalTables.deleteGlobal(this.client, this.config.appName, table, key);
|
|
1021
1171
|
}
|
|
1022
1172
|
|
|
@@ -1027,6 +1177,9 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
1027
1177
|
* @returns {Promise<Array>} Array of all data objects in the table
|
|
1028
1178
|
*/
|
|
1029
1179
|
async getAllGlobal(table) {
|
|
1180
|
+
// Auto-convert to strings for convenience
|
|
1181
|
+
if (table != null) table = String(table);
|
|
1182
|
+
|
|
1030
1183
|
return globalTables.getAllGlobal(this.client, this.config.appName, table);
|
|
1031
1184
|
}
|
|
1032
1185
|
|
|
@@ -1037,6 +1190,9 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
1037
1190
|
* @returns {Promise<boolean>} True if deletion succeeded
|
|
1038
1191
|
*/
|
|
1039
1192
|
async deleteAllGlobal(table) {
|
|
1193
|
+
// Auto-convert to strings for convenience
|
|
1194
|
+
if (table != null) table = String(table);
|
|
1195
|
+
|
|
1040
1196
|
return globalTables.deleteAllGlobal(this.client, this.config.appName, table);
|
|
1041
1197
|
}
|
|
1042
1198
|
|
|
@@ -1061,6 +1217,10 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
1061
1217
|
* @returns {Promise<boolean>} True if deletion succeeded
|
|
1062
1218
|
*/
|
|
1063
1219
|
async deleteAll(holonId, lensName) {
|
|
1220
|
+
// Auto-convert to strings for convenience
|
|
1221
|
+
if (holonId != null) holonId = String(holonId);
|
|
1222
|
+
if (lensName != null) lensName = String(lensName);
|
|
1223
|
+
|
|
1064
1224
|
const path = storage.buildPath(this.config.appName, holonId, lensName);
|
|
1065
1225
|
return storage.deleteAll(this.client, path);
|
|
1066
1226
|
}
|
|
@@ -1136,56 +1296,11 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
1136
1296
|
}
|
|
1137
1297
|
|
|
1138
1298
|
// === Federation Operations ===
|
|
1139
|
-
|
|
1140
|
-
/**
|
|
1141
|
-
* Sets up federation between two holons for a specific lens.
|
|
1142
|
-
* Federation enables data sharing and synchronization between holons.
|
|
1143
|
-
*
|
|
1144
|
-
* @param {string} sourceHolon - Source holon H3 cell ID
|
|
1145
|
-
* @param {string} targetHolon - Target holon H3 cell ID
|
|
1146
|
-
* @param {string} lensName - Name of the lens to federate
|
|
1147
|
-
* @param {Object} [options={}] - Federation options
|
|
1148
|
-
* @param {string} [options.direction='outbound'] - Direction: 'inbound', 'outbound', or 'bidirectional'
|
|
1149
|
-
* @param {string} [options.mode='reference'] - Mode: 'reference' (hologram) or 'copy'
|
|
1150
|
-
* @param {Function} [options.filter] - Filter function to select which data to federate
|
|
1151
|
-
* @returns {Promise<boolean>} True if federation was set up successfully
|
|
1152
|
-
* @throws {Error} If trying to federate a holon with itself or invalid direction
|
|
1153
|
-
*/
|
|
1154
|
-
async federate(sourceHolon, targetHolon, lensName, options = {}) {
|
|
1155
|
-
const { direction = 'outbound', mode = 'reference', filter = null } = options;
|
|
1156
|
-
|
|
1157
|
-
// Validation
|
|
1158
|
-
if (sourceHolon === targetHolon) {
|
|
1159
|
-
throw new Error('Cannot federate a holon with itself');
|
|
1160
|
-
}
|
|
1161
|
-
if (!['inbound', 'outbound', 'bidirectional'].includes(direction)) {
|
|
1162
|
-
throw new Error(`Invalid direction: ${direction}. Must be 'inbound', 'outbound', or 'bidirectional'`);
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
// Store federation config
|
|
1166
|
-
await federation.setupFederation(
|
|
1167
|
-
this.client,
|
|
1168
|
-
this.config.appName,
|
|
1169
|
-
sourceHolon,
|
|
1170
|
-
targetHolon,
|
|
1171
|
-
lensName,
|
|
1172
|
-
options
|
|
1173
|
-
);
|
|
1174
|
-
|
|
1175
|
-
// Actually propagate existing data based on direction
|
|
1176
|
-
if (direction === 'outbound' || direction === 'bidirectional') {
|
|
1177
|
-
await this._propagateExistingData(sourceHolon, targetHolon, lensName, { mode, filter });
|
|
1178
|
-
}
|
|
1179
|
-
if (direction === 'inbound' || direction === 'bidirectional') {
|
|
1180
|
-
await this._propagateExistingData(targetHolon, sourceHolon, lensName, { mode, filter });
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
this._metrics.federations = (this._metrics.federations || 0) + 1;
|
|
1184
|
-
return true;
|
|
1185
|
-
}
|
|
1299
|
+
// Note: federate() and unfederate() methods are provided by withFederationMethods mixin
|
|
1186
1300
|
|
|
1187
1301
|
/**
|
|
1188
1302
|
* Propagates existing data from one holon to another.
|
|
1303
|
+
* UNIFIED MODEL: Passes sourceAuthorPubKey for capability-based federation.
|
|
1189
1304
|
*
|
|
1190
1305
|
* @private
|
|
1191
1306
|
* @param {string} fromHolon - Source holon H3 cell ID
|
|
@@ -1194,10 +1309,11 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
1194
1309
|
* @param {Object} [options={}] - Propagation options
|
|
1195
1310
|
* @param {string} [options.mode='reference'] - Mode: 'reference' or 'copy'
|
|
1196
1311
|
* @param {Function} [options.filter] - Filter function to select data
|
|
1312
|
+
* @param {string} [options.sourceAuthorPubKey] - Source author's public key (defaults to client.publicKey)
|
|
1197
1313
|
* @returns {Promise<void>}
|
|
1198
1314
|
*/
|
|
1199
1315
|
async _propagateExistingData(fromHolon, toHolon, lensName, options = {}) {
|
|
1200
|
-
const { mode = 'reference', filter = null } = options;
|
|
1316
|
+
const { mode = 'reference', filter = null, sourceAuthorPubKey } = options;
|
|
1201
1317
|
const existingData = await this.read(fromHolon, lensName, null, { resolveHolograms: false });
|
|
1202
1318
|
if (!existingData) return;
|
|
1203
1319
|
|
|
@@ -1207,6 +1323,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
1207
1323
|
if (item.hologram === true) continue;
|
|
1208
1324
|
// Apply filter if provided
|
|
1209
1325
|
if (filter && !filter(item)) continue;
|
|
1326
|
+
// UNIFIED MODEL: Pass sourceAuthorPubKey for capability-based federation
|
|
1210
1327
|
await federation.propagateData(
|
|
1211
1328
|
this.client,
|
|
1212
1329
|
this.config.appName,
|
|
@@ -1214,7 +1331,10 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
1214
1331
|
fromHolon,
|
|
1215
1332
|
toHolon,
|
|
1216
1333
|
lensName,
|
|
1217
|
-
mode
|
|
1334
|
+
mode,
|
|
1335
|
+
{
|
|
1336
|
+
sourceAuthorPubKey: sourceAuthorPubKey || this.client.publicKey,
|
|
1337
|
+
}
|
|
1218
1338
|
);
|
|
1219
1339
|
}
|
|
1220
1340
|
}
|
|
@@ -1239,25 +1359,6 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
1239
1359
|
return this._resolveHolograms(data);
|
|
1240
1360
|
}
|
|
1241
1361
|
|
|
1242
|
-
/**
|
|
1243
|
-
* Removes federation between two holons for a specific lens.
|
|
1244
|
-
*
|
|
1245
|
-
* @param {string} sourceHolon - Source holon H3 cell ID
|
|
1246
|
-
* @param {string} targetHolon - Target holon H3 cell ID
|
|
1247
|
-
* @param {string} lensName - Name of the lens
|
|
1248
|
-
* @returns {Promise<boolean>} Always returns true (idempotent operation)
|
|
1249
|
-
*/
|
|
1250
|
-
async unfederate(sourceHolon, targetHolon, lensName) {
|
|
1251
|
-
// Remove federation config for this relationship - idempotent
|
|
1252
|
-
const configPath = storage.buildPath(this.config.appName, sourceHolon, lensName, '_federation');
|
|
1253
|
-
try {
|
|
1254
|
-
await storage.deleteData(this.client, configPath);
|
|
1255
|
-
} catch (e) {
|
|
1256
|
-
// Ignore errors - already unfederated or doesn't exist
|
|
1257
|
-
}
|
|
1258
|
-
return true;
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
1362
|
/**
|
|
1262
1363
|
* Updates local override values on a hologram.
|
|
1263
1364
|
*
|
|
@@ -1369,6 +1470,7 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
1369
1470
|
async propagateData(data, sourceHolon, targetHolon, lensName, options = {}) {
|
|
1370
1471
|
// Extract mode from options, default to 'reference' for hologram creation
|
|
1371
1472
|
const mode = options.mode || 'reference';
|
|
1473
|
+
// UNIFIED MODEL: Pass sourceAuthorPubKey for capability-based federation
|
|
1372
1474
|
return federation.propagateData(
|
|
1373
1475
|
this.client,
|
|
1374
1476
|
this.config.appName,
|
|
@@ -1376,7 +1478,11 @@ class HoloSphereBase extends HoloSphereCore {
|
|
|
1376
1478
|
sourceHolon,
|
|
1377
1479
|
targetHolon,
|
|
1378
1480
|
lensName,
|
|
1379
|
-
mode
|
|
1481
|
+
mode,
|
|
1482
|
+
{
|
|
1483
|
+
sourceAuthorPubKey: options.sourceAuthorPubKey || this.client.publicKey,
|
|
1484
|
+
capability: options.capability,
|
|
1485
|
+
}
|
|
1380
1486
|
);
|
|
1381
1487
|
}
|
|
1382
1488
|
|
|
@@ -2224,10 +2330,33 @@ export {
|
|
|
2224
2330
|
// Re-export types and utilities
|
|
2225
2331
|
export { spatial, storage, schema, federation, handshake, crypto, nostrUtils, social, subscriptions, hierarchical };
|
|
2226
2332
|
|
|
2333
|
+
// Re-export federation submodules
|
|
2334
|
+
export { capabilities, requestCard, cardStorage, holonRegistry, registry };
|
|
2335
|
+
|
|
2227
2336
|
// Re-export specific utilities used in tests
|
|
2228
2337
|
export { matchScope } from './crypto/secp256k1.js';
|
|
2229
2338
|
export { createHologram } from './federation/hologram.js';
|
|
2230
2339
|
|
|
2340
|
+
// Re-export federation card functions
|
|
2341
|
+
export {
|
|
2342
|
+
createFederationCard,
|
|
2343
|
+
getVisibleLenses,
|
|
2344
|
+
getLensConfigForHandshake,
|
|
2345
|
+
toggleLens,
|
|
2346
|
+
toggleCardExpansion,
|
|
2347
|
+
dismissCard,
|
|
2348
|
+
} from './federation/request-card.js';
|
|
2349
|
+
|
|
2350
|
+
// Re-export card storage functions
|
|
2351
|
+
export {
|
|
2352
|
+
saveCard,
|
|
2353
|
+
getCard,
|
|
2354
|
+
getDisplayableCards,
|
|
2355
|
+
dismissRequest,
|
|
2356
|
+
markResponseProcessed,
|
|
2357
|
+
isResponseProcessed,
|
|
2358
|
+
} from './federation/card-storage.js';
|
|
2359
|
+
|
|
2231
2360
|
// Export AI factory function
|
|
2232
2361
|
export { createAIServices } from './ai/index.js';
|
|
2233
2362
|
|