holosphere 2.0.0-alpha1 → 2.0.0-alpha11
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/CHANGELOG.md +473 -0
- package/FEATURES.md +431 -0
- package/LICENSE +29 -166
- package/LICENSE-AGPL.md +180 -0
- package/README.md +97 -16
- 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 +2 -0
- package/dist/cjs/holosphere.cjs.map +1 -0
- package/dist/esm/holosphere.js +53 -0
- package/dist/esm/holosphere.js.map +1 -0
- package/dist/index-Bl6rM1NW.js +15104 -0
- package/dist/index-Bl6rM1NW.js.map +1 -0
- package/dist/index-Bwg3OzRM.cjs +12 -0
- package/dist/index-Bwg3OzRM.cjs.map +1 -0
- package/dist/index-D-jZhliX.js +40274 -0
- package/dist/index-D-jZhliX.js.map +1 -0
- package/dist/index-Dc6Z8Aob.cjs +18 -0
- package/dist/index-Dc6Z8Aob.cjs.map +1 -0
- package/dist/indexeddb-storage-5eiUNsHC.js +176 -0
- package/dist/indexeddb-storage-5eiUNsHC.js.map +1 -0
- package/dist/indexeddb-storage-FNFUVvTJ.cjs +2 -0
- package/dist/indexeddb-storage-FNFUVvTJ.cjs.map +1 -0
- package/dist/memory-storage-CI-gfmuG.js +91 -0
- package/dist/memory-storage-CI-gfmuG.js.map +1 -0
- package/dist/memory-storage-DMt36uZO.cjs +2 -0
- package/dist/memory-storage-DMt36uZO.cjs.map +1 -0
- package/dist/secp256k1-CEwJNcfV.js +1890 -0
- package/dist/secp256k1-CEwJNcfV.js.map +1 -0
- package/dist/secp256k1-CiEONUnj.cjs +12 -0
- package/dist/secp256k1-CiEONUnj.cjs.map +1 -0
- package/docs/CONTRACTS.md +797 -0
- package/docs/FOSDEM_PROPOSAL.md +388 -0
- package/docs/LOCALFIRST.md +266 -0
- package/docs/api/ai_aggregation.js.html +333 -0
- package/docs/api/ai_breakdown.js.html +524 -0
- package/docs/api/ai_classifier.js.html +231 -0
- package/docs/api/ai_council.js.html +246 -0
- package/docs/api/ai_embeddings.js.html +304 -0
- package/docs/api/ai_federation-ai.js.html +338 -0
- package/docs/api/ai_h3-ai.js.html +970 -0
- package/docs/api/ai_index.js.html +124 -0
- package/docs/api/ai_json-ops.js.html +241 -0
- package/docs/api/ai_llm-service.js.html +239 -0
- package/docs/api/ai_nl-query.js.html +236 -0
- package/docs/api/ai_relationships.js.html +367 -0
- package/docs/api/ai_schema-extractor.js.html +235 -0
- package/docs/api/ai_spatial.js.html +307 -0
- package/docs/api/ai_tts.js.html +214 -0
- package/docs/api/content_social-protocols.js.html +180 -0
- package/docs/api/core_holosphere.js.html +757 -0
- package/docs/api/crypto_nostr-utils.js.html +306 -0
- package/docs/api/crypto_secp256k1.js.html +267 -0
- package/docs/api/data/search.json +1 -0
- package/docs/api/federation_discovery.js.html +337 -0
- package/docs/api/federation_handshake.js.html +478 -0
- package/docs/api/federation_hologram.js.html +1053 -0
- package/docs/api/federation_registry.js.html +389 -0
- package/docs/api/fonts/Inconsolata-Regular.ttf +0 -0
- package/docs/api/fonts/OpenSans-Regular.ttf +0 -0
- package/docs/api/fonts/WorkSans-Bold.ttf +0 -0
- package/docs/api/global.html +3 -0
- package/docs/api/hierarchical_upcast.js.html +128 -0
- package/docs/api/index.html +265 -0
- package/docs/api/index.js.html +1868 -0
- package/docs/api/lib_ai-methods.js.html +660 -0
- package/docs/api/lib_contract-methods.js.html +445 -0
- package/docs/api/lib_errors.js.html +56 -0
- package/docs/api/lib_federation-methods.js.html +348 -0
- package/docs/api/lib_index.js.html +33 -0
- package/docs/api/module-ai.html +5 -0
- package/docs/api/module-ai_aggregation-SmartAggregation.html +6 -0
- package/docs/api/module-ai_aggregation.SmartAggregation.html +3 -0
- package/docs/api/module-ai_aggregation.html +3 -0
- package/docs/api/module-ai_breakdown-TaskBreakdown.html +5 -0
- package/docs/api/module-ai_breakdown.TaskBreakdown.html +3 -0
- package/docs/api/module-ai_breakdown.html +3 -0
- package/docs/api/module-ai_classifier-Classifier.html +6 -0
- package/docs/api/module-ai_classifier.Classifier.html +3 -0
- package/docs/api/module-ai_classifier.html +3 -0
- package/docs/api/module-ai_council-Council.html +6 -0
- package/docs/api/module-ai_council.Council.html +3 -0
- package/docs/api/module-ai_council.html +3 -0
- package/docs/api/module-ai_embeddings-Embeddings.html +5 -0
- package/docs/api/module-ai_embeddings.Embeddings.html +3 -0
- package/docs/api/module-ai_embeddings.html +3 -0
- package/docs/api/module-ai_federation-ai-FederationAdvisor.html +6 -0
- package/docs/api/module-ai_federation-ai.FederationAdvisor.html +3 -0
- package/docs/api/module-ai_federation-ai.html +3 -0
- package/docs/api/module-ai_h3-ai-H3AI.html +6 -0
- package/docs/api/module-ai_h3-ai.H3AI.html +3 -0
- package/docs/api/module-ai_h3-ai.html +3 -0
- package/docs/api/module-ai_json-ops-JSONOps.html +5 -0
- package/docs/api/module-ai_json-ops.JSONOps.html +3 -0
- package/docs/api/module-ai_json-ops.html +3 -0
- package/docs/api/module-ai_llm-service-LLMService.html +5 -0
- package/docs/api/module-ai_llm-service.LLMService.html +3 -0
- package/docs/api/module-ai_llm-service.html +3 -0
- package/docs/api/module-ai_nl-query-NLQuery.html +5 -0
- package/docs/api/module-ai_nl-query.NLQuery.html +3 -0
- package/docs/api/module-ai_nl-query.html +3 -0
- package/docs/api/module-ai_relationships-RelationshipDiscovery.html +6 -0
- package/docs/api/module-ai_relationships.RelationshipDiscovery.html +3 -0
- package/docs/api/module-ai_relationships.html +3 -0
- package/docs/api/module-ai_schema-extractor-SchemaExtractor.html +5 -0
- package/docs/api/module-ai_schema-extractor.SchemaExtractor.html +3 -0
- package/docs/api/module-ai_schema-extractor.html +3 -0
- package/docs/api/module-ai_spatial-SpatialAnalysis.html +6 -0
- package/docs/api/module-ai_spatial.SpatialAnalysis.html +3 -0
- package/docs/api/module-ai_spatial.html +3 -0
- package/docs/api/module-ai_tts-TTS.html +5 -0
- package/docs/api/module-ai_tts.TTS.html +3 -0
- package/docs/api/module-ai_tts.html +3 -0
- package/docs/api/module-content_social-protocols.html +3 -0
- package/docs/api/module-core_holosphere-HoloSphere.html +6 -0
- package/docs/api/module-core_holosphere.HoloSphere.html +3 -0
- package/docs/api/module-core_holosphere.html +3 -0
- package/docs/api/module-crypto_nostr-utils.html +3 -0
- package/docs/api/module-crypto_secp256k1.html +3 -0
- package/docs/api/module-federation_hologram.html +3 -0
- package/docs/api/module-hierarchical_upcast.html +3 -0
- package/docs/api/module-holosphere-HoloSphereBase.html +3 -0
- package/docs/api/module-holosphere.html +3 -0
- package/docs/api/module-lib_ai-methods.html +3 -0
- package/docs/api/module-lib_contract-methods.html +3 -0
- package/docs/api/module-lib_errors-AuthorizationError.html +3 -0
- package/docs/api/module-lib_errors-ValidationError.html +3 -0
- package/docs/api/module-lib_errors.AuthorizationError.html +3 -0
- package/docs/api/module-lib_errors.ValidationError.html +3 -0
- package/docs/api/module-lib_errors.html +3 -0
- package/docs/api/module-lib_federation-methods.html +3 -0
- package/docs/api/module-lib_index.html +3 -0
- package/docs/api/module-schema_validator-ValidationError.html +3 -0
- package/docs/api/module-schema_validator.ValidationError.html +3 -0
- package/docs/api/module-schema_validator.html +3 -0
- package/docs/api/module-spatial_h3-operations.html +4 -0
- package/docs/api/module-storage_backend-factory.BackendFactory.html +3 -0
- package/docs/api/module-storage_backend-factory.html +3 -0
- package/docs/api/module-storage_backend-interface-StorageBackend.html +3 -0
- package/docs/api/module-storage_backend-interface.StorageBackend.html +3 -0
- package/docs/api/module-storage_backend-interface.html +3 -0
- package/docs/api/module-storage_backends_activitypub-backend-ActivityPubBackend.html +7 -0
- package/docs/api/module-storage_backends_activitypub-backend.ActivityPubBackend.html +3 -0
- package/docs/api/module-storage_backends_activitypub-backend.html +3 -0
- package/docs/api/module-storage_backends_activitypub_server-ActivityPubServer.html +8 -0
- package/docs/api/module-storage_backends_activitypub_server.ActivityPubServer.html +3 -0
- package/docs/api/module-storage_backends_activitypub_server.html +3 -0
- package/docs/api/module-storage_backends_gundb-backend-GunDBBackend.html +7 -0
- package/docs/api/module-storage_backends_gundb-backend.GunDBBackend.html +3 -0
- package/docs/api/module-storage_backends_gundb-backend.html +3 -0
- package/docs/api/module-storage_backends_nostr-backend-NostrBackend.html +8 -0
- package/docs/api/module-storage_backends_nostr-backend.NostrBackend.html +3 -0
- package/docs/api/module-storage_backends_nostr-backend.html +3 -0
- package/docs/api/module-storage_filesystem-storage-FileSystemStorage.html +5 -0
- package/docs/api/module-storage_filesystem-storage-browser-FileSystemStorage.html +3 -0
- package/docs/api/module-storage_filesystem-storage-browser.FileSystemStorage.html +3 -0
- package/docs/api/module-storage_filesystem-storage-browser.html +3 -0
- package/docs/api/module-storage_filesystem-storage.FileSystemStorage.html +3 -0
- package/docs/api/module-storage_filesystem-storage.html +3 -0
- package/docs/api/module-storage_global-tables.html +3 -0
- package/docs/api/module-storage_gun-async.html +3 -0
- package/docs/api/module-storage_gun-auth-GunAuth.html +5 -0
- package/docs/api/module-storage_gun-auth.GunAuth.html +3 -0
- package/docs/api/module-storage_gun-auth.html +3 -0
- package/docs/api/module-storage_gun-federation.html +3 -0
- package/docs/api/module-storage_gun-references-GunReferenceHandler.html +5 -0
- package/docs/api/module-storage_gun-references.GunReferenceHandler.html +3 -0
- package/docs/api/module-storage_gun-references.html +3 -0
- package/docs/api/module-storage_gun-schema-GunSchemaValidator.html +5 -0
- package/docs/api/module-storage_gun-schema.GunSchemaValidator.html +3 -0
- package/docs/api/module-storage_gun-schema.html +3 -0
- package/docs/api/module-storage_gun-wrapper.html +11 -0
- package/docs/api/module-storage_indexeddb-storage-IndexedDBStorage.html +5 -0
- package/docs/api/module-storage_indexeddb-storage.IndexedDBStorage.html +3 -0
- package/docs/api/module-storage_indexeddb-storage.html +3 -0
- package/docs/api/module-storage_key-storage-simple.html +3 -0
- package/docs/api/module-storage_key-storage.html +4 -0
- package/docs/api/module-storage_memory-storage-MemoryStorage.html +5 -0
- package/docs/api/module-storage_memory-storage.MemoryStorage.html +3 -0
- package/docs/api/module-storage_memory-storage.html +3 -0
- package/docs/api/module-storage_migration-MigrationTool.html +6 -0
- package/docs/api/module-storage_migration.MigrationTool.html +3 -0
- package/docs/api/module-storage_migration.html +3 -0
- package/docs/api/module-storage_nostr-async.html +18 -0
- package/docs/api/module-storage_nostr-client-LRUCache.html +3 -0
- package/docs/api/module-storage_nostr-client-NostrClient.html +7 -0
- package/docs/api/module-storage_nostr-client.NostrClient.html +15 -0
- package/docs/api/module-storage_nostr-client.html +6 -0
- package/docs/api/module-storage_nostr-wrapper.html +3 -0
- package/docs/api/module-storage_outbox-queue-OutboxQueue.html +4 -0
- package/docs/api/module-storage_outbox-queue.OutboxQueue.html +3 -0
- package/docs/api/module-storage_outbox-queue.html +3 -0
- package/docs/api/module-storage_persistent-storage-PersistentStorage.html +3 -0
- package/docs/api/module-storage_persistent-storage.html +4 -0
- package/docs/api/module-storage_sync-service-SyncService.html +5 -0
- package/docs/api/module-storage_sync-service.SyncService.html +3 -0
- package/docs/api/module-storage_sync-service.html +3 -0
- package/docs/api/module-storage_unified-storage.html +3 -0
- package/docs/api/module-subscriptions_manager.SubscriptionRegistry.html +3 -0
- package/docs/api/module-subscriptions_manager.html +3 -0
- package/docs/api/schema_validator.js.html +113 -0
- package/docs/api/scripts/core.js +726 -0
- package/docs/api/scripts/core.min.js +23 -0
- package/docs/api/scripts/resize.js +90 -0
- package/docs/api/scripts/search.js +265 -0
- package/docs/api/scripts/search.min.js +6 -0
- package/docs/api/scripts/third-party/Apache-License-2.0.txt +202 -0
- package/docs/api/scripts/third-party/fuse.js +9 -0
- package/docs/api/scripts/third-party/hljs-line-num-original.js +369 -0
- package/docs/api/scripts/third-party/hljs-line-num.js +1 -0
- package/docs/api/scripts/third-party/hljs-original.js +5171 -0
- package/docs/api/scripts/third-party/hljs.js +1 -0
- package/docs/api/scripts/third-party/popper.js +5 -0
- package/docs/api/scripts/third-party/tippy.js +1 -0
- package/docs/api/scripts/third-party/tocbot.js +672 -0
- package/docs/api/scripts/third-party/tocbot.min.js +1 -0
- package/docs/api/spatial_h3-operations.js.html +129 -0
- package/docs/api/storage_backend-factory.js.html +133 -0
- package/docs/api/storage_backend-interface.js.html +164 -0
- package/docs/api/storage_backends_activitypub-backend.js.html +298 -0
- package/docs/api/storage_backends_activitypub_server.js.html +678 -0
- package/docs/api/storage_backends_gundb-backend.js.html +878 -0
- package/docs/api/storage_backends_nostr-backend.js.html +254 -0
- package/docs/api/storage_filesystem-storage-browser.js.html +83 -0
- package/docs/api/storage_filesystem-storage.js.html +207 -0
- package/docs/api/storage_global-tables.js.html +116 -0
- package/docs/api/storage_gun-async.js.html +344 -0
- package/docs/api/storage_gun-auth.js.html +376 -0
- package/docs/api/storage_gun-federation.js.html +788 -0
- package/docs/api/storage_gun-references.js.html +212 -0
- package/docs/api/storage_gun-schema.js.html +309 -0
- package/docs/api/storage_gun-wrapper.js.html +645 -0
- package/docs/api/storage_indexeddb-storage.js.html +224 -0
- package/docs/api/storage_key-storage-simple.js.html +102 -0
- package/docs/api/storage_key-storage.js.html +171 -0
- package/docs/api/storage_memory-storage.js.html +128 -0
- package/docs/api/storage_migration.js.html +354 -0
- package/docs/api/storage_nostr-async.js.html +1076 -0
- package/docs/api/storage_nostr-client.js.html +1598 -0
- package/docs/api/storage_nostr-wrapper.js.html +218 -0
- package/docs/api/storage_outbox-queue.js.html +248 -0
- package/docs/api/storage_persistent-storage.js.html +160 -0
- package/docs/api/storage_sync-service.js.html +201 -0
- package/docs/api/storage_unified-storage.js.html +157 -0
- package/docs/api/styles/clean-jsdoc-theme-base.css +1159 -0
- package/docs/api/styles/clean-jsdoc-theme-dark.css +412 -0
- package/docs/api/styles/clean-jsdoc-theme-light.css +482 -0
- package/docs/api/styles/clean-jsdoc-theme-scrollbar.css +30 -0
- package/docs/api/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
- package/docs/api/styles/clean-jsdoc-theme.min.css +1 -0
- package/docs/api/subscriptions_manager.js.html +162 -0
- package/docs/contracts/api-interface.md +793 -0
- package/docs/data-model.md +476 -0
- package/docs/gun-async-usage.md +338 -0
- package/docs/plan.md +349 -0
- package/docs/quickstart.md +674 -0
- package/docs/research.md +362 -0
- package/docs/spec.md +244 -0
- package/docs/storage-backends.md +326 -0
- package/docs/tasks.md +947 -0
- package/examples/demo.html +47 -0
- package/examples/holosphere-widget.js +1242 -0
- package/examples/widget-demo.html +274 -0
- package/examples/widget.html +703 -0
- package/jsdoc.json +26 -0
- package/package.json +25 -7
- package/src/ai/aggregation.js +13 -2
- package/src/ai/breakdown.js +12 -2
- package/src/ai/classifier.js +14 -3
- package/src/ai/council.js +22 -7
- package/src/ai/embeddings.js +37 -15
- package/src/ai/federation-ai.js +13 -2
- package/src/ai/h3-ai.js +14 -2
- package/src/ai/index.js +16 -7
- package/src/ai/json-ops.js +18 -5
- package/src/ai/llm-service.js +62 -31
- package/src/ai/nl-query.js +12 -2
- package/src/ai/relationships.js +13 -2
- package/src/ai/schema-extractor.js +24 -10
- package/src/ai/spatial.js +13 -2
- package/src/ai/tts.js +25 -8
- package/src/cdn-entry.js +22 -0
- package/src/content/social-protocols.js +34 -25
- package/src/contracts/abis/Appreciative.json +1280 -0
- package/src/contracts/abis/AppreciativeFactory.json +101 -0
- package/src/contracts/abis/Bundle.json +1438 -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 +403 -0
- package/src/contracts/deployer.js +500 -0
- package/src/contracts/event-listener.js +539 -0
- package/src/contracts/holon-contracts.js +359 -0
- package/src/contracts/index.js +82 -0
- package/src/contracts/networks.js +229 -0
- package/src/contracts/operations.js +687 -0
- package/src/contracts/queries.js +638 -0
- package/src/core/holosphere.js +487 -6
- package/src/crypto/nostr-utils.js +303 -0
- package/src/crypto/secp256k1.js +7 -2
- package/src/federation/handshake.js +475 -0
- package/src/federation/hologram.js +117 -3
- package/src/hierarchical/upcast.js +40 -25
- package/src/index.js +1547 -1912
- package/src/lib/ai-methods.js +657 -0
- package/src/lib/contract-methods.js +442 -0
- package/src/lib/errors.js +53 -0
- package/src/lib/federation-methods.js +345 -0
- package/src/lib/index.js +30 -0
- package/src/schema/validator.js +22 -3
- package/src/spatial/h3-operations.js +19 -3
- package/src/storage/backend-factory.js +7 -2
- package/src/storage/backend-interface.js +21 -2
- package/src/storage/backends/activitypub/server.js +25 -3
- package/src/storage/backends/activitypub-backend.js +25 -2
- package/src/storage/backends/gundb-backend.js +692 -50
- package/src/storage/backends/nostr-backend.js +116 -1
- package/src/storage/filesystem-storage-browser.js +42 -2
- package/src/storage/filesystem-storage.js +72 -5
- package/src/storage/global-tables.js +35 -3
- package/src/storage/gun-async.js +75 -15
- package/src/storage/gun-auth.js +373 -0
- package/src/storage/gun-federation.js +785 -0
- package/src/storage/gun-references.js +209 -0
- package/src/storage/gun-schema.js +306 -0
- package/src/storage/gun-wrapper.js +475 -54
- package/src/storage/indexeddb-storage.js +112 -13
- package/src/storage/key-storage-simple.js +32 -9
- package/src/storage/key-storage.js +45 -13
- package/src/storage/memory-storage.js +68 -2
- package/src/storage/migration.js +20 -7
- package/src/storage/nostr-async.js +412 -122
- package/src/storage/nostr-client.js +749 -76
- package/src/storage/nostr-wrapper.js +6 -2
- package/src/storage/outbox-queue.js +55 -18
- package/src/storage/persistent-storage.js +62 -14
- package/src/storage/sync-service.js +51 -17
- package/src/storage/unified-storage.js +154 -0
- package/src/subscriptions/manager.js +34 -17
- package/types/index.d.ts +133 -0
- package/vite.config.cdn.js +60 -0
- package/tests/unit/ai/aggregation.test.js +0 -295
- package/tests/unit/ai/breakdown.test.js +0 -446
- package/tests/unit/ai/classifier.test.js +0 -294
- package/tests/unit/ai/council.test.js +0 -262
- package/tests/unit/ai/embeddings.test.js +0 -384
- package/tests/unit/ai/federation-ai.test.js +0 -344
- package/tests/unit/ai/h3-ai.test.js +0 -458
- package/tests/unit/ai/index.test.js +0 -304
- package/tests/unit/ai/json-ops.test.js +0 -307
- package/tests/unit/ai/llm-service.test.js +0 -390
- package/tests/unit/ai/nl-query.test.js +0 -383
- package/tests/unit/ai/relationships.test.js +0 -311
- package/tests/unit/ai/schema-extractor.test.js +0 -384
- package/tests/unit/ai/spatial.test.js +0 -279
- package/tests/unit/ai/tts.test.js +0 -279
- package/tests/unit/content.test.js +0 -332
- package/tests/unit/contract/core.test.js +0 -88
- package/tests/unit/contract/crypto.test.js +0 -198
- package/tests/unit/contract/data.test.js +0 -223
- package/tests/unit/contract/federation.test.js +0 -181
- package/tests/unit/contract/hierarchical.test.js +0 -113
- package/tests/unit/contract/schema.test.js +0 -114
- package/tests/unit/contract/social.test.js +0 -217
- package/tests/unit/contract/spatial.test.js +0 -110
- package/tests/unit/contract/subscriptions.test.js +0 -128
- package/tests/unit/contract/utils.test.js +0 -159
- package/tests/unit/core.test.js +0 -152
- package/tests/unit/crypto.test.js +0 -328
- package/tests/unit/federation.test.js +0 -234
- package/tests/unit/gun-async.test.js +0 -252
- package/tests/unit/hierarchical.test.js +0 -399
- package/tests/unit/integration/scenario-01-geographic-storage.test.js +0 -74
- package/tests/unit/integration/scenario-02-federation.test.js +0 -76
- package/tests/unit/integration/scenario-03-subscriptions.test.js +0 -102
- package/tests/unit/integration/scenario-04-validation.test.js +0 -129
- package/tests/unit/integration/scenario-05-hierarchy.test.js +0 -125
- package/tests/unit/integration/scenario-06-social.test.js +0 -135
- package/tests/unit/integration/scenario-07-persistence.test.js +0 -130
- package/tests/unit/integration/scenario-08-authorization.test.js +0 -161
- package/tests/unit/integration/scenario-09-cross-dimensional.test.js +0 -139
- package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +0 -357
- package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +0 -410
- package/tests/unit/integration/scenario-12-capability-federated-read.test.js +0 -719
- package/tests/unit/performance/benchmark.test.js +0 -85
- package/tests/unit/schema.test.js +0 -213
- package/tests/unit/spatial.test.js +0 -158
- package/tests/unit/storage.test.js +0 -195
- package/tests/unit/subscriptions.test.js +0 -328
- package/tests/unit/test-data-permanence-debug.js +0 -197
- package/tests/unit/test-data-permanence.js +0 -340
- package/tests/unit/test-key-persistence-fixed.js +0 -148
- package/tests/unit/test-key-persistence.js +0 -172
- package/tests/unit/test-relay-permanence.js +0 -376
- package/tests/unit/test-second-node.js +0 -95
- package/tests/unit/test-simple-write.js +0 -89
- /package/{cleanup-test-data.js → scripts/cleanup-test-data.js} +0 -0
- /package/{test-ai-real-api.js → scripts/test-ai-real-api.js} +0 -0
package/src/index.js
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HoloSphere - Holonic Geospatial Communication Infrastructure
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview HoloSphere - Holonic Geospatial Communication Infrastructure
|
|
3
|
+
*
|
|
4
|
+
* This is the main entry point for the HoloSphere library, providing a comprehensive
|
|
5
|
+
* distributed P2P geospatial communication system. It combines H3 hexagonal indexing
|
|
6
|
+
* for spatial organization, Nostr protocol for distributed storage, smart contracts
|
|
7
|
+
* for fund distribution, and AI services for intelligent data processing.
|
|
8
|
+
*
|
|
9
|
+
* @module holosphere
|
|
10
|
+
* @author HoloSphere Team
|
|
11
|
+
* @license MIT
|
|
4
12
|
*/
|
|
5
13
|
|
|
6
14
|
import { HoloSphere as HoloSphereCore } from './core/holosphere.js';
|
|
7
15
|
import * as spatial from './spatial/h3-operations.js';
|
|
8
|
-
import * as storage from './storage/
|
|
16
|
+
import * as storage from './storage/unified-storage.js';
|
|
17
|
+
import * as nostrStorage from './storage/nostr-wrapper.js';
|
|
9
18
|
import * as nostrAsync from './storage/nostr-async.js';
|
|
10
19
|
import * as globalTables from './storage/global-tables.js';
|
|
11
20
|
import * as schema from './schema/validator.js';
|
|
12
21
|
import { ValidationError } from './schema/validator.js';
|
|
13
22
|
import * as federation from './federation/hologram.js';
|
|
23
|
+
import * as handshake from './federation/handshake.js';
|
|
14
24
|
import * as crypto from './crypto/secp256k1.js';
|
|
25
|
+
import * as nostrUtils from './crypto/nostr-utils.js';
|
|
15
26
|
import * as social from './content/social-protocols.js';
|
|
16
27
|
import * as subscriptions from './subscriptions/manager.js';
|
|
17
28
|
import * as hierarchical from './hierarchical/upcast.js';
|
|
18
|
-
import * as registry from './federation/registry.js';
|
|
19
|
-
import * as discovery from './federation/discovery.js';
|
|
20
29
|
|
|
21
30
|
// AI Module imports
|
|
22
31
|
import { LLMService } from './ai/llm-service.js';
|
|
@@ -34,16 +43,72 @@ import { RelationshipDiscovery } from './ai/relationships.js';
|
|
|
34
43
|
import { TaskBreakdown } from './ai/breakdown.js';
|
|
35
44
|
import { H3AI } from './ai/h3-ai.js';
|
|
36
45
|
|
|
46
|
+
// Contracts Module imports
|
|
47
|
+
import { ChainManager } from './contracts/chain-manager.js';
|
|
48
|
+
import { ContractDeployer, ABIs as ContractABIs } from './contracts/deployer.js';
|
|
49
|
+
import { HolonContracts } from './contracts/holon-contracts.js';
|
|
50
|
+
import { ContractOperations } from './contracts/operations.js';
|
|
51
|
+
import { EventListener, SANKEY_EVENTS } from './contracts/event-listener.js';
|
|
52
|
+
import { ContractQueries } from './contracts/queries.js';
|
|
53
|
+
import * as networks from './contracts/networks.js';
|
|
54
|
+
|
|
55
|
+
// Mixin imports
|
|
56
|
+
import { withAIMethods } from './lib/ai-methods.js';
|
|
57
|
+
import { withContractMethods } from './lib/contract-methods.js';
|
|
58
|
+
import { withFederationMethods } from './lib/federation-methods.js';
|
|
59
|
+
import { AuthorizationError } from './lib/errors.js';
|
|
60
|
+
|
|
37
61
|
/**
|
|
38
|
-
*
|
|
62
|
+
* Base HoloSphere class with core data operations.
|
|
63
|
+
* Extends HoloSphereCore with schema validation, caching, subscriptions, and AI capabilities.
|
|
64
|
+
*
|
|
65
|
+
* @class HoloSphereBase
|
|
66
|
+
* @extends HoloSphereCore
|
|
39
67
|
*/
|
|
40
|
-
|
|
68
|
+
class HoloSphereBase extends HoloSphereCore {
|
|
69
|
+
/**
|
|
70
|
+
* Creates a new HoloSphereBase instance.
|
|
71
|
+
*
|
|
72
|
+
* @param {Object} config - Configuration options
|
|
73
|
+
* @param {string} config.appName - Application namespace for data isolation
|
|
74
|
+
* @param {string} [config.backend='nostr'] - Storage backend type ('nostr', 'gundb', 'activitypub')
|
|
75
|
+
* @param {string[]} [config.relays] - Nostr relay URLs for distributed storage
|
|
76
|
+
* @param {string} [config.openaiKey] - OpenAI API key for AI services
|
|
77
|
+
* @param {Object} [config.aiOptions] - AI service configuration
|
|
78
|
+
* @param {string} [config.aiOptions.model] - OpenAI model to use
|
|
79
|
+
* @param {number} [config.aiOptions.temperature] - Temperature for AI responses
|
|
80
|
+
*/
|
|
41
81
|
constructor(config) {
|
|
42
82
|
super(config);
|
|
83
|
+
/** @type {Map<string, {schema: Object, strict: boolean, timestamp: number}>} */
|
|
43
84
|
this.schemas = new Map();
|
|
85
|
+
/** @type {Map<string, any>} */
|
|
86
|
+
this._cache = new Map();
|
|
87
|
+
/** @type {subscriptions.SubscriptionRegistry} */
|
|
44
88
|
this.subscriptionRegistry = new subscriptions.SubscriptionRegistry();
|
|
45
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Write cache for optimistic updates.
|
|
92
|
+
* Stores data immediately so reads return fresh data before relay sync completes.
|
|
93
|
+
* @type {Map<string, {data: Object, timestamp: number}>}
|
|
94
|
+
*/
|
|
95
|
+
this._writeCache = new Map();
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Pending write promises for deduplication.
|
|
99
|
+
* @type {Map<string, Promise>}
|
|
100
|
+
*/
|
|
101
|
+
this._pendingWrites = new Map();
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Delete cache for optimistic deletes.
|
|
105
|
+
* Tracks paths that have been deleted so reads don't return stale storage data.
|
|
106
|
+
* @type {Set<string>}
|
|
107
|
+
*/
|
|
108
|
+
this._deleteCache = new Set();
|
|
109
|
+
|
|
46
110
|
// Initialize AI services if openaiKey is provided or OPENAI_API_KEY env var is set
|
|
111
|
+
/** @type {Object|null} */
|
|
47
112
|
this._ai = null;
|
|
48
113
|
const openaiKey = config.openaiKey || this._getEnv('OPENAI_API_KEY');
|
|
49
114
|
if (openaiKey) {
|
|
@@ -54,11 +119,18 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
54
119
|
};
|
|
55
120
|
this._initializeAI(openaiKey, aiOptions);
|
|
56
121
|
}
|
|
122
|
+
|
|
123
|
+
// Contracts module (lazy initialized)
|
|
124
|
+
/** @type {Object|null} */
|
|
125
|
+
this._contracts = null;
|
|
57
126
|
}
|
|
58
127
|
|
|
59
128
|
/**
|
|
60
|
-
*
|
|
129
|
+
* Gets an environment variable value (Node.js only).
|
|
130
|
+
*
|
|
61
131
|
* @private
|
|
132
|
+
* @param {string} name - Environment variable name
|
|
133
|
+
* @returns {string|undefined} Environment variable value or undefined
|
|
62
134
|
*/
|
|
63
135
|
_getEnv(name) {
|
|
64
136
|
if (typeof process !== 'undefined' && process.env) {
|
|
@@ -68,8 +140,11 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
68
140
|
}
|
|
69
141
|
|
|
70
142
|
/**
|
|
71
|
-
*
|
|
143
|
+
* Parses a string value to a float.
|
|
144
|
+
*
|
|
72
145
|
* @private
|
|
146
|
+
* @param {string|undefined|null} value - Value to parse
|
|
147
|
+
* @returns {number|undefined} Parsed float or undefined if invalid
|
|
73
148
|
*/
|
|
74
149
|
_parseFloat(value) {
|
|
75
150
|
if (value === undefined || value === null) return undefined;
|
|
@@ -78,28 +153,29 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
78
153
|
}
|
|
79
154
|
|
|
80
155
|
/**
|
|
81
|
-
*
|
|
156
|
+
* Initializes AI services with the provided API key.
|
|
157
|
+
*
|
|
82
158
|
* @private
|
|
159
|
+
* @param {string} apiKey - OpenAI API key
|
|
160
|
+
* @param {Object} [options={}] - AI configuration options
|
|
161
|
+
* @param {string} [options.model] - OpenAI model to use
|
|
162
|
+
* @param {number} [options.temperature] - Temperature for AI responses
|
|
83
163
|
*/
|
|
84
164
|
_initializeAI(apiKey, options = {}) {
|
|
85
|
-
// Build LLM options from config
|
|
86
165
|
const llmOptions = {
|
|
87
166
|
...options.llm,
|
|
88
167
|
model: options.model || options.llm?.model,
|
|
89
168
|
temperature: options.temperature ?? options.llm?.temperature,
|
|
90
169
|
};
|
|
91
170
|
|
|
92
|
-
// Create shared LLM service
|
|
93
171
|
this._ai = {
|
|
94
172
|
llm: new LLMService(apiKey, llmOptions),
|
|
95
173
|
};
|
|
96
174
|
|
|
97
|
-
// Create OpenAI client for embeddings and TTS
|
|
98
175
|
const OpenAI = require('openai').default;
|
|
99
176
|
const openai = new OpenAI({ apiKey });
|
|
100
177
|
this._ai.openai = openai;
|
|
101
178
|
|
|
102
|
-
// Create all services
|
|
103
179
|
this._ai.embeddings = new Embeddings(openai, this);
|
|
104
180
|
this._ai.schemaExtractor = new SchemaExtractor(this._ai.llm);
|
|
105
181
|
this._ai.jsonOps = new JSONOps(this._ai.llm);
|
|
@@ -116,574 +192,1021 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
116
192
|
}
|
|
117
193
|
|
|
118
194
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
195
|
+
* Checks if AI services are initialized.
|
|
196
|
+
*
|
|
197
|
+
* @returns {boolean} True if AI services are available
|
|
121
198
|
*/
|
|
122
199
|
hasAI() {
|
|
123
200
|
return this._ai !== null;
|
|
124
201
|
}
|
|
125
202
|
|
|
126
203
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
204
|
+
* Gets the initialized AI services object.
|
|
205
|
+
*
|
|
206
|
+
* @returns {Object|null} AI services object containing llm, embeddings, etc., or null if not initialized
|
|
129
207
|
*/
|
|
130
208
|
getAIServices() {
|
|
131
209
|
return this._ai;
|
|
132
210
|
}
|
|
133
211
|
|
|
134
212
|
// === Spatial Operations ===
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Converts geographic coordinates to an H3 holon ID.
|
|
216
|
+
*
|
|
217
|
+
* @param {number} lat - Latitude (-90 to 90)
|
|
218
|
+
* @param {number} lng - Longitude (-180 to 180)
|
|
219
|
+
* @param {number} resolution - H3 resolution level (0-15)
|
|
220
|
+
* @returns {Promise<string>} H3 cell ID representing the holon
|
|
221
|
+
*/
|
|
135
222
|
async toHolon(lat, lng, resolution) {
|
|
136
223
|
return spatial.toHolon(lat, lng, resolution);
|
|
137
224
|
}
|
|
138
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Gets all parent holons at higher resolutions.
|
|
228
|
+
*
|
|
229
|
+
* @param {string} holonId - H3 cell ID
|
|
230
|
+
* @param {number} [maxResolution] - Maximum resolution to traverse up to
|
|
231
|
+
* @returns {Promise<string[]>} Array of parent H3 cell IDs
|
|
232
|
+
*/
|
|
139
233
|
async getParents(holonId, maxResolution) {
|
|
140
234
|
return spatial.getParents(holonId, maxResolution);
|
|
141
235
|
}
|
|
142
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Gets all child holons at the next resolution level.
|
|
239
|
+
*
|
|
240
|
+
* @param {string} holonId - H3 cell ID
|
|
241
|
+
* @returns {Promise<string[]>} Array of child H3 cell IDs
|
|
242
|
+
*/
|
|
143
243
|
async getChildren(holonId) {
|
|
144
244
|
return spatial.getChildren(holonId);
|
|
145
245
|
}
|
|
146
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Validates if a string is a valid H3 cell ID.
|
|
249
|
+
*
|
|
250
|
+
* @param {string} holonId - String to validate
|
|
251
|
+
* @returns {boolean} True if valid H3 cell ID
|
|
252
|
+
*/
|
|
147
253
|
isValidH3(holonId) {
|
|
148
254
|
return spatial.isValidH3(holonId);
|
|
149
255
|
}
|
|
150
256
|
|
|
151
257
|
// === Data Operations ===
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Writes data to a specific holon and lens.
|
|
261
|
+
* By default, write is optimistic (non-blocking) - returns immediately after caching locally.
|
|
262
|
+
* Data is synced to relays in the background.
|
|
263
|
+
*
|
|
264
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
265
|
+
* @param {string} lensName - Name of the lens (data category)
|
|
266
|
+
* @param {Object} data - Data object to write (must have or will be assigned an id)
|
|
267
|
+
* @param {Object} [options={}] - Write options
|
|
268
|
+
* @param {string} [options.capabilityToken] - Capability token for authorization
|
|
269
|
+
* @param {boolean} [options.validate=true] - Whether to validate against schema
|
|
270
|
+
* @param {boolean} [options.strict] - Override schema strict mode
|
|
271
|
+
* @param {boolean} [options.autoPropagate=true] - Whether to propagate to federated holons
|
|
272
|
+
* @param {Object} [options.propagationOptions] - Options for propagation
|
|
273
|
+
* @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
|
|
274
|
+
* @returns {Promise<boolean>} True if write succeeded (or queued for optimistic writes)
|
|
275
|
+
* @throws {ValidationError} If holonId, lensName, or data is invalid
|
|
276
|
+
* @throws {AuthorizationError} If capability token is invalid
|
|
277
|
+
*/
|
|
152
278
|
async write(holonId, lensName, data, options = {}) {
|
|
153
|
-
//
|
|
154
|
-
if (
|
|
279
|
+
// Auto-convert to strings for convenience
|
|
280
|
+
if (holonId != null) holonId = String(holonId);
|
|
281
|
+
if (lensName != null) lensName = String(lensName);
|
|
282
|
+
|
|
283
|
+
if (!holonId) {
|
|
155
284
|
throw new ValidationError('ValidationError: holonId must be a non-empty string');
|
|
156
285
|
}
|
|
157
|
-
if (!lensName
|
|
286
|
+
if (!lensName) {
|
|
158
287
|
throw new ValidationError('ValidationError: lensName must be a non-empty string');
|
|
159
288
|
}
|
|
160
289
|
if (!data || typeof data !== 'object') {
|
|
161
290
|
throw new ValidationError('ValidationError: data must be an object');
|
|
162
291
|
}
|
|
163
292
|
|
|
164
|
-
// Check authorization if capability token provided
|
|
165
293
|
const capToken = options.capabilityToken || options.capability;
|
|
166
294
|
if (capToken) {
|
|
167
|
-
const authorized = await this.verifyCapability(
|
|
168
|
-
capToken,
|
|
169
|
-
'write',
|
|
170
|
-
{ holonId, lensName }
|
|
171
|
-
);
|
|
295
|
+
const authorized = await this.verifyCapability(capToken, 'write', { holonId, lensName });
|
|
172
296
|
if (!authorized) {
|
|
173
297
|
throw new AuthorizationError('AuthorizationError: Invalid capability token for write operation', 'write');
|
|
174
298
|
}
|
|
175
299
|
}
|
|
176
300
|
|
|
177
|
-
// Auto-generate ID if not provided
|
|
178
301
|
if (!data.id) {
|
|
179
302
|
data.id = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
303
|
+
} else {
|
|
304
|
+
// Auto-convert data.id to string for convenience
|
|
305
|
+
data.id = String(data.id);
|
|
180
306
|
}
|
|
181
307
|
|
|
182
|
-
//
|
|
308
|
+
// Add metadata
|
|
309
|
+
if (!data._meta) data._meta = {};
|
|
310
|
+
data._meta.createdAt = data._meta.createdAt || Date.now();
|
|
311
|
+
data._meta.updatedAt = Date.now();
|
|
312
|
+
|
|
183
313
|
const path = storage.buildPath(this.config.appName, holonId, lensName, data.id);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
//
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// Split data: local fields stay local, everything else goes to source
|
|
194
|
-
const localData = {
|
|
195
|
-
...existingData, // Keep hologram structure
|
|
196
|
-
};
|
|
197
|
-
const sourceUpdates = {};
|
|
198
|
-
|
|
199
|
-
for (const [key, value] of Object.entries(data)) {
|
|
200
|
-
if (hologramStructureFields.includes(key)) {
|
|
201
|
-
// Skip - don't overwrite hologram structure
|
|
202
|
-
continue;
|
|
203
|
-
} else if (key === '_hologram') {
|
|
204
|
-
// Skip - this is resolved metadata, not real data
|
|
205
|
-
continue;
|
|
206
|
-
} else if (localOverrideFields.includes(key)) {
|
|
207
|
-
// This field exists in the hologram - update locally
|
|
208
|
-
localData[key] = value;
|
|
209
|
-
} else {
|
|
210
|
-
// This field doesn't exist in hologram - update source
|
|
211
|
-
sourceUpdates[key] = value;
|
|
212
|
-
}
|
|
314
|
+
this._log('DEBUG', 'write', { holonId, lensName, dataId: data.id, path, blocking: !!options.blocking });
|
|
315
|
+
|
|
316
|
+
// Validate synchronously if schema exists
|
|
317
|
+
if (options.validate !== false && this.schemas.has(lensName)) {
|
|
318
|
+
const schemaObj = this.schemas.get(lensName);
|
|
319
|
+
const strict = options.strict !== undefined ? options.strict : schemaObj.strict;
|
|
320
|
+
if (schemaObj.schema && typeof schemaObj.schema === 'object' && !schemaObj.schema.$ref) {
|
|
321
|
+
schema.validate(data, schemaObj.schema, lensName, strict);
|
|
213
322
|
}
|
|
323
|
+
}
|
|
214
324
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const currentSourceData = await storage.read(this.client, sourcePath);
|
|
225
|
-
const mergedSourceData = { ...currentSourceData, ...sourceUpdates };
|
|
325
|
+
// OPTIMISTIC UPDATE: Store in write cache and clear from delete cache
|
|
326
|
+
this._writeCache.set(path, { data: { ...data }, timestamp: Date.now() });
|
|
327
|
+
this._deleteCache.delete(path);
|
|
328
|
+
this._log('DEBUG', '📝 CACHE WRITE', {
|
|
329
|
+
path,
|
|
330
|
+
dataId: data.id,
|
|
331
|
+
cacheSize: this._writeCache.size,
|
|
332
|
+
mode: options.blocking ? 'blocking' : 'optimistic'
|
|
333
|
+
});
|
|
226
334
|
|
|
227
|
-
|
|
228
|
-
|
|
335
|
+
// Queue background sync
|
|
336
|
+
const syncPromise = this._syncWriteToRelay(holonId, lensName, data, path, options);
|
|
229
337
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
338
|
+
// If blocking mode, wait for relay confirmation
|
|
339
|
+
if (options.blocking) {
|
|
340
|
+
this._log('DEBUG', '⏳ BLOCKING: Waiting for relay confirmation', { path });
|
|
341
|
+
return syncPromise;
|
|
342
|
+
}
|
|
234
343
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
344
|
+
// Non-blocking: return immediately, sync happens in background
|
|
345
|
+
this._log('DEBUG', '⚡ OPTIMISTIC: Returning immediately, sync in background', { path });
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Syncs a write operation to the relay in the background.
|
|
351
|
+
* Handles hologram writes, propagation, and error logging.
|
|
352
|
+
*
|
|
353
|
+
* @private
|
|
354
|
+
* @param {string} holonId - Holon ID
|
|
355
|
+
* @param {string} lensName - Lens name
|
|
356
|
+
* @param {Object} data - Data to write
|
|
357
|
+
* @param {string} path - Storage path
|
|
358
|
+
* @param {Object} options - Write options
|
|
359
|
+
* @returns {Promise<boolean>} True if write succeeded
|
|
360
|
+
*/
|
|
361
|
+
async _syncWriteToRelay(holonId, lensName, data, path, options) {
|
|
362
|
+
const startTime = Date.now();
|
|
363
|
+
this._log('DEBUG', '🔄 RELAY SYNC START', {
|
|
364
|
+
path,
|
|
365
|
+
dataId: data.id,
|
|
366
|
+
relays: this.client?.relays?.map(r => r.url) || ['local-only']
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
// Check for existing hologram
|
|
371
|
+
const existingData = await storage.read(this.client, path);
|
|
372
|
+
|
|
373
|
+
// Handle hologram writes
|
|
374
|
+
if (existingData && existingData.hologram === true && existingData.target) {
|
|
375
|
+
this._log('DEBUG', '🔗 Syncing hologram write', { path, target: existingData.target });
|
|
376
|
+
return this._syncHologramWrite(existingData, data, path, lensName, options);
|
|
257
377
|
}
|
|
258
378
|
|
|
379
|
+
// Regular write to relay
|
|
380
|
+
await storage.write(this.client, path, data);
|
|
381
|
+
|
|
259
382
|
const endTime = Date.now();
|
|
383
|
+
const duration = endTime - startTime;
|
|
260
384
|
this._metrics.writes++;
|
|
261
385
|
if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
|
|
262
|
-
this._metrics.totalWriteTime +=
|
|
386
|
+
this._metrics.totalWriteTime += duration;
|
|
387
|
+
|
|
388
|
+
this._log('DEBUG', '✅ RELAY SYNC SUCCESS', {
|
|
389
|
+
path,
|
|
390
|
+
dataId: data.id,
|
|
391
|
+
duration: `${duration}ms`,
|
|
392
|
+
totalWrites: this._metrics.writes
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Clear from write cache after successful sync (optional - keeps cache smaller)
|
|
396
|
+
// this._writeCache.delete(path);
|
|
397
|
+
|
|
398
|
+
// Handle propagation in background
|
|
399
|
+
const { autoPropagate = true } = options;
|
|
400
|
+
if (autoPropagate && !data.hologram) {
|
|
401
|
+
this._log('DEBUG', '📤 Starting propagation', { path });
|
|
402
|
+
this.propagate(holonId, lensName, data, options.propagationOptions || {})
|
|
403
|
+
.catch(err => this._log('WARN', '⚠️ Propagation failed', { path, error: err.message }));
|
|
404
|
+
}
|
|
263
405
|
|
|
264
406
|
return true;
|
|
407
|
+
} catch (error) {
|
|
408
|
+
const duration = Date.now() - startTime;
|
|
409
|
+
this._log('ERROR', '❌ RELAY SYNC FAILED', {
|
|
410
|
+
path,
|
|
411
|
+
dataId: data.id,
|
|
412
|
+
duration: `${duration}ms`,
|
|
413
|
+
error: error.message,
|
|
414
|
+
stack: error.stack?.split('\n')[1]?.trim()
|
|
415
|
+
});
|
|
416
|
+
// Keep data in write cache - will be retried or eventually consistent
|
|
417
|
+
this._log('WARN', '💾 Data retained in write cache for retry', { path, cacheSize: this._writeCache.size });
|
|
418
|
+
return false;
|
|
265
419
|
}
|
|
420
|
+
}
|
|
266
421
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
422
|
+
/**
|
|
423
|
+
* Handles hologram write synchronization.
|
|
424
|
+
*
|
|
425
|
+
* @private
|
|
426
|
+
*/
|
|
427
|
+
async _syncHologramWrite(existingData, data, path, lensName, options) {
|
|
428
|
+
const hologramStructureFields = ['hologram', 'soul', 'target', 'id', '_meta'];
|
|
429
|
+
const localOverrideFields = Object.keys(existingData).filter(k => !hologramStructureFields.includes(k));
|
|
430
|
+
|
|
431
|
+
const localData = { ...existingData };
|
|
432
|
+
const sourceUpdates = {};
|
|
433
|
+
|
|
434
|
+
for (const [key, value] of Object.entries(data)) {
|
|
435
|
+
if (hologramStructureFields.includes(key) || key === '_hologram') continue;
|
|
436
|
+
if (localOverrideFields.includes(key)) {
|
|
437
|
+
localData[key] = value;
|
|
438
|
+
} else {
|
|
439
|
+
sourceUpdates[key] = value;
|
|
440
|
+
}
|
|
271
441
|
}
|
|
272
|
-
data._meta.timestamp = Date.now();
|
|
273
442
|
|
|
274
|
-
|
|
275
|
-
|
|
443
|
+
if (Object.keys(sourceUpdates).length > 0 && options.validate !== false && this.schemas.has(lensName)) {
|
|
444
|
+
const sourcePath = storage.buildPath(
|
|
445
|
+
existingData.target.appname || this.config.appName,
|
|
446
|
+
existingData.target.holonId,
|
|
447
|
+
existingData.target.lensName,
|
|
448
|
+
existingData.target.dataId
|
|
449
|
+
);
|
|
450
|
+
const currentSourceData = await storage.read(this.client, sourcePath);
|
|
451
|
+
const mergedSourceData = { ...currentSourceData, ...sourceUpdates };
|
|
452
|
+
|
|
276
453
|
const schemaObj = this.schemas.get(lensName);
|
|
277
454
|
const strict = options.strict !== undefined ? options.strict : schemaObj.strict;
|
|
278
455
|
|
|
279
|
-
|
|
280
|
-
|
|
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
|
|
286
|
-
schema.validate(data, schemaObj.schema, lensName, strict);
|
|
456
|
+
if (schemaObj.schema && typeof schemaObj.schema === 'object' && !schemaObj.schema.$ref) {
|
|
457
|
+
schema.validate(mergedSourceData, schemaObj.schema, lensName, strict);
|
|
287
458
|
}
|
|
288
459
|
}
|
|
289
460
|
|
|
290
|
-
// Write to storage
|
|
291
461
|
const startTime = Date.now();
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
}
|
|
462
|
+
await storage.write(this.client, path, localData);
|
|
463
|
+
|
|
464
|
+
if (Object.keys(sourceUpdates).length > 0) {
|
|
465
|
+
const sourcePath = storage.buildPath(
|
|
466
|
+
existingData.target.appname || this.config.appName,
|
|
467
|
+
existingData.target.holonId,
|
|
468
|
+
existingData.target.lensName,
|
|
469
|
+
existingData.target.dataId
|
|
470
|
+
);
|
|
471
|
+
await storage.update(this.client, sourcePath, sourceUpdates);
|
|
472
|
+
await federation.refreshActiveHolograms(
|
|
473
|
+
this.client,
|
|
474
|
+
existingData.target.appname || this.config.appName,
|
|
475
|
+
existingData.target.holonId,
|
|
476
|
+
existingData.target.lensName,
|
|
477
|
+
existingData.target.dataId
|
|
478
|
+
);
|
|
318
479
|
}
|
|
319
480
|
|
|
320
|
-
|
|
481
|
+
const endTime = Date.now();
|
|
482
|
+
this._metrics.writes++;
|
|
483
|
+
if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
|
|
484
|
+
this._metrics.totalWriteTime += (endTime - startTime);
|
|
485
|
+
|
|
486
|
+
return true;
|
|
321
487
|
}
|
|
322
488
|
|
|
323
489
|
/**
|
|
324
|
-
*
|
|
490
|
+
* Recursively resolves holograms (references) to their source data.
|
|
491
|
+
* Handles circular reference detection and local overrides.
|
|
492
|
+
*
|
|
325
493
|
* @private
|
|
326
|
-
* @param {
|
|
327
|
-
* @
|
|
494
|
+
* @param {Object|Array|null} data - Data that may contain holograms
|
|
495
|
+
* @param {Set<string>} [visited=new Set()] - Set of visited paths for circular detection
|
|
496
|
+
* @returns {Promise<Object|Array|null>} Resolved data with holograms replaced by source data
|
|
328
497
|
*/
|
|
329
|
-
async _resolveHolograms(data) {
|
|
330
|
-
// Handle null/undefined
|
|
498
|
+
async _resolveHolograms(data, visited = new Set()) {
|
|
331
499
|
if (!data) return data;
|
|
332
500
|
|
|
333
|
-
// Handle arrays - resolve each item and filter out nulls
|
|
334
501
|
if (Array.isArray(data)) {
|
|
335
502
|
const resolved = [];
|
|
336
503
|
for (const item of data) {
|
|
337
|
-
const resolvedItem = await this._resolveHolograms(item);
|
|
338
|
-
|
|
339
|
-
if (resolvedItem != null) {
|
|
504
|
+
const resolvedItem = await this._resolveHolograms(item, new Set());
|
|
505
|
+
if (resolvedItem !== null) {
|
|
340
506
|
resolved.push(resolvedItem);
|
|
341
507
|
}
|
|
342
508
|
}
|
|
343
509
|
return resolved;
|
|
344
510
|
}
|
|
345
511
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
512
|
+
if (data && typeof data === 'object') {
|
|
513
|
+
if (data.hologram === true && data.target) {
|
|
514
|
+
const sourcePath = storage.buildPath(
|
|
515
|
+
data.target.appname || this.config.appName,
|
|
516
|
+
data.target.holonId,
|
|
517
|
+
data.target.lensName,
|
|
518
|
+
data.target.dataId
|
|
519
|
+
);
|
|
520
|
+
this._log('DEBUG', 'resolving hologram', {
|
|
521
|
+
hologramId: data.id,
|
|
522
|
+
sourcePath,
|
|
523
|
+
targetHolon: data.target.holonId,
|
|
524
|
+
targetLens: data.target.lensName,
|
|
525
|
+
targetDataId: data.target.dataId
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Circular reference detection
|
|
529
|
+
if (visited.has(sourcePath)) {
|
|
530
|
+
this._log('WARN', 'Circular hologram reference detected', { sourcePath });
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
visited.add(sourcePath);
|
|
351
534
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
535
|
+
let resolveOptions = {};
|
|
536
|
+
if (data.target.authorPubKey) {
|
|
537
|
+
resolveOptions.authors = [data.target.authorPubKey];
|
|
538
|
+
resolveOptions.includeAuthor = true;
|
|
539
|
+
}
|
|
357
540
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
541
|
+
const sourceData = await storage.read(this.client, sourcePath, resolveOptions);
|
|
542
|
+
this._log('DEBUG', 'hologram source fetched', { found: !!sourceData, sourcePath });
|
|
543
|
+
if (sourceData) {
|
|
544
|
+
// If source is also a hologram, recursively resolve it
|
|
545
|
+
let resolvedSource = sourceData;
|
|
546
|
+
if (sourceData.hologram === true && sourceData.target) {
|
|
547
|
+
resolvedSource = await this._resolveHolograms(sourceData, visited);
|
|
548
|
+
if (resolvedSource === null) {
|
|
549
|
+
return null; // Circular reference or unresolvable
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Get local override fields from the hologram (excluding hologram structure fields)
|
|
554
|
+
const hologramStructureFields = ['hologram', 'soul', 'target', '_meta', 'id', 'capability', 'crossHolosphere'];
|
|
555
|
+
const localOverrides = {};
|
|
556
|
+
for (const [k, v] of Object.entries(data)) {
|
|
557
|
+
if (!hologramStructureFields.includes(k)) {
|
|
558
|
+
localOverrides[k] = v;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
363
561
|
|
|
364
|
-
|
|
562
|
+
const merged = {
|
|
563
|
+
...resolvedSource,
|
|
564
|
+
...localOverrides,
|
|
565
|
+
_hologram: {
|
|
566
|
+
isHologram: true,
|
|
567
|
+
soul: data.soul,
|
|
568
|
+
sourceHolon: data.target.holonId,
|
|
569
|
+
source: data.target,
|
|
570
|
+
localOverrides: Object.keys(localOverrides),
|
|
571
|
+
crossHolosphere: data.crossHolosphere || false,
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
// Preserve source _meta but add hologram source info
|
|
576
|
+
if (resolvedSource._meta) {
|
|
577
|
+
merged._meta = { ...resolvedSource._meta, source: data.target.holonId };
|
|
578
|
+
} else {
|
|
579
|
+
merged._meta = { source: data.target.holonId };
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return merged;
|
|
583
|
+
}
|
|
584
|
+
return null; // Source not found
|
|
585
|
+
}
|
|
586
|
+
}
|
|
365
587
|
return data;
|
|
366
588
|
}
|
|
367
589
|
|
|
590
|
+
/**
|
|
591
|
+
* Reads data from a specific holon and lens.
|
|
592
|
+
*
|
|
593
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
594
|
+
* @param {string} lensName - Name of the lens (data category)
|
|
595
|
+
* @param {string|null} [dataId=null] - Specific data ID, or null to read all
|
|
596
|
+
* @param {Object} [options={}] - Read options
|
|
597
|
+
* @param {string} [options.capabilityToken] - Capability token for authorization
|
|
598
|
+
* @param {boolean} [options.resolveHolograms=true] - Whether to resolve hologram references
|
|
599
|
+
* @returns {Promise<Object|Array|null>} Data object, array of objects, or null if not found
|
|
600
|
+
* @throws {ValidationError} If holonId or lensName is invalid
|
|
601
|
+
* @throws {AuthorizationError} If capability token is invalid
|
|
602
|
+
*/
|
|
368
603
|
async read(holonId, lensName, dataId = null, options = {}) {
|
|
369
|
-
//
|
|
370
|
-
if (
|
|
604
|
+
// Auto-convert to strings for convenience
|
|
605
|
+
if (holonId != null) holonId = String(holonId);
|
|
606
|
+
if (lensName != null) lensName = String(lensName);
|
|
607
|
+
if (dataId != null) dataId = String(dataId);
|
|
608
|
+
|
|
609
|
+
if (!holonId) {
|
|
371
610
|
throw new ValidationError('ValidationError: holonId must be a non-empty string');
|
|
372
611
|
}
|
|
373
|
-
if (!lensName
|
|
612
|
+
if (!lensName) {
|
|
374
613
|
throw new ValidationError('ValidationError: lensName must be a non-empty string');
|
|
375
614
|
}
|
|
376
615
|
|
|
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);
|
|
616
|
+
const capToken = options.capabilityToken || options.capability;
|
|
617
|
+
if (capToken) {
|
|
618
|
+
const authorized = await this.verifyCapability(capToken, 'read', { holonId, lensName, dataId });
|
|
619
|
+
if (!authorized) {
|
|
620
|
+
throw new AuthorizationError('AuthorizationError: Invalid capability token for read operation', 'read');
|
|
406
621
|
}
|
|
407
622
|
}
|
|
408
623
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
authors,
|
|
412
|
-
includeAuthor: true, // Track which author each item came from
|
|
413
|
-
};
|
|
624
|
+
const startTime = Date.now();
|
|
625
|
+
let result;
|
|
414
626
|
|
|
415
627
|
if (dataId) {
|
|
416
628
|
const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
|
|
417
|
-
const data = await storage.read(this.client, path, queryOptions);
|
|
418
|
-
|
|
419
|
-
// Filter out deleted items
|
|
420
|
-
if (data && data._deleted) {
|
|
421
|
-
const endTime = Date.now();
|
|
422
|
-
if (!this._metrics.totalReadTime) this._metrics.totalReadTime = 0;
|
|
423
|
-
this._metrics.totalReadTime += (endTime - startTime);
|
|
424
|
-
return null;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Always resolve holograms
|
|
428
|
-
const resolved = await this._resolveHolograms(data);
|
|
429
629
|
|
|
430
|
-
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
|
|
630
|
+
// Check delete cache first - if deleted, return null immediately
|
|
631
|
+
if (this._deleteCache.has(path)) {
|
|
632
|
+
this._log('DEBUG', '🗑️ CACHE READ: Deleted item', {
|
|
633
|
+
path,
|
|
634
|
+
source: 'delete-cache',
|
|
635
|
+
deleteCacheSize: this._deleteCache.size
|
|
636
|
+
});
|
|
637
|
+
result = null;
|
|
638
|
+
} else {
|
|
639
|
+
// Check write cache for optimistic reads
|
|
640
|
+
const cached = this._writeCache.get(path);
|
|
641
|
+
if (cached) {
|
|
642
|
+
const cacheAge = Date.now() - cached.timestamp;
|
|
643
|
+
this._log('DEBUG', '⚡ CACHE HIT: Write cache', {
|
|
644
|
+
path,
|
|
645
|
+
source: 'write-cache',
|
|
646
|
+
cacheAge: `${cacheAge}ms`,
|
|
647
|
+
writeCacheSize: this._writeCache.size
|
|
648
|
+
});
|
|
649
|
+
result = cached.data;
|
|
650
|
+
} else {
|
|
651
|
+
this._log('DEBUG', '📖 CACHE MISS: Reading from storage', { path });
|
|
652
|
+
result = await storage.read(this.client, path);
|
|
653
|
+
this._log('DEBUG', '💾 STORAGE READ', {
|
|
654
|
+
path,
|
|
655
|
+
source: 'storage',
|
|
656
|
+
found: !!result,
|
|
657
|
+
isHologram: result?.hologram === true
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
434
661
|
} else {
|
|
435
662
|
const path = storage.buildPath(this.config.appName, holonId, lensName);
|
|
436
|
-
|
|
437
|
-
...queryOptions,
|
|
438
|
-
hybrid: this.config.hybridMode
|
|
439
|
-
});
|
|
663
|
+
this._log('DEBUG', 'readAll', { holonId, lensName, path });
|
|
440
664
|
|
|
441
|
-
//
|
|
442
|
-
const
|
|
665
|
+
// For readAll, merge cached writes with storage results and filter deleted
|
|
666
|
+
const storageResult = await storage.readAll(this.client, path);
|
|
667
|
+
result = this._mergeWithWriteCache(storageResult, path);
|
|
668
|
+
this._log('DEBUG', 'readAll result', { count: Array.isArray(result) ? result.length : 0 });
|
|
669
|
+
}
|
|
443
670
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
671
|
+
const { resolveHolograms = true } = options;
|
|
672
|
+
if (resolveHolograms && result) {
|
|
673
|
+
this._log('DEBUG', 'resolving holograms', { itemCount: Array.isArray(result) ? result.length : 1 });
|
|
674
|
+
result = await this._resolveHolograms(result);
|
|
675
|
+
}
|
|
448
676
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
677
|
+
const endTime = Date.now();
|
|
678
|
+
this._metrics.reads++;
|
|
679
|
+
if (!this._metrics.totalReadTime) this._metrics.totalReadTime = 0;
|
|
680
|
+
this._metrics.totalReadTime += (endTime - startTime);
|
|
681
|
+
|
|
682
|
+
return result;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Merges storage results with write cache for readAll operations.
|
|
687
|
+
* Cached writes take precedence over storage data.
|
|
688
|
+
* Deleted items are filtered out.
|
|
689
|
+
*
|
|
690
|
+
* @private
|
|
691
|
+
* @param {Array} storageResult - Results from storage
|
|
692
|
+
* @param {string} pathPrefix - Path prefix for filtering cache
|
|
693
|
+
* @returns {Array} Merged results
|
|
694
|
+
*/
|
|
695
|
+
_mergeWithWriteCache(storageResult, pathPrefix) {
|
|
696
|
+
const results = Array.isArray(storageResult) ? [...storageResult] : [];
|
|
697
|
+
const resultMap = new Map(results.map(item => [item.id, item]));
|
|
698
|
+
|
|
699
|
+
// Check for cached writes matching this path prefix
|
|
700
|
+
for (const [cachedPath, cached] of this._writeCache.entries()) {
|
|
701
|
+
if (cachedPath.startsWith(pathPrefix + '/')) {
|
|
702
|
+
// Cached write - update or add to results
|
|
703
|
+
const cachedData = cached.data;
|
|
704
|
+
if (cachedData.id) {
|
|
705
|
+
resultMap.set(cachedData.id, cachedData);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
453
708
|
}
|
|
709
|
+
|
|
710
|
+
// Filter out deleted items
|
|
711
|
+
for (const deletedPath of this._deleteCache) {
|
|
712
|
+
if (deletedPath.startsWith(pathPrefix + '/')) {
|
|
713
|
+
// Extract the dataId from the path (last segment)
|
|
714
|
+
const dataId = deletedPath.split('/').pop();
|
|
715
|
+
resultMap.delete(dataId);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return Array.from(resultMap.values());
|
|
454
720
|
}
|
|
455
721
|
|
|
722
|
+
/**
|
|
723
|
+
* Updates existing data in a specific holon and lens.
|
|
724
|
+
* By default, update is optimistic (non-blocking) - returns immediately after caching merged data.
|
|
725
|
+
* Data is synced to relays in the background.
|
|
726
|
+
*
|
|
727
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
728
|
+
* @param {string} lensName - Name of the lens (data category)
|
|
729
|
+
* @param {string} dataId - ID of the data to update
|
|
730
|
+
* @param {Object} updates - Object containing fields to update
|
|
731
|
+
* @param {Object} [options={}] - Update options
|
|
732
|
+
* @param {string} [options.capabilityToken] - Capability token for authorization
|
|
733
|
+
* @param {boolean} [options.validate=true] - Whether to validate against schema
|
|
734
|
+
* @param {boolean} [options.strict] - Override schema strict mode
|
|
735
|
+
* @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
|
|
736
|
+
* @returns {Promise<boolean>} True if update succeeded, false if data not found
|
|
737
|
+
* @throws {ValidationError} If holonId, lensName, dataId, or updates is invalid
|
|
738
|
+
* @throws {AuthorizationError} If capability token is invalid
|
|
739
|
+
*/
|
|
456
740
|
async update(holonId, lensName, dataId, updates, options = {}) {
|
|
457
|
-
|
|
741
|
+
if (!holonId || typeof holonId !== 'string') {
|
|
742
|
+
throw new ValidationError('ValidationError: holonId must be a non-empty string');
|
|
743
|
+
}
|
|
744
|
+
if (!lensName || typeof lensName !== 'string') {
|
|
745
|
+
throw new ValidationError('ValidationError: lensName must be a non-empty string');
|
|
746
|
+
}
|
|
747
|
+
if (!dataId || typeof dataId !== 'string') {
|
|
748
|
+
throw new ValidationError('ValidationError: dataId must be a non-empty string');
|
|
749
|
+
}
|
|
750
|
+
if (!updates || typeof updates !== 'object') {
|
|
751
|
+
throw new ValidationError('ValidationError: updates must be an object');
|
|
752
|
+
}
|
|
753
|
+
|
|
458
754
|
const capToken = options.capabilityToken || options.capability;
|
|
459
755
|
if (capToken) {
|
|
460
|
-
const authorized = await this.verifyCapability(
|
|
461
|
-
capToken,
|
|
462
|
-
'write',
|
|
463
|
-
{ holonId, lensName }
|
|
464
|
-
);
|
|
756
|
+
const authorized = await this.verifyCapability(capToken, 'write', { holonId, lensName, dataId });
|
|
465
757
|
if (!authorized) {
|
|
466
758
|
throw new AuthorizationError('AuthorizationError: Invalid capability token for update operation', 'write');
|
|
467
759
|
}
|
|
468
760
|
}
|
|
469
761
|
|
|
470
|
-
// Read raw data (without resolving) to check if it's a hologram
|
|
471
762
|
const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
|
|
472
|
-
const rawData = await storage.read(this.client, path);
|
|
473
|
-
|
|
474
|
-
// If this is a hologram, split updates between local overrides and source
|
|
475
|
-
if (rawData && rawData.hologram === true && rawData.target) {
|
|
476
|
-
// Fields that are stored in the hologram itself (local overrides)
|
|
477
|
-
// These are fields that exist in the hologram beyond the core hologram structure
|
|
478
|
-
const hologramStructureFields = ['hologram', 'soul', 'target', 'id', '_meta'];
|
|
479
|
-
const localOverrideFields = Object.keys(rawData).filter(k => !hologramStructureFields.includes(k));
|
|
480
|
-
|
|
481
|
-
// Split updates: local fields stay local, everything else goes to source
|
|
482
|
-
const localUpdates = {};
|
|
483
|
-
const sourceUpdates = {};
|
|
484
|
-
|
|
485
|
-
for (const [key, value] of Object.entries(updates)) {
|
|
486
|
-
if (hologramStructureFields.includes(key)) {
|
|
487
|
-
// Skip hologram structure fields entirely
|
|
488
|
-
continue;
|
|
489
|
-
} else if (localOverrideFields.includes(key)) {
|
|
490
|
-
// This field exists in the hologram - update locally
|
|
491
|
-
localUpdates[key] = value;
|
|
492
|
-
} else {
|
|
493
|
-
// This field doesn't exist in hologram - update source
|
|
494
|
-
sourceUpdates[key] = value;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
763
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
764
|
+
// Check write cache first, then storage
|
|
765
|
+
const cached = this._writeCache.get(path);
|
|
766
|
+
const existingData = cached ? cached.data : await storage.read(this.client, path);
|
|
767
|
+
const dataSource = cached ? 'write-cache' : 'storage';
|
|
502
768
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
rawData.target.holonId,
|
|
508
|
-
rawData.target.lensName,
|
|
509
|
-
rawData.target.dataId
|
|
510
|
-
);
|
|
511
|
-
await storage.update(this.client, sourcePath, sourceUpdates);
|
|
512
|
-
}
|
|
769
|
+
if (!existingData) {
|
|
770
|
+
this._log('DEBUG', '✏️ UPDATE: Data not found', { path, dataId });
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
513
773
|
|
|
514
|
-
|
|
774
|
+
this._log('DEBUG', '✏️ UPDATE: Found data', { path, dataId, source: dataSource });
|
|
775
|
+
|
|
776
|
+
// Handle hologram updates - delegate to write() which is already optimistic
|
|
777
|
+
if (existingData.hologram === true && existingData.target) {
|
|
778
|
+
this._log('DEBUG', '🔗 Updating hologram', { path });
|
|
779
|
+
return this.write(holonId, lensName, { ...existingData, ...updates, id: dataId }, options);
|
|
515
780
|
}
|
|
516
781
|
|
|
517
|
-
|
|
518
|
-
|
|
782
|
+
const mergedData = { ...existingData, ...updates };
|
|
783
|
+
mergedData._meta = mergedData._meta || {};
|
|
784
|
+
mergedData._meta.updatedAt = Date.now();
|
|
785
|
+
|
|
519
786
|
if (options.validate !== false && this.schemas.has(lensName)) {
|
|
520
|
-
const existing = await this.read(holonId, lensName, dataId);
|
|
521
|
-
const merged = { ...existing, ...updates };
|
|
522
787
|
const schemaObj = this.schemas.get(lensName);
|
|
523
788
|
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);
|
|
789
|
+
if (schemaObj.schema && typeof schemaObj.schema === 'object' && !schemaObj.schema.$ref) {
|
|
790
|
+
schema.validate(mergedData, schemaObj.schema, lensName, strict);
|
|
532
791
|
}
|
|
533
792
|
}
|
|
534
793
|
|
|
535
|
-
|
|
794
|
+
// OPTIMISTIC UPDATE: Store in write cache and clear from delete cache
|
|
795
|
+
this._writeCache.set(path, { data: { ...mergedData }, timestamp: Date.now() });
|
|
796
|
+
this._deleteCache.delete(path);
|
|
797
|
+
this._log('DEBUG', '✏️ CACHE UPDATE', {
|
|
798
|
+
path,
|
|
799
|
+
dataId,
|
|
800
|
+
updatedFields: Object.keys(updates),
|
|
801
|
+
cacheSize: this._writeCache.size,
|
|
802
|
+
mode: options.blocking ? 'blocking' : 'optimistic'
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// Queue background sync
|
|
806
|
+
const syncPromise = this._syncUpdateToRelay(holonId, lensName, dataId, mergedData, existingData, path);
|
|
807
|
+
|
|
808
|
+
if (options.blocking) {
|
|
809
|
+
this._log('DEBUG', '⏳ BLOCKING: Waiting for update confirmation', { path });
|
|
810
|
+
return syncPromise;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Syncs an update operation to the relay in the background.
|
|
818
|
+
*
|
|
819
|
+
* @private
|
|
820
|
+
*/
|
|
821
|
+
async _syncUpdateToRelay(holonId, lensName, dataId, mergedData, existingData, path) {
|
|
822
|
+
const startTime = Date.now();
|
|
823
|
+
this._log('DEBUG', '🔄 UPDATE SYNC START', {
|
|
824
|
+
path,
|
|
825
|
+
dataId,
|
|
826
|
+
relays: this.client?.relays?.map(r => r.url) || ['local-only']
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
try {
|
|
830
|
+
await storage.write(this.client, path, mergedData);
|
|
831
|
+
const duration = Date.now() - startTime;
|
|
832
|
+
|
|
833
|
+
this._metrics.writes++;
|
|
834
|
+
if (!this._metrics.totalWriteTime) this._metrics.totalWriteTime = 0;
|
|
835
|
+
this._metrics.totalWriteTime += duration;
|
|
836
|
+
|
|
837
|
+
this._log('DEBUG', '✅ UPDATE SYNC SUCCESS', {
|
|
838
|
+
path,
|
|
839
|
+
dataId,
|
|
840
|
+
duration: `${duration}ms`,
|
|
841
|
+
totalWrites: this._metrics.writes
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
if (existingData._meta && existingData._meta.activeHolograms) {
|
|
845
|
+
this._log('DEBUG', '🔗 Refreshing active holograms', { path });
|
|
846
|
+
federation.refreshActiveHolograms(this.client, this.config.appName, holonId, lensName, dataId)
|
|
847
|
+
.catch(err => this._log('WARN', '⚠️ Hologram refresh failed', { path, error: err.message }));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
return true;
|
|
851
|
+
} catch (error) {
|
|
852
|
+
const duration = Date.now() - startTime;
|
|
853
|
+
this._log('ERROR', '❌ UPDATE SYNC FAILED', {
|
|
854
|
+
path,
|
|
855
|
+
dataId,
|
|
856
|
+
duration: `${duration}ms`,
|
|
857
|
+
error: error.message,
|
|
858
|
+
stack: error.stack?.split('\n')[1]?.trim()
|
|
859
|
+
});
|
|
860
|
+
this._log('WARN', '💾 Data retained in write cache for retry', { path, cacheSize: this._writeCache.size });
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
536
863
|
}
|
|
537
864
|
|
|
865
|
+
/**
|
|
866
|
+
* Deletes data from a specific holon and lens.
|
|
867
|
+
* By default, delete is optimistic (non-blocking) - returns immediately after cache invalidation.
|
|
868
|
+
*
|
|
869
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
870
|
+
* @param {string} lensName - Name of the lens (data category)
|
|
871
|
+
* @param {string} dataId - ID of the data to delete
|
|
872
|
+
* @param {Object} [options={}] - Delete options
|
|
873
|
+
* @param {string} [options.capabilityToken] - Capability token for authorization
|
|
874
|
+
* @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
|
|
875
|
+
* @returns {Promise<boolean>} True if deletion succeeded, false if data not found
|
|
876
|
+
* @throws {ValidationError} If holonId, lensName, or dataId is invalid
|
|
877
|
+
* @throws {AuthorizationError} If not owner and no valid capability token
|
|
878
|
+
*/
|
|
538
879
|
async delete(holonId, lensName, dataId, options = {}) {
|
|
539
|
-
//
|
|
540
|
-
|
|
880
|
+
// Auto-convert to strings for convenience
|
|
881
|
+
if (holonId != null) holonId = String(holonId);
|
|
882
|
+
if (lensName != null) lensName = String(lensName);
|
|
883
|
+
if (dataId != null) dataId = String(dataId);
|
|
884
|
+
|
|
885
|
+
if (!holonId) {
|
|
886
|
+
throw new ValidationError('ValidationError: holonId must be a non-empty string');
|
|
887
|
+
}
|
|
888
|
+
if (!lensName) {
|
|
889
|
+
throw new ValidationError('ValidationError: lensName must be a non-empty string');
|
|
890
|
+
}
|
|
891
|
+
if (!dataId) {
|
|
892
|
+
throw new ValidationError('ValidationError: dataId must be a non-empty string');
|
|
893
|
+
}
|
|
541
894
|
|
|
542
|
-
|
|
543
|
-
|
|
895
|
+
const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
|
|
896
|
+
|
|
897
|
+
// Check write cache first, then storage
|
|
898
|
+
const cached = this._writeCache.get(path);
|
|
899
|
+
let existingData = cached ? cached.data : null;
|
|
900
|
+
let dataSource = cached ? 'write-cache' : null;
|
|
901
|
+
|
|
902
|
+
if (!existingData) {
|
|
903
|
+
existingData = await storage.read(this.client, path);
|
|
904
|
+
dataSource = existingData ? 'storage' : null;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Return false if data doesn't exist in cache or storage
|
|
908
|
+
if (!existingData) {
|
|
909
|
+
this._log('DEBUG', '🗑️ DELETE: Data not found', { path });
|
|
544
910
|
return false;
|
|
545
911
|
}
|
|
546
912
|
|
|
547
|
-
|
|
548
|
-
const capToken = options.capabilityToken || options.capability;
|
|
549
|
-
const creator = existing._creator || existing.owner;
|
|
550
|
-
const currentUser = this.client.publicKey;
|
|
913
|
+
this._log('DEBUG', '🗑️ DELETE: Found data', { path, source: dataSource });
|
|
551
914
|
|
|
552
|
-
//
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
// 3. Else allow (no owner or current user is owner)
|
|
915
|
+
// Check authorization: owner can delete, others need capability token
|
|
916
|
+
const dataOwner = existingData.owner || existingData._creator;
|
|
917
|
+
const isOwner = !dataOwner || dataOwner === this.client.publicKey;
|
|
556
918
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
919
|
+
const capToken = options.capabilityToken || options.capability;
|
|
920
|
+
if (!isOwner) {
|
|
921
|
+
// Non-owner must provide a valid capability token
|
|
922
|
+
if (!capToken) {
|
|
923
|
+
throw new AuthorizationError('AuthorizationError: Capability token required for delete operation', 'delete');
|
|
924
|
+
}
|
|
925
|
+
const authorized = await this.verifyCapability(capToken, 'delete', { holonId, lensName, dataId });
|
|
563
926
|
if (!authorized) {
|
|
564
|
-
|
|
565
|
-
throw error;
|
|
927
|
+
throw new AuthorizationError('AuthorizationError: Invalid capability token for delete operation', 'delete');
|
|
566
928
|
}
|
|
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
|
-
}
|
|
929
|
+
} else if (capToken) {
|
|
930
|
+
// Owner provided a token - validate it anyway
|
|
931
|
+
const authorized = await this.verifyCapability(capToken, 'delete', { holonId, lensName, dataId });
|
|
932
|
+
if (!authorized) {
|
|
933
|
+
throw new AuthorizationError('AuthorizationError: Invalid capability token for delete operation', 'delete');
|
|
590
934
|
}
|
|
591
935
|
}
|
|
592
936
|
|
|
593
|
-
|
|
594
|
-
|
|
937
|
+
// OPTIMISTIC DELETE: Remove from write cache and add to delete cache
|
|
938
|
+
this._writeCache.delete(path);
|
|
939
|
+
this._deleteCache.add(path);
|
|
940
|
+
this._log('DEBUG', '🗑️ CACHE DELETE', {
|
|
941
|
+
path,
|
|
942
|
+
dataId,
|
|
943
|
+
deleteCacheSize: this._deleteCache.size,
|
|
944
|
+
writeCacheSize: this._writeCache.size,
|
|
945
|
+
mode: options.blocking ? 'blocking' : 'optimistic'
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
if (existingData.hologram === true) {
|
|
949
|
+
this._log('DEBUG', '🔗 Deleting hologram', { path });
|
|
950
|
+
return this.deleteHologram(holonId, lensName, dataId, options);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Delete from storage (background unless blocking)
|
|
954
|
+
const startTime = Date.now();
|
|
955
|
+
this._log('DEBUG', '🔄 DELETE SYNC START', { path, dataId });
|
|
956
|
+
|
|
957
|
+
const deletePromise = storage.deleteData(this.client, path).then(() => {
|
|
958
|
+
const duration = Date.now() - startTime;
|
|
959
|
+
this._metrics.deletes++;
|
|
960
|
+
// Clear from delete cache after successful sync
|
|
961
|
+
this._deleteCache.delete(path);
|
|
962
|
+
this._log('DEBUG', '✅ DELETE SYNC SUCCESS', {
|
|
963
|
+
path,
|
|
964
|
+
dataId,
|
|
965
|
+
duration: `${duration}ms`,
|
|
966
|
+
totalDeletes: this._metrics.deletes,
|
|
967
|
+
deleteCacheSize: this._deleteCache.size
|
|
968
|
+
});
|
|
969
|
+
return true;
|
|
970
|
+
}).catch(error => {
|
|
971
|
+
const duration = Date.now() - startTime;
|
|
972
|
+
this._log('ERROR', '❌ DELETE SYNC FAILED', {
|
|
973
|
+
path,
|
|
974
|
+
dataId,
|
|
975
|
+
duration: `${duration}ms`,
|
|
976
|
+
error: error.message
|
|
977
|
+
});
|
|
978
|
+
// Keep in delete cache so reads still return null
|
|
979
|
+
this._log('WARN', '💾 Path retained in delete cache', { path, deleteCacheSize: this._deleteCache.size });
|
|
980
|
+
return false;
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
if (options.blocking) {
|
|
984
|
+
this._log('DEBUG', '⏳ BLOCKING: Waiting for delete confirmation', { path });
|
|
985
|
+
return deletePromise;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Non-blocking: return immediately
|
|
989
|
+
this._log('DEBUG', '⚡ OPTIMISTIC: Returning immediately, delete sync in background', { path });
|
|
990
|
+
return true;
|
|
595
991
|
}
|
|
596
992
|
|
|
597
|
-
// === Global
|
|
993
|
+
// === Global Tables ===
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Writes data to a global table (not holon-specific).
|
|
997
|
+
*
|
|
998
|
+
* @param {string} table - Name of the global table
|
|
999
|
+
* @param {Object} data - Data object to write (must have an id field)
|
|
1000
|
+
* @returns {Promise<boolean>} True if write succeeded
|
|
1001
|
+
*/
|
|
598
1002
|
async writeGlobal(table, data) {
|
|
1003
|
+
// Auto-convert to strings for convenience
|
|
1004
|
+
if (table != null) table = String(table);
|
|
1005
|
+
if (data?.id != null) data.id = String(data.id);
|
|
1006
|
+
|
|
599
1007
|
return globalTables.writeGlobal(this.client, this.config.appName, table, data);
|
|
600
1008
|
}
|
|
601
1009
|
|
|
1010
|
+
/**
|
|
1011
|
+
* Reads data from a global table.
|
|
1012
|
+
*
|
|
1013
|
+
* @param {string} table - Name of the global table
|
|
1014
|
+
* @param {string|null} [key=null] - Specific key to read, or null to read all
|
|
1015
|
+
* @returns {Promise<Object|Array|null>} Data object, array of objects, or null
|
|
1016
|
+
*/
|
|
602
1017
|
async readGlobal(table, key = null) {
|
|
1018
|
+
// Auto-convert to strings for convenience
|
|
1019
|
+
if (table != null) table = String(table);
|
|
1020
|
+
if (key != null) key = String(key);
|
|
1021
|
+
|
|
603
1022
|
return globalTables.readGlobal(this.client, this.config.appName, table, key);
|
|
604
1023
|
}
|
|
605
1024
|
|
|
1025
|
+
/**
|
|
1026
|
+
* Updates data in a global table.
|
|
1027
|
+
*
|
|
1028
|
+
* @param {string} table - Name of the global table
|
|
1029
|
+
* @param {string} key - Key of the data to update
|
|
1030
|
+
* @param {Object} updates - Object containing fields to update
|
|
1031
|
+
* @returns {Promise<boolean>} True if update succeeded
|
|
1032
|
+
*/
|
|
606
1033
|
async updateGlobal(table, key, updates) {
|
|
1034
|
+
// Auto-convert to strings for convenience
|
|
1035
|
+
if (table != null) table = String(table);
|
|
1036
|
+
if (key != null) key = String(key);
|
|
1037
|
+
|
|
607
1038
|
return globalTables.updateGlobal(this.client, this.config.appName, table, key, updates);
|
|
608
1039
|
}
|
|
609
1040
|
|
|
1041
|
+
/**
|
|
1042
|
+
* Deletes data from a global table.
|
|
1043
|
+
*
|
|
1044
|
+
* @param {string} table - Name of the global table
|
|
1045
|
+
* @param {string} key - Key of the data to delete
|
|
1046
|
+
* @returns {Promise<boolean>} True if deletion succeeded
|
|
1047
|
+
*/
|
|
610
1048
|
async deleteGlobal(table, key) {
|
|
1049
|
+
// Auto-convert to strings for convenience
|
|
1050
|
+
if (table != null) table = String(table);
|
|
1051
|
+
if (key != null) key = String(key);
|
|
1052
|
+
|
|
611
1053
|
return globalTables.deleteGlobal(this.client, this.config.appName, table, key);
|
|
612
1054
|
}
|
|
613
1055
|
|
|
1056
|
+
/**
|
|
1057
|
+
* Gets all data from a global table.
|
|
1058
|
+
*
|
|
1059
|
+
* @param {string} table - Name of the global table
|
|
1060
|
+
* @returns {Promise<Array>} Array of all data objects in the table
|
|
1061
|
+
*/
|
|
614
1062
|
async getAllGlobal(table) {
|
|
615
|
-
|
|
1063
|
+
// Auto-convert to strings for convenience
|
|
1064
|
+
if (table != null) table = String(table);
|
|
1065
|
+
|
|
1066
|
+
return globalTables.getAllGlobal(this.client, this.config.appName, table);
|
|
616
1067
|
}
|
|
617
1068
|
|
|
1069
|
+
/**
|
|
1070
|
+
* Deletes all data from a global table.
|
|
1071
|
+
*
|
|
1072
|
+
* @param {string} table - Name of the global table
|
|
1073
|
+
* @returns {Promise<boolean>} True if deletion succeeded
|
|
1074
|
+
*/
|
|
618
1075
|
async deleteAllGlobal(table) {
|
|
1076
|
+
// Auto-convert to strings for convenience
|
|
1077
|
+
if (table != null) table = String(table);
|
|
1078
|
+
|
|
619
1079
|
return globalTables.deleteAllGlobal(this.client, this.config.appName, table);
|
|
620
1080
|
}
|
|
621
1081
|
|
|
622
|
-
// ===
|
|
1082
|
+
// === Batch Operations ===
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Gets all data from a specific holon and lens.
|
|
1086
|
+
*
|
|
1087
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
1088
|
+
* @param {string} lensName - Name of the lens (data category)
|
|
1089
|
+
* @returns {Promise<Array|null>} Array of data objects or null
|
|
1090
|
+
*/
|
|
623
1091
|
async getAll(holonId, lensName) {
|
|
624
|
-
return this.read(holonId, lensName);
|
|
1092
|
+
return this.read(holonId, lensName, null);
|
|
625
1093
|
}
|
|
626
1094
|
|
|
1095
|
+
/**
|
|
1096
|
+
* Deletes all data from a specific holon and lens.
|
|
1097
|
+
*
|
|
1098
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
1099
|
+
* @param {string} lensName - Name of the lens (data category)
|
|
1100
|
+
* @returns {Promise<boolean>} True if deletion succeeded
|
|
1101
|
+
*/
|
|
627
1102
|
async deleteAll(holonId, lensName) {
|
|
1103
|
+
// Auto-convert to strings for convenience
|
|
1104
|
+
if (holonId != null) holonId = String(holonId);
|
|
1105
|
+
if (lensName != null) lensName = String(lensName);
|
|
1106
|
+
|
|
628
1107
|
const path = storage.buildPath(this.config.appName, holonId, lensName);
|
|
629
1108
|
return storage.deleteAll(this.client, path);
|
|
630
1109
|
}
|
|
631
1110
|
|
|
632
1111
|
// === Schema Operations ===
|
|
633
|
-
async setSchema(lensName, schemaObj, strict = false) {
|
|
634
|
-
let resolvedSchema = schemaObj;
|
|
635
1112
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
1113
|
+
/**
|
|
1114
|
+
* Sets a JSON Schema for validating data in a specific lens.
|
|
1115
|
+
*
|
|
1116
|
+
* @param {string} lensName - Name of the lens to associate the schema with
|
|
1117
|
+
* @param {Object|string} schemaInput - JSON Schema object or URI reference
|
|
1118
|
+
* @param {boolean} [strict=false] - If true, validation will reject additional properties
|
|
1119
|
+
* @returns {Promise<void>}
|
|
1120
|
+
* @throws {ValidationError} If lensName is invalid or schema format is invalid
|
|
1121
|
+
*/
|
|
1122
|
+
async setSchema(lensName, schemaInput, strict = false) {
|
|
1123
|
+
if (!lensName || typeof lensName !== 'string') {
|
|
1124
|
+
throw new ValidationError('ValidationError: lensName must be a non-empty string');
|
|
640
1125
|
}
|
|
641
1126
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
1127
|
+
// Handle null/undefined
|
|
1128
|
+
if (schemaInput == null) {
|
|
1129
|
+
throw new ValidationError('ValidationError: schema cannot be null or undefined');
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
let schemaObj;
|
|
1133
|
+
|
|
1134
|
+
// Handle URI string - store as $ref
|
|
1135
|
+
if (typeof schemaInput === 'string') {
|
|
1136
|
+
schemaObj = { $ref: schemaInput };
|
|
1137
|
+
} else if (typeof schemaInput === 'object') {
|
|
1138
|
+
// Validate it looks like a JSON Schema (must have type or $ref or properties or items)
|
|
1139
|
+
const isValidSchema = schemaInput.$ref ||
|
|
1140
|
+
schemaInput.type ||
|
|
1141
|
+
schemaInput.properties ||
|
|
1142
|
+
schemaInput.items ||
|
|
1143
|
+
schemaInput.allOf ||
|
|
1144
|
+
schemaInput.anyOf ||
|
|
1145
|
+
schemaInput.oneOf ||
|
|
1146
|
+
schemaInput.$schema;
|
|
1147
|
+
|
|
1148
|
+
if (!isValidSchema) {
|
|
1149
|
+
throw new ValidationError('ValidationError: Invalid JSON Schema format');
|
|
650
1150
|
}
|
|
1151
|
+
schemaObj = schemaInput;
|
|
1152
|
+
} else {
|
|
1153
|
+
throw new ValidationError('ValidationError: schema must be an object or URI string');
|
|
651
1154
|
}
|
|
652
1155
|
|
|
653
|
-
this.schemas.set(lensName, { schema:
|
|
1156
|
+
this.schemas.set(lensName, { schema: schemaObj, strict, timestamp: Date.now() });
|
|
654
1157
|
}
|
|
655
1158
|
|
|
1159
|
+
/**
|
|
1160
|
+
* Gets the JSON Schema for a specific lens.
|
|
1161
|
+
*
|
|
1162
|
+
* @param {string} lensName - Name of the lens
|
|
1163
|
+
* @returns {Promise<Object|null>} The schema object or null if not set
|
|
1164
|
+
*/
|
|
656
1165
|
async getSchema(lensName) {
|
|
657
1166
|
const schemaObj = this.schemas.get(lensName);
|
|
658
1167
|
return schemaObj ? schemaObj.schema : null;
|
|
659
1168
|
}
|
|
660
1169
|
|
|
1170
|
+
/**
|
|
1171
|
+
* Clears the JSON Schema for a specific lens.
|
|
1172
|
+
*
|
|
1173
|
+
* @param {string} lensName - Name of the lens
|
|
1174
|
+
* @returns {Promise<void>}
|
|
1175
|
+
*/
|
|
661
1176
|
async clearSchema(lensName) {
|
|
662
1177
|
this.schemas.delete(lensName);
|
|
663
|
-
|
|
1178
|
+
// Returns undefined for contract compliance
|
|
664
1179
|
}
|
|
665
1180
|
|
|
666
1181
|
// === Federation Operations ===
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Sets up federation between two holons for a specific lens.
|
|
1185
|
+
* Federation enables data sharing and synchronization between holons.
|
|
1186
|
+
*
|
|
1187
|
+
* @param {string} sourceHolon - Source holon H3 cell ID
|
|
1188
|
+
* @param {string} targetHolon - Target holon H3 cell ID
|
|
1189
|
+
* @param {string} lensName - Name of the lens to federate
|
|
1190
|
+
* @param {Object} [options={}] - Federation options
|
|
1191
|
+
* @param {string} [options.direction='outbound'] - Direction: 'inbound', 'outbound', or 'bidirectional'
|
|
1192
|
+
* @param {string} [options.mode='reference'] - Mode: 'reference' (hologram) or 'copy'
|
|
1193
|
+
* @param {Function} [options.filter] - Filter function to select which data to federate
|
|
1194
|
+
* @returns {Promise<boolean>} True if federation was set up successfully
|
|
1195
|
+
* @throws {Error} If trying to federate a holon with itself or invalid direction
|
|
1196
|
+
*/
|
|
667
1197
|
async federate(sourceHolon, targetHolon, lensName, options = {}) {
|
|
668
|
-
|
|
1198
|
+
const { direction = 'outbound', mode = 'reference', filter = null } = options;
|
|
1199
|
+
|
|
1200
|
+
// Validation
|
|
669
1201
|
if (sourceHolon === targetHolon) {
|
|
670
1202
|
throw new Error('Cannot federate a holon with itself');
|
|
671
1203
|
}
|
|
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;
|
|
1204
|
+
if (!['inbound', 'outbound', 'bidirectional'].includes(direction)) {
|
|
1205
|
+
throw new Error(`Invalid direction: ${direction}. Must be 'inbound', 'outbound', or 'bidirectional'`);
|
|
684
1206
|
}
|
|
685
1207
|
|
|
686
|
-
|
|
1208
|
+
// Store federation config
|
|
1209
|
+
await federation.setupFederation(
|
|
687
1210
|
this.client,
|
|
688
1211
|
this.config.appName,
|
|
689
1212
|
sourceHolon,
|
|
@@ -692,133 +1215,100 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
692
1215
|
options
|
|
693
1216
|
);
|
|
694
1217
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
this.
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
if (!propagateExisting) {
|
|
704
|
-
return result;
|
|
705
|
-
}
|
|
1218
|
+
// Actually propagate existing data based on direction
|
|
1219
|
+
if (direction === 'outbound' || direction === 'bidirectional') {
|
|
1220
|
+
await this._propagateExistingData(sourceHolon, targetHolon, lensName, { mode, filter });
|
|
1221
|
+
}
|
|
1222
|
+
if (direction === 'inbound' || direction === 'bidirectional') {
|
|
1223
|
+
await this._propagateExistingData(targetHolon, sourceHolon, lensName, { mode, filter });
|
|
1224
|
+
}
|
|
706
1225
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const mode = options.mode || 'reference';
|
|
711
|
-
const filter = options.filter;
|
|
712
|
-
|
|
713
|
-
if (direction === 'outbound') {
|
|
714
|
-
// Source -> Target: propagate data from source to target
|
|
715
|
-
// Check if receiver accepts this lens in their inbound
|
|
716
|
-
const targetFederation = await this.getFederation(targetHolon);
|
|
717
|
-
const receiverLensConfig = targetFederation?.lensConfig?.[sourceHolon];
|
|
718
|
-
if (receiverLensConfig && receiverLensConfig.inbound && !receiverLensConfig.inbound.includes(lensName)) {
|
|
719
|
-
console.log(`Skipping outbound propagation - ${targetHolon} doesn't accept lens '${lensName}' in inbound from ${sourceHolon}`);
|
|
720
|
-
} else {
|
|
721
|
-
// Read raw data to avoid resolving holograms
|
|
722
|
-
const path = storage.buildPath(this.config.appName, sourceHolon, lensName);
|
|
723
|
-
const sourceData = await storage.readAll(this.client, path);
|
|
724
|
-
const sourceArray = Array.isArray(sourceData) ? sourceData : (sourceData ? [sourceData] : []);
|
|
725
|
-
|
|
726
|
-
for (const item of sourceArray) {
|
|
727
|
-
if (item.id === '_federation') continue; // Skip federation config
|
|
728
|
-
// Skip items that are already holograms or from another source
|
|
729
|
-
if (item.hologram === true) continue;
|
|
730
|
-
if (item._meta && item._meta.source && item._meta.source !== sourceHolon) continue;
|
|
731
|
-
if (filter && !filter(item)) continue; // Apply filter if provided
|
|
732
|
-
|
|
733
|
-
await federation.propagateData(
|
|
734
|
-
this.client,
|
|
735
|
-
this.config.appName,
|
|
736
|
-
item,
|
|
737
|
-
sourceHolon,
|
|
738
|
-
targetHolon,
|
|
739
|
-
lensName,
|
|
740
|
-
mode
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
1226
|
+
this._metrics.federations = (this._metrics.federations || 0) + 1;
|
|
1227
|
+
return true;
|
|
1228
|
+
}
|
|
745
1229
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1230
|
+
/**
|
|
1231
|
+
* Propagates existing data from one holon to another.
|
|
1232
|
+
*
|
|
1233
|
+
* @private
|
|
1234
|
+
* @param {string} fromHolon - Source holon H3 cell ID
|
|
1235
|
+
* @param {string} toHolon - Target holon H3 cell ID
|
|
1236
|
+
* @param {string} lensName - Name of the lens
|
|
1237
|
+
* @param {Object} [options={}] - Propagation options
|
|
1238
|
+
* @param {string} [options.mode='reference'] - Mode: 'reference' or 'copy'
|
|
1239
|
+
* @param {Function} [options.filter] - Filter function to select data
|
|
1240
|
+
* @returns {Promise<void>}
|
|
1241
|
+
*/
|
|
1242
|
+
async _propagateExistingData(fromHolon, toHolon, lensName, options = {}) {
|
|
1243
|
+
const { mode = 'reference', filter = null } = options;
|
|
1244
|
+
const existingData = await this.read(fromHolon, lensName, null, { resolveHolograms: false });
|
|
1245
|
+
if (!existingData) return;
|
|
1246
|
+
|
|
1247
|
+
const items = Array.isArray(existingData) ? existingData : [existingData];
|
|
1248
|
+
for (const item of items) {
|
|
1249
|
+
// Skip holograms to avoid circular propagation
|
|
1250
|
+
if (item.hologram === true) continue;
|
|
1251
|
+
// Apply filter if provided
|
|
1252
|
+
if (filter && !filter(item)) continue;
|
|
1253
|
+
await federation.propagateData(
|
|
1254
|
+
this.client,
|
|
1255
|
+
this.config.appName,
|
|
1256
|
+
item,
|
|
1257
|
+
fromHolon,
|
|
1258
|
+
toHolon,
|
|
1259
|
+
lensName,
|
|
1260
|
+
mode
|
|
1261
|
+
);
|
|
778
1262
|
}
|
|
779
|
-
|
|
780
|
-
return result;
|
|
781
1263
|
}
|
|
782
1264
|
|
|
1265
|
+
/**
|
|
1266
|
+
* Gets federated data from a holon, optionally resolving holograms.
|
|
1267
|
+
*
|
|
1268
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
1269
|
+
* @param {string} lensName - Name of the lens
|
|
1270
|
+
* @param {Object} [options={}] - Options
|
|
1271
|
+
* @param {boolean} [options.resolveHolograms=true] - Whether to resolve hologram references
|
|
1272
|
+
* @returns {Promise<Array|null>} Array of data objects or null
|
|
1273
|
+
*/
|
|
783
1274
|
async getFederatedData(holonId, lensName, options = {}) {
|
|
784
1275
|
const { resolveHolograms = true } = options;
|
|
1276
|
+
const path = storage.buildPath(this.config.appName, holonId, lensName);
|
|
1277
|
+
const data = await storage.readAll(this.client, path);
|
|
785
1278
|
|
|
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] : []);
|
|
1279
|
+
if (!data || !resolveHolograms) return data;
|
|
802
1280
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
}
|
|
1281
|
+
// Resolve holograms using existing method
|
|
1282
|
+
return this._resolveHolograms(data);
|
|
806
1283
|
}
|
|
807
1284
|
|
|
1285
|
+
/**
|
|
1286
|
+
* Removes federation between two holons for a specific lens.
|
|
1287
|
+
*
|
|
1288
|
+
* @param {string} sourceHolon - Source holon H3 cell ID
|
|
1289
|
+
* @param {string} targetHolon - Target holon H3 cell ID
|
|
1290
|
+
* @param {string} lensName - Name of the lens
|
|
1291
|
+
* @returns {Promise<boolean>} Always returns true (idempotent operation)
|
|
1292
|
+
*/
|
|
808
1293
|
async unfederate(sourceHolon, targetHolon, lensName) {
|
|
809
|
-
//
|
|
810
|
-
const
|
|
811
|
-
|
|
1294
|
+
// Remove federation config for this relationship - idempotent
|
|
1295
|
+
const configPath = storage.buildPath(this.config.appName, sourceHolon, lensName, '_federation');
|
|
1296
|
+
try {
|
|
1297
|
+
await storage.deleteData(this.client, configPath);
|
|
1298
|
+
} catch (e) {
|
|
1299
|
+
// Ignore errors - already unfederated or doesn't exist
|
|
1300
|
+
}
|
|
812
1301
|
return true;
|
|
813
1302
|
}
|
|
814
1303
|
|
|
815
1304
|
/**
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
* @param {string}
|
|
819
|
-
* @param {string}
|
|
820
|
-
* @param {
|
|
821
|
-
* @
|
|
1305
|
+
* Updates local override values on a hologram.
|
|
1306
|
+
*
|
|
1307
|
+
* @param {string} holonId - H3 cell ID where the hologram exists
|
|
1308
|
+
* @param {string} lensName - Name of the lens
|
|
1309
|
+
* @param {string} dataId - ID of the hologram
|
|
1310
|
+
* @param {Object} overrides - Object containing local override values
|
|
1311
|
+
* @returns {Promise<boolean>} True if update succeeded
|
|
822
1312
|
*/
|
|
823
1313
|
async updateHologramOverrides(holonId, lensName, dataId, overrides) {
|
|
824
1314
|
return federation.updateHologramOverrides(
|
|
@@ -832,17 +1322,15 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
832
1322
|
}
|
|
833
1323
|
|
|
834
1324
|
/**
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
* @param {string}
|
|
838
|
-
* @param {
|
|
839
|
-
* @param {
|
|
840
|
-
* @
|
|
1325
|
+
* Creates a hologram (reference) object structure.
|
|
1326
|
+
*
|
|
1327
|
+
* @param {string} sourceHolon - Source holon H3 cell ID where the original data lives
|
|
1328
|
+
* @param {string} lensName - Name of the lens
|
|
1329
|
+
* @param {Object} data - Data object containing the id to reference
|
|
1330
|
+
* @param {string} [targetHolon=null] - Target holon for the hologram, defaults to sourceHolon
|
|
1331
|
+
* @returns {Object} Hologram object structure
|
|
841
1332
|
*/
|
|
842
1333
|
createHologram(sourceHolon, lensName, data, targetHolon = null) {
|
|
843
|
-
if (!data || !data.id) {
|
|
844
|
-
throw new Error('createHologram: data must have an id property');
|
|
845
|
-
}
|
|
846
1334
|
return federation.createHologram(
|
|
847
1335
|
sourceHolon,
|
|
848
1336
|
targetHolon || sourceHolon,
|
|
@@ -853,29 +1341,31 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
853
1341
|
}
|
|
854
1342
|
|
|
855
1343
|
/**
|
|
856
|
-
*
|
|
857
|
-
*
|
|
858
|
-
* @param {string} holonId -
|
|
859
|
-
* @param {string} lensName -
|
|
860
|
-
* @param {string} dataId -
|
|
861
|
-
* @returns {Promise<
|
|
1344
|
+
* Gets all active holograms (references) pointing to a specific data item.
|
|
1345
|
+
*
|
|
1346
|
+
* @param {string} holonId - H3 cell ID of the source holon
|
|
1347
|
+
* @param {string} lensName - Name of the lens
|
|
1348
|
+
* @param {string} dataId - ID of the source data
|
|
1349
|
+
* @returns {Promise<Array>} Array of active hologram references
|
|
862
1350
|
*/
|
|
863
1351
|
async getActiveHolograms(holonId, lensName, dataId) {
|
|
864
|
-
|
|
865
|
-
|
|
1352
|
+
// Read the source data and return its activeHolograms list
|
|
1353
|
+
const path = storage.buildPath(this.config.appName, holonId, lensName, dataId);
|
|
1354
|
+
const sourceData = await storage.read(this.client, path);
|
|
1355
|
+
if (!sourceData || !sourceData._meta || !sourceData._meta.activeHolograms) {
|
|
866
1356
|
return [];
|
|
867
1357
|
}
|
|
868
|
-
return
|
|
1358
|
+
return sourceData._meta.activeHolograms;
|
|
869
1359
|
}
|
|
870
1360
|
|
|
871
1361
|
/**
|
|
872
|
-
*
|
|
873
|
-
*
|
|
874
|
-
* @param {string} sourceHolon - Source holon ID
|
|
875
|
-
* @param {string} lensName -
|
|
876
|
-
* @param {string} dataId -
|
|
877
|
-
* @param {string} targetHolon - Target holon where hologram
|
|
878
|
-
* @returns {Promise<boolean>}
|
|
1362
|
+
* Removes an active hologram reference from a source data item.
|
|
1363
|
+
*
|
|
1364
|
+
* @param {string} sourceHolon - Source holon H3 cell ID
|
|
1365
|
+
* @param {string} lensName - Name of the lens
|
|
1366
|
+
* @param {string} dataId - ID of the source data
|
|
1367
|
+
* @param {string} targetHolon - Target holon where the hologram exists
|
|
1368
|
+
* @returns {Promise<boolean>} True if removal succeeded
|
|
879
1369
|
*/
|
|
880
1370
|
async removeActiveHologram(sourceHolon, lensName, dataId, targetHolon) {
|
|
881
1371
|
return federation.removeActiveHologram(
|
|
@@ -889,12 +1379,13 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
889
1379
|
}
|
|
890
1380
|
|
|
891
1381
|
/**
|
|
892
|
-
*
|
|
893
|
-
*
|
|
894
|
-
* @param {string}
|
|
895
|
-
* @param {string}
|
|
896
|
-
* @param {
|
|
897
|
-
* @
|
|
1382
|
+
* Deletes a hologram and cleans up its references.
|
|
1383
|
+
*
|
|
1384
|
+
* @param {string} holonId - H3 cell ID where the hologram exists
|
|
1385
|
+
* @param {string} lensName - Name of the lens
|
|
1386
|
+
* @param {string} dataId - ID of the hologram to delete
|
|
1387
|
+
* @param {Object} [options={}] - Delete options
|
|
1388
|
+
* @returns {Promise<boolean>} True if deletion succeeded
|
|
898
1389
|
*/
|
|
899
1390
|
async deleteHologram(holonId, lensName, dataId, options = {}) {
|
|
900
1391
|
return federation.deleteHologram(
|
|
@@ -908,18 +1399,19 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
908
1399
|
}
|
|
909
1400
|
|
|
910
1401
|
/**
|
|
911
|
-
*
|
|
912
|
-
*
|
|
1402
|
+
* Propagates data from source holon to target holon.
|
|
1403
|
+
*
|
|
913
1404
|
* @param {Object} data - Data to propagate
|
|
914
|
-
* @param {string} sourceHolon - Source holon ID
|
|
915
|
-
* @param {string} targetHolon - Target holon ID
|
|
916
|
-
* @param {string} lensName -
|
|
917
|
-
* @param {Object} options -
|
|
918
|
-
* @param {string} options.mode - 'reference'
|
|
919
|
-
* @returns {Promise<
|
|
1405
|
+
* @param {string} sourceHolon - Source holon H3 cell ID
|
|
1406
|
+
* @param {string} targetHolon - Target holon H3 cell ID
|
|
1407
|
+
* @param {string} lensName - Name of the lens
|
|
1408
|
+
* @param {Object} [options={}] - Propagation options
|
|
1409
|
+
* @param {string} [options.mode='reference'] - Mode: 'reference' creates hologram, 'copy' duplicates data
|
|
1410
|
+
* @returns {Promise<Object>} Result of propagation
|
|
920
1411
|
*/
|
|
921
1412
|
async propagateData(data, sourceHolon, targetHolon, lensName, options = {}) {
|
|
922
|
-
|
|
1413
|
+
// Extract mode from options, default to 'reference' for hologram creation
|
|
1414
|
+
const mode = options.mode || 'reference';
|
|
923
1415
|
return federation.propagateData(
|
|
924
1416
|
this.client,
|
|
925
1417
|
this.config.appName,
|
|
@@ -932,49 +1424,98 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
932
1424
|
}
|
|
933
1425
|
|
|
934
1426
|
/**
|
|
935
|
-
*
|
|
1427
|
+
* Resolves a hologram to its source data.
|
|
1428
|
+
*
|
|
936
1429
|
* @param {Object} hologram - Hologram object to resolve
|
|
937
|
-
* @
|
|
1430
|
+
* @param {Object} [options={}] - Resolution options
|
|
1431
|
+
* @returns {Promise<Object|null>} Resolved data or null if source not found
|
|
938
1432
|
*/
|
|
939
|
-
async resolveHologram(hologram) {
|
|
940
|
-
return federation.resolveHologram(this.client, hologram);
|
|
1433
|
+
async resolveHologram(hologram, options = {}) {
|
|
1434
|
+
return federation.resolveHologram(this.client, hologram, new Set(), [], { ...options, appname: this.config.appName });
|
|
941
1435
|
}
|
|
942
1436
|
|
|
943
1437
|
/**
|
|
944
|
-
*
|
|
945
|
-
*
|
|
946
|
-
* @
|
|
1438
|
+
* Checks if an object is a hologram (unresolved reference).
|
|
1439
|
+
*
|
|
1440
|
+
* @param {Object} data - Data object to check
|
|
1441
|
+
* @returns {boolean} True if the object is a hologram
|
|
947
1442
|
*/
|
|
948
1443
|
isHologram(data) {
|
|
949
1444
|
return federation.isHologram(data);
|
|
950
1445
|
}
|
|
951
1446
|
|
|
952
1447
|
/**
|
|
953
|
-
*
|
|
954
|
-
*
|
|
955
|
-
* @
|
|
1448
|
+
* Checks if an object is a resolved hologram (has _hologram metadata).
|
|
1449
|
+
*
|
|
1450
|
+
* @param {Object} data - Data object to check
|
|
1451
|
+
* @returns {boolean} True if the object is a resolved hologram
|
|
956
1452
|
*/
|
|
957
1453
|
isResolvedHologram(data) {
|
|
958
1454
|
return federation.isResolvedHologram(data);
|
|
959
1455
|
}
|
|
960
1456
|
|
|
961
1457
|
/**
|
|
962
|
-
*
|
|
963
|
-
*
|
|
964
|
-
* @
|
|
1458
|
+
* Gets the source information from a hologram or resolved hologram.
|
|
1459
|
+
*
|
|
1460
|
+
* @param {Object} data - Hologram or resolved hologram object
|
|
1461
|
+
* @returns {Object|null} Source information or null
|
|
965
1462
|
*/
|
|
966
1463
|
getHologramSource(data) {
|
|
967
1464
|
return federation.getHologramSource(data);
|
|
968
1465
|
}
|
|
969
1466
|
|
|
970
1467
|
/**
|
|
971
|
-
*
|
|
972
|
-
*
|
|
973
|
-
*
|
|
974
|
-
* @param {string}
|
|
975
|
-
* @param {
|
|
976
|
-
* @param {
|
|
977
|
-
* @
|
|
1468
|
+
* Updates platform-specific data for an active hologram entry.
|
|
1469
|
+
* Used by platform clients (Telegram, Discord, etc.) to store their message IDs.
|
|
1470
|
+
*
|
|
1471
|
+
* @param {string} sourceHolon - Source holon ID where the original data lives
|
|
1472
|
+
* @param {string} lensName - Lens name (e.g., 'quests', 'events')
|
|
1473
|
+
* @param {string} dataId - Data ID
|
|
1474
|
+
* @param {string} targetHolon - Target holon ID where the hologram exists
|
|
1475
|
+
* @param {string} platform - Platform name (e.g., 'telegram', 'discord')
|
|
1476
|
+
* @param {Object} platformData - Platform-specific data (e.g., { messageId: 123 })
|
|
1477
|
+
* @returns {Promise<boolean>} Success indicator
|
|
1478
|
+
*/
|
|
1479
|
+
async updateHologramPlatform(sourceHolon, lensName, dataId, targetHolon, platform, platformData) {
|
|
1480
|
+
return federation.updateActiveHologramPlatform(
|
|
1481
|
+
this.client,
|
|
1482
|
+
this.config.appName,
|
|
1483
|
+
sourceHolon,
|
|
1484
|
+
lensName,
|
|
1485
|
+
dataId,
|
|
1486
|
+
targetHolon,
|
|
1487
|
+
platform,
|
|
1488
|
+
platformData
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
/**
|
|
1493
|
+
* Gets active holograms for a data item, optionally filtered by platform.
|
|
1494
|
+
*
|
|
1495
|
+
* @param {string} sourceHolon - Source holon ID
|
|
1496
|
+
* @param {string} lensName - Lens name
|
|
1497
|
+
* @param {string} dataId - Data ID
|
|
1498
|
+
* @param {string} [platform] - Optional platform filter (e.g., 'telegram')
|
|
1499
|
+
* @returns {Promise<Array>} Array of hologram entries with platform data
|
|
1500
|
+
*/
|
|
1501
|
+
async getActiveHolograms(sourceHolon, lensName, dataId, platform = null) {
|
|
1502
|
+
return federation.getActiveHolograms(
|
|
1503
|
+
this.client,
|
|
1504
|
+
this.config.appName,
|
|
1505
|
+
sourceHolon,
|
|
1506
|
+
lensName,
|
|
1507
|
+
dataId,
|
|
1508
|
+
platform
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
/**
|
|
1513
|
+
* Cleans up circular hologram references in a holon.
|
|
1514
|
+
*
|
|
1515
|
+
* @param {string} holonId - H3 cell ID of the holon
|
|
1516
|
+
* @param {string} lensName - Name of the lens
|
|
1517
|
+
* @param {Object} [options={}] - Cleanup options
|
|
1518
|
+
* @returns {Promise<Object>} Cleanup results
|
|
978
1519
|
*/
|
|
979
1520
|
async cleanupCircularHolograms(holonId, lensName, options = {}) {
|
|
980
1521
|
return federation.cleanupCircularHolograms(
|
|
@@ -987,14 +1528,13 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
987
1528
|
}
|
|
988
1529
|
|
|
989
1530
|
/**
|
|
990
|
-
*
|
|
991
|
-
*
|
|
992
|
-
* @param {string} holonId -
|
|
993
|
-
* @param {string} lensName -
|
|
1531
|
+
* Cleans up circular hologram references for specific data IDs.
|
|
1532
|
+
*
|
|
1533
|
+
* @param {string} holonId - H3 cell ID of the holon
|
|
1534
|
+
* @param {string} lensName - Name of the lens
|
|
994
1535
|
* @param {string[]} dataIds - Array of data IDs to check
|
|
995
|
-
* @param {Object} options -
|
|
996
|
-
* @
|
|
997
|
-
* @returns {Promise<Object>} Result with cleanup info
|
|
1536
|
+
* @param {Object} [options={}] - Cleanup options
|
|
1537
|
+
* @returns {Promise<Object>} Cleanup results
|
|
998
1538
|
*/
|
|
999
1539
|
async cleanupCircularHologramsByIds(holonId, lensName, dataIds, options = {}) {
|
|
1000
1540
|
return federation.cleanupCircularHologramsByIds(
|
|
@@ -1008,130 +1548,59 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
1008
1548
|
}
|
|
1009
1549
|
|
|
1010
1550
|
/**
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
* @param {string}
|
|
1014
|
-
* @param {
|
|
1015
|
-
* @param {Object}
|
|
1016
|
-
* @param {
|
|
1017
|
-
* @param {boolean} options.
|
|
1018
|
-
* @param {
|
|
1019
|
-
* @returns {Promise<
|
|
1551
|
+
* Propagates data to all federated holons.
|
|
1552
|
+
*
|
|
1553
|
+
* @param {string} holonId - Source holon H3 cell ID
|
|
1554
|
+
* @param {string} lensName - Name of the lens
|
|
1555
|
+
* @param {Object} data - Data to propagate
|
|
1556
|
+
* @param {Object} [options={}] - Propagation options
|
|
1557
|
+
* @param {boolean} [options.useHolograms=true] - Whether to create holograms
|
|
1558
|
+
* @param {boolean} [options.resolveExisting=true] - Whether to resolve existing data
|
|
1559
|
+
* @returns {Promise<Array|undefined>} Array of propagation results or undefined if no targets
|
|
1020
1560
|
*/
|
|
1021
1561
|
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
1562
|
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
|
-
}
|
|
1563
|
+
// getFederation returns an object with federated array, not an array directly
|
|
1564
|
+
const targets = federationData?.federated || federationData?.outbound || [];
|
|
1565
|
+
if (!targets || targets.length === 0) return;
|
|
1566
|
+
|
|
1567
|
+
const { useHolograms = true, resolveExisting = true } = options;
|
|
1568
|
+
const results = [];
|
|
1569
|
+
|
|
1570
|
+
for (const targetHolonId of targets) {
|
|
1571
|
+
const result = await this.propagateData(
|
|
1572
|
+
data,
|
|
1573
|
+
holonId,
|
|
1574
|
+
targetHolonId,
|
|
1575
|
+
lensName,
|
|
1576
|
+
{ useHolograms, resolveExisting }
|
|
1577
|
+
);
|
|
1578
|
+
results.push({ parent: targetHolonId, ...result });
|
|
1109
1579
|
}
|
|
1110
1580
|
|
|
1111
|
-
return
|
|
1581
|
+
return results;
|
|
1112
1582
|
}
|
|
1113
1583
|
|
|
1584
|
+
/**
|
|
1585
|
+
* Gets the federation configuration for a holon.
|
|
1586
|
+
*
|
|
1587
|
+
* @param {string} holonId - H3 cell ID of the holon
|
|
1588
|
+
* @returns {Promise<Object|null>} Federation configuration object or null
|
|
1589
|
+
* @throws {Error} If holonId is not provided
|
|
1590
|
+
*/
|
|
1114
1591
|
async getFederation(holonId) {
|
|
1115
1592
|
if (!holonId) {
|
|
1116
1593
|
throw new Error('getFederation: Missing holon ID');
|
|
1117
1594
|
}
|
|
1118
|
-
const data = await this.
|
|
1595
|
+
const data = await this.readGlobal('federation', holonId);
|
|
1119
1596
|
if (!data) return null;
|
|
1120
1597
|
|
|
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
|
-
}
|
|
1598
|
+
if (!Array.isArray(data.inbound)) data.inbound = [];
|
|
1599
|
+
if (!Array.isArray(data.outbound)) data.outbound = [];
|
|
1600
|
+
if (!data.lensConfig || typeof data.lensConfig !== 'object') data.lensConfig = {};
|
|
1601
|
+
if (!data.partnerNames || typeof data.partnerNames !== 'object') data.partnerNames = {};
|
|
1131
1602
|
|
|
1132
|
-
// Backwards compatibility: populate federated from inbound/outbound if it doesn't exist
|
|
1133
1603
|
if (!Array.isArray(data.federated)) {
|
|
1134
|
-
// Combine inbound and outbound into a unique set
|
|
1135
1604
|
const allFederated = new Set([...data.inbound, ...data.outbound]);
|
|
1136
1605
|
data.federated = Array.from(allFederated);
|
|
1137
1606
|
}
|
|
@@ -1140,94 +1609,82 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
1140
1609
|
}
|
|
1141
1610
|
|
|
1142
1611
|
/**
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
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
|
|
1149
|
-
* @
|
|
1612
|
+
* Establishes a holon-level federation relationship.
|
|
1613
|
+
*
|
|
1614
|
+
* @param {string} sourceHolon - Source holon H3 cell ID
|
|
1615
|
+
* @param {string} targetHolon - Target holon H3 cell ID
|
|
1616
|
+
* @param {Object} [options={}] - Federation options
|
|
1617
|
+
* @param {Object} [options.lensConfig] - Lens configuration for federation
|
|
1618
|
+
* @param {string[]} [options.lensConfig.inbound] - Lenses for inbound federation
|
|
1619
|
+
* @param {string[]} [options.lensConfig.outbound] - Lenses for outbound federation
|
|
1620
|
+
* @param {string} [options.partnerName] - Human-readable name for the partner holon
|
|
1621
|
+
* @param {boolean} [options.skipPropagation=false] - Skip propagating existing data
|
|
1622
|
+
* @returns {Promise<boolean>} True if federation was established
|
|
1623
|
+
* @throws {Error} If trying to federate a holon with itself
|
|
1150
1624
|
*/
|
|
1151
1625
|
async federateHolon(sourceHolon, targetHolon, options = {}) {
|
|
1152
|
-
const { lensConfig = { inbound: [], outbound: [] } } = options;
|
|
1626
|
+
const { lensConfig = { inbound: [], outbound: [] }, partnerName = null, skipPropagation = false } = options;
|
|
1153
1627
|
|
|
1154
|
-
// Validate self-federation
|
|
1155
1628
|
if (sourceHolon === targetHolon) {
|
|
1156
1629
|
throw new Error('Cannot federate a holon with itself');
|
|
1157
1630
|
}
|
|
1158
1631
|
|
|
1159
|
-
|
|
1160
|
-
let federationData = await this.getGlobal('federation', sourceHolon) || {
|
|
1632
|
+
let federationData = await this.readGlobal('federation', sourceHolon) || {
|
|
1161
1633
|
id: sourceHolon,
|
|
1162
1634
|
name: sourceHolon,
|
|
1163
1635
|
federated: [],
|
|
1164
1636
|
inbound: [],
|
|
1165
1637
|
outbound: [],
|
|
1166
1638
|
lensConfig: {},
|
|
1639
|
+
partnerNames: {},
|
|
1167
1640
|
timestamp: Date.now()
|
|
1168
1641
|
};
|
|
1169
1642
|
|
|
1170
|
-
|
|
1171
|
-
if (!Array.isArray(federationData.
|
|
1172
|
-
|
|
1173
|
-
}
|
|
1174
|
-
if (!
|
|
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
|
-
}
|
|
1643
|
+
if (!Array.isArray(federationData.federated)) federationData.federated = [];
|
|
1644
|
+
if (!Array.isArray(federationData.inbound)) federationData.inbound = [];
|
|
1645
|
+
if (!Array.isArray(federationData.outbound)) federationData.outbound = [];
|
|
1646
|
+
if (!federationData.lensConfig || typeof federationData.lensConfig !== 'object') federationData.lensConfig = {};
|
|
1647
|
+
if (!federationData.partnerNames || typeof federationData.partnerNames !== 'object') federationData.partnerNames = {};
|
|
1183
1648
|
|
|
1184
|
-
// Always add target to federated list (tracks all federation links)
|
|
1185
1649
|
if (!federationData.federated.includes(targetHolon)) {
|
|
1186
1650
|
federationData.federated.push(targetHolon);
|
|
1187
1651
|
}
|
|
1188
1652
|
|
|
1189
|
-
//
|
|
1190
|
-
|
|
1653
|
+
// Store the partner's name if provided
|
|
1654
|
+
if (partnerName) {
|
|
1655
|
+
federationData.partnerNames[targetHolon] = partnerName;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1191
1658
|
if (lensConfig.outbound && lensConfig.outbound.length > 0) {
|
|
1192
1659
|
if (!federationData.outbound.includes(targetHolon)) {
|
|
1193
1660
|
federationData.outbound.push(targetHolon);
|
|
1194
1661
|
}
|
|
1195
1662
|
} else {
|
|
1196
|
-
// Remove from outbound if no outbound lenses
|
|
1197
1663
|
federationData.outbound = federationData.outbound.filter(id => id !== targetHolon);
|
|
1198
1664
|
}
|
|
1199
1665
|
|
|
1200
|
-
// If there are inbound lenses, add to inbound list
|
|
1201
1666
|
if (lensConfig.inbound && lensConfig.inbound.length > 0) {
|
|
1202
1667
|
if (!federationData.inbound.includes(targetHolon)) {
|
|
1203
1668
|
federationData.inbound.push(targetHolon);
|
|
1204
1669
|
}
|
|
1205
1670
|
} else {
|
|
1206
|
-
// Remove from inbound if no inbound lenses
|
|
1207
1671
|
federationData.inbound = federationData.inbound.filter(id => id !== targetHolon);
|
|
1208
1672
|
}
|
|
1209
1673
|
|
|
1210
|
-
// Store lens configuration for this target holon
|
|
1211
1674
|
federationData.lensConfig[targetHolon] = {
|
|
1212
1675
|
inbound: lensConfig.inbound || [],
|
|
1213
1676
|
outbound: lensConfig.outbound || [],
|
|
1214
1677
|
timestamp: Date.now()
|
|
1215
1678
|
};
|
|
1216
1679
|
|
|
1217
|
-
// Save federation metadata
|
|
1218
1680
|
const success = await this.writeGlobal('federation', federationData);
|
|
1219
|
-
|
|
1220
|
-
// Clear cache so getFederation returns fresh data immediately
|
|
1221
1681
|
this.clearCache('federation');
|
|
1222
1682
|
|
|
1223
|
-
//
|
|
1224
|
-
if (success) {
|
|
1225
|
-
// Federate each lens specified in inbound array (receive from target)
|
|
1683
|
+
// Only propagate existing data if skipPropagation is false
|
|
1684
|
+
if (success && !skipPropagation) {
|
|
1226
1685
|
for (const lens of (lensConfig.inbound || [])) {
|
|
1227
1686
|
await this.federate(targetHolon, sourceHolon, lens, { direction: 'outbound', mode: 'reference' });
|
|
1228
1687
|
}
|
|
1229
|
-
|
|
1230
|
-
// Federate each lens specified in outbound array (send to target)
|
|
1231
1688
|
for (const lens of (lensConfig.outbound || [])) {
|
|
1232
1689
|
await this.federate(sourceHolon, targetHolon, lens, { direction: 'outbound', mode: 'reference' });
|
|
1233
1690
|
}
|
|
@@ -1237,45 +1694,33 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
1237
1694
|
}
|
|
1238
1695
|
|
|
1239
1696
|
/**
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1242
|
-
* @param {string}
|
|
1243
|
-
* @
|
|
1697
|
+
* Removes a holon-level federation relationship.
|
|
1698
|
+
*
|
|
1699
|
+
* @param {string} sourceHolon - Source holon H3 cell ID
|
|
1700
|
+
* @param {string} targetHolon - Target holon H3 cell ID
|
|
1701
|
+
* @returns {Promise<boolean>} True if unfederation succeeded, false if no federation existed
|
|
1244
1702
|
*/
|
|
1245
1703
|
async unfederateHolon(sourceHolon, targetHolon) {
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
if (!federationData) {
|
|
1249
|
-
return false;
|
|
1250
|
-
}
|
|
1704
|
+
const federationData = await this.readGlobal('federation', sourceHolon);
|
|
1705
|
+
if (!federationData) return false;
|
|
1251
1706
|
|
|
1252
|
-
// Get lens config for this target before removing
|
|
1253
1707
|
const lensConfig = federationData.lensConfig?.[targetHolon];
|
|
1254
1708
|
|
|
1255
|
-
// Remove target from federation lists
|
|
1256
1709
|
federationData.federated = (federationData.federated || []).filter(id => id !== targetHolon);
|
|
1257
1710
|
federationData.inbound = (federationData.inbound || []).filter(id => id !== targetHolon);
|
|
1258
1711
|
federationData.outbound = (federationData.outbound || []).filter(id => id !== targetHolon);
|
|
1259
1712
|
|
|
1260
|
-
// Remove lens config for this target
|
|
1261
1713
|
if (federationData.lensConfig) {
|
|
1262
1714
|
delete federationData.lensConfig[targetHolon];
|
|
1263
1715
|
}
|
|
1264
1716
|
|
|
1265
|
-
// Update federation metadata
|
|
1266
1717
|
const success = await this.writeGlobal('federation', federationData);
|
|
1267
|
-
|
|
1268
|
-
// Clear cache so getFederation returns fresh data immediately
|
|
1269
1718
|
this.clearCache('federation');
|
|
1270
1719
|
|
|
1271
|
-
// Unfederate lens-specific federations
|
|
1272
1720
|
if (success && lensConfig) {
|
|
1273
|
-
// Unfederate each lens that was in inbound array
|
|
1274
1721
|
for (const lens of (lensConfig.inbound || [])) {
|
|
1275
1722
|
await this.unfederate(targetHolon, sourceHolon, lens);
|
|
1276
1723
|
}
|
|
1277
|
-
|
|
1278
|
-
// Unfederate each lens that was in outbound array
|
|
1279
1724
|
for (const lens of (lensConfig.outbound || [])) {
|
|
1280
1725
|
await this.unfederate(sourceHolon, targetHolon, lens);
|
|
1281
1726
|
}
|
|
@@ -1285,47 +1730,59 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
1285
1730
|
}
|
|
1286
1731
|
|
|
1287
1732
|
/**
|
|
1288
|
-
*
|
|
1289
|
-
*
|
|
1290
|
-
* @param {string}
|
|
1291
|
-
* @
|
|
1733
|
+
* Gets the federation configuration between two holons.
|
|
1734
|
+
*
|
|
1735
|
+
* @param {string} sourceHolon - Source holon H3 cell ID
|
|
1736
|
+
* @param {string} targetHolon - Target holon H3 cell ID
|
|
1737
|
+
* @returns {Promise<Object|null>} Lens configuration or null
|
|
1292
1738
|
*/
|
|
1293
1739
|
async getFederatedConfig(sourceHolon, targetHolon) {
|
|
1294
|
-
const federationData = await this.
|
|
1295
|
-
if (!federationData || !federationData.lensConfig)
|
|
1296
|
-
return null;
|
|
1297
|
-
}
|
|
1740
|
+
const federationData = await this.readGlobal('federation', sourceHolon);
|
|
1741
|
+
if (!federationData || !federationData.lensConfig) return null;
|
|
1298
1742
|
return federationData.lensConfig[targetHolon] || null;
|
|
1299
1743
|
}
|
|
1300
1744
|
|
|
1301
1745
|
// === Hierarchical Operations ===
|
|
1746
|
+
|
|
1747
|
+
/**
|
|
1748
|
+
* Performs hierarchical upcast aggregation.
|
|
1749
|
+
* Aggregates data from child holons up to parent holons in the H3 hierarchy.
|
|
1750
|
+
*
|
|
1751
|
+
* @param {string} holonId - Starting holon H3 cell ID
|
|
1752
|
+
* @param {string} lensName - Name of the lens
|
|
1753
|
+
* @param {string} dataId - ID of the data to upcast
|
|
1754
|
+
* @param {Object} [options={}] - Upcast options
|
|
1755
|
+
* @returns {Promise<Object>} Aggregation result
|
|
1756
|
+
*/
|
|
1302
1757
|
async upcast(holonId, lensName, dataId, options = {}) {
|
|
1303
|
-
return hierarchical.upcast(this
|
|
1758
|
+
return hierarchical.upcast(this, holonId, lensName, dataId, options);
|
|
1304
1759
|
}
|
|
1305
1760
|
|
|
1306
|
-
// ===
|
|
1761
|
+
// === Subscriptions ===
|
|
1762
|
+
|
|
1763
|
+
/**
|
|
1764
|
+
* Subscribes to real-time updates for a holon and lens.
|
|
1765
|
+
*
|
|
1766
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
1767
|
+
* @param {string} lensName - Name of the lens
|
|
1768
|
+
* @param {Function} callback - Callback function called with updated data
|
|
1769
|
+
* @param {Object} [options={}] - Subscription options
|
|
1770
|
+
* @returns {{unsubscribe: Function}} Subscription object with unsubscribe method
|
|
1771
|
+
* @throws {TypeError} If callback is not a function
|
|
1772
|
+
*/
|
|
1307
1773
|
subscribe(holonId, lensName, callback, options = {}) {
|
|
1308
1774
|
if (typeof callback !== 'function') {
|
|
1309
1775
|
throw new TypeError('callback must be a function');
|
|
1310
1776
|
}
|
|
1311
|
-
|
|
1312
1777
|
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
|
-
|
|
1778
|
+
const subscriptionOptions = { realtimeOnly: true, ...options };
|
|
1320
1779
|
const subscriptionId = `${holonId}-${lensName}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1321
1780
|
let innerSubscription = null;
|
|
1322
1781
|
let unsubscribeCalled = false;
|
|
1323
1782
|
|
|
1324
|
-
// Start async subscription setup in background
|
|
1325
1783
|
subscriptions.createSubscription(this.client, path, callback, subscriptionOptions)
|
|
1326
1784
|
.then(subscription => {
|
|
1327
1785
|
innerSubscription = subscription;
|
|
1328
|
-
// If unsubscribe was called before setup completed, clean up immediately
|
|
1329
1786
|
if (unsubscribeCalled) {
|
|
1330
1787
|
subscription.unsubscribe();
|
|
1331
1788
|
} else {
|
|
@@ -1336,1334 +1793,512 @@ export class HoloSphere extends HoloSphereCore {
|
|
|
1336
1793
|
this._log('ERROR', 'Subscription setup failed', { path, error: err.message });
|
|
1337
1794
|
});
|
|
1338
1795
|
|
|
1339
|
-
this._metrics.subscriptions
|
|
1340
|
-
|
|
1341
|
-
// Return synchronous subscription object
|
|
1796
|
+
this._metrics.subscriptions = (this._metrics.subscriptions || 0) + 1;
|
|
1342
1797
|
return {
|
|
1343
1798
|
unsubscribe: () => {
|
|
1344
1799
|
unsubscribeCalled = true;
|
|
1345
1800
|
if (innerSubscription) {
|
|
1346
1801
|
this.subscriptionRegistry.unregister(subscriptionId);
|
|
1347
1802
|
}
|
|
1348
|
-
}
|
|
1803
|
+
}
|
|
1349
1804
|
};
|
|
1350
1805
|
}
|
|
1351
1806
|
|
|
1352
1807
|
/**
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1355
|
-
* @param {string}
|
|
1356
|
-
* @param {
|
|
1357
|
-
* @param {
|
|
1358
|
-
* @
|
|
1808
|
+
* Subscribes to real-time updates for a global table.
|
|
1809
|
+
*
|
|
1810
|
+
* @param {string} table - Name of the global table
|
|
1811
|
+
* @param {string} key - Key to subscribe to
|
|
1812
|
+
* @param {Function} callback - Callback function called with updated data
|
|
1813
|
+
* @param {Object} [options={}] - Subscription options
|
|
1814
|
+
* @returns {Promise<{unsubscribe: Function}>} Subscription object
|
|
1359
1815
|
*/
|
|
1360
1816
|
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
|
-
};
|
|
1817
|
+
return globalTables.subscribeGlobal(
|
|
1818
|
+
this.client,
|
|
1819
|
+
this.config.appName,
|
|
1820
|
+
table,
|
|
1821
|
+
key,
|
|
1822
|
+
callback,
|
|
1823
|
+
options
|
|
1824
|
+
);
|
|
1385
1825
|
}
|
|
1386
1826
|
|
|
1387
1827
|
// === Crypto Operations ===
|
|
1828
|
+
|
|
1829
|
+
/**
|
|
1830
|
+
* Derives a public key from a private key.
|
|
1831
|
+
*
|
|
1832
|
+
* @param {string} privateKey - Hex-encoded private key
|
|
1833
|
+
* @returns {Promise<string>} Hex-encoded public key
|
|
1834
|
+
*/
|
|
1388
1835
|
async getPublicKey(privateKey) {
|
|
1389
1836
|
return crypto.getPublicKey(privateKey);
|
|
1390
1837
|
}
|
|
1391
1838
|
|
|
1839
|
+
/**
|
|
1840
|
+
* Signs content with a private key.
|
|
1841
|
+
*
|
|
1842
|
+
* @param {string|Object} content - Content to sign
|
|
1843
|
+
* @param {string} privateKey - Hex-encoded private key
|
|
1844
|
+
* @returns {Promise<string>} Hex-encoded signature
|
|
1845
|
+
*/
|
|
1392
1846
|
async sign(content, privateKey) {
|
|
1393
1847
|
return crypto.sign(content, privateKey);
|
|
1394
1848
|
}
|
|
1395
1849
|
|
|
1850
|
+
/**
|
|
1851
|
+
* Verifies a signature against content and public key.
|
|
1852
|
+
*
|
|
1853
|
+
* @param {string|Object} content - Original content
|
|
1854
|
+
* @param {string} signature - Hex-encoded signature
|
|
1855
|
+
* @param {string} publicKey - Hex-encoded public key
|
|
1856
|
+
* @returns {Promise<boolean>} True if signature is valid
|
|
1857
|
+
*/
|
|
1396
1858
|
async verify(content, signature, publicKey) {
|
|
1397
1859
|
return crypto.verify(content, signature, publicKey);
|
|
1398
1860
|
}
|
|
1399
1861
|
|
|
1862
|
+
/**
|
|
1863
|
+
* Issues a capability token for authorization.
|
|
1864
|
+
*
|
|
1865
|
+
* @param {string[]} permissions - Array of permissions ('read', 'write', 'delete')
|
|
1866
|
+
* @param {Object} scope - Scope of the capability (holonId, lensName, etc.)
|
|
1867
|
+
* @param {string} recipient - Public key of the recipient
|
|
1868
|
+
* @param {Object} [options] - Additional options
|
|
1869
|
+
* @returns {Promise<string>} Signed capability token
|
|
1870
|
+
*/
|
|
1400
1871
|
async issueCapability(permissions, scope, recipient, options) {
|
|
1401
1872
|
return crypto.issueCapability(permissions, scope, recipient, options);
|
|
1402
1873
|
}
|
|
1403
1874
|
|
|
1875
|
+
/**
|
|
1876
|
+
* Verifies a capability token.
|
|
1877
|
+
*
|
|
1878
|
+
* @param {string} token - Capability token to verify
|
|
1879
|
+
* @param {string} requiredPermission - Required permission to check
|
|
1880
|
+
* @param {Object} scope - Scope to verify against
|
|
1881
|
+
* @returns {Promise<boolean>} True if token is valid and has required permission
|
|
1882
|
+
*/
|
|
1404
1883
|
async verifyCapability(token, requiredPermission, scope) {
|
|
1405
1884
|
return crypto.verifyCapability(token, requiredPermission, scope);
|
|
1406
1885
|
}
|
|
1407
1886
|
|
|
1408
1887
|
// === Social Protocol Operations ===
|
|
1888
|
+
|
|
1889
|
+
/**
|
|
1890
|
+
* Publishes a Nostr event to a holon.
|
|
1891
|
+
*
|
|
1892
|
+
* @param {Object} event - Nostr event object
|
|
1893
|
+
* @param {number} event.kind - Event kind
|
|
1894
|
+
* @param {string} event.content - Event content
|
|
1895
|
+
* @param {Array} [event.tags] - Event tags
|
|
1896
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
1897
|
+
* @param {string} [lensName='social'] - Name of the lens
|
|
1898
|
+
* @returns {Promise<boolean>} True if publish succeeded
|
|
1899
|
+
*/
|
|
1409
1900
|
async publishNostr(event, holonId, lensName = 'social') {
|
|
1410
|
-
// Validate
|
|
1411
|
-
social.validateNostrEvent(event, true, true);
|
|
1901
|
+
// Validate Nostr event format
|
|
1902
|
+
social.validateNostrEvent(event, true, true); // partial=true, throwOnError=true
|
|
1412
1903
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1904
|
+
const enrichedEvent = {
|
|
1905
|
+
...event,
|
|
1906
|
+
tags: [...(event.tags || []), ['h', holonId], ['l', lensName]],
|
|
1907
|
+
};
|
|
1908
|
+
const signedEvent = nostrUtils.signEvent(enrichedEvent, this.client.privateKey);
|
|
1418
1909
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
}
|
|
1910
|
+
// Add protocol field for querySocial filtering
|
|
1911
|
+
const eventWithProtocol = {
|
|
1912
|
+
...signedEvent,
|
|
1913
|
+
protocol: 'nostr',
|
|
1914
|
+
};
|
|
1424
1915
|
|
|
1425
|
-
const
|
|
1426
|
-
|
|
1916
|
+
const path = storage.buildPath(this.config.appName, holonId, lensName, signedEvent.id);
|
|
1917
|
+
await storage.write(this.client, path, eventWithProtocol);
|
|
1918
|
+
return true;
|
|
1427
1919
|
}
|
|
1428
1920
|
|
|
1921
|
+
/**
|
|
1922
|
+
* Publishes an ActivityPub object to a holon.
|
|
1923
|
+
*
|
|
1924
|
+
* @param {Object} object - ActivityPub object
|
|
1925
|
+
* @param {string} object.type - ActivityPub type (Note, Article, etc.)
|
|
1926
|
+
* @param {string} [object.actor] - Actor ID (defaults to client public key)
|
|
1927
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
1928
|
+
* @param {string} [lensName='social'] - Name of the lens
|
|
1929
|
+
* @returns {Promise<boolean>} True if publish succeeded
|
|
1930
|
+
*/
|
|
1429
1931
|
async publishActivityPub(object, holonId, lensName = 'social') {
|
|
1430
|
-
// Validate
|
|
1431
|
-
social.validateActivityPubObject(object, true);
|
|
1932
|
+
// Validate ActivityPub object format
|
|
1933
|
+
social.validateActivityPubObject(object, true); // throwOnError=true
|
|
1432
1934
|
|
|
1433
|
-
const
|
|
1434
|
-
|
|
1935
|
+
const activity = social.transformActivityPubObject({
|
|
1936
|
+
...object,
|
|
1937
|
+
actor: object.actor || this.client.publicKey,
|
|
1938
|
+
});
|
|
1939
|
+
return this.write(holonId, lensName, activity);
|
|
1435
1940
|
}
|
|
1436
1941
|
|
|
1942
|
+
/**
|
|
1943
|
+
* Queries social protocol data from a holon.
|
|
1944
|
+
*
|
|
1945
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
1946
|
+
* @param {Object} [options={}] - Query options
|
|
1947
|
+
* @param {string} [options.lens='social'] - Name of the lens
|
|
1948
|
+
* @param {string} [options.protocol] - Filter by protocol ('nostr', 'activitypub', 'all')
|
|
1949
|
+
* @param {string} [options.type] - Filter by content type
|
|
1950
|
+
* @param {number} [options.since] - Filter events after this timestamp
|
|
1951
|
+
* @param {number} [options.until] - Filter events before this timestamp
|
|
1952
|
+
* @returns {Promise<Array>} Array of matching social items
|
|
1953
|
+
*/
|
|
1437
1954
|
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;
|
|
1955
|
+
const lensName = options.lens || 'social';
|
|
1956
|
+
const data = await this.read(holonId, lensName);
|
|
1957
|
+
|
|
1958
|
+
if (!data) return [];
|
|
1959
|
+
const items = Array.isArray(data) ? data : [data];
|
|
1960
|
+
|
|
1961
|
+
return items.filter(item => {
|
|
1962
|
+
// 'all' means don't filter by protocol
|
|
1963
|
+
if (options.protocol && options.protocol !== 'all' && item.protocol !== options.protocol) return false;
|
|
1964
|
+
if (options.type && item.type !== options.type) return false;
|
|
1965
|
+
if (options.since && item.created_at < options.since) return false;
|
|
1966
|
+
if (options.until && item.created_at > options.until) return false;
|
|
1967
|
+
return true;
|
|
1968
|
+
});
|
|
1460
1969
|
}
|
|
1461
1970
|
|
|
1971
|
+
/**
|
|
1972
|
+
* Verifies a Nostr event signature.
|
|
1973
|
+
*
|
|
1974
|
+
* @param {Object} event - Nostr event to verify
|
|
1975
|
+
* @param {string} event.id - Event ID
|
|
1976
|
+
* @param {string} event.pubkey - Author public key
|
|
1977
|
+
* @param {number} event.created_at - Creation timestamp
|
|
1978
|
+
* @param {number} event.kind - Event kind
|
|
1979
|
+
* @param {Array} event.tags - Event tags
|
|
1980
|
+
* @param {string} event.content - Event content
|
|
1981
|
+
* @param {string} event.sig - Event signature
|
|
1982
|
+
* @returns {Promise<boolean>} True if event signature is valid
|
|
1983
|
+
*/
|
|
1462
1984
|
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);
|
|
1985
|
+
// Extract only standard Nostr event fields for verification
|
|
1986
|
+
// (nostr-tools can't serialize events with extra properties like 'protocol')
|
|
1987
|
+
const { id, pubkey, created_at, kind, tags, content, sig } = event;
|
|
1988
|
+
const standardEvent = { id, pubkey, created_at, kind, tags, content, sig };
|
|
1989
|
+
return nostrUtils.verifyEvent(standardEvent);
|
|
1473
1990
|
}
|
|
1474
1991
|
|
|
1475
1992
|
// === Metrics ===
|
|
1993
|
+
|
|
1994
|
+
/**
|
|
1995
|
+
* Gets performance metrics for this HoloSphere instance.
|
|
1996
|
+
*
|
|
1997
|
+
* @returns {Object} Metrics object
|
|
1998
|
+
* @returns {number} return.reads - Number of read operations
|
|
1999
|
+
* @returns {number} return.writes - Number of write operations
|
|
2000
|
+
* @returns {number} return.deletes - Number of delete operations
|
|
2001
|
+
* @returns {number} return.federations - Number of federation operations
|
|
2002
|
+
* @returns {number} return.subscriptions - Number of active subscriptions
|
|
2003
|
+
* @returns {number} return.avgReadTime - Average read time in milliseconds
|
|
2004
|
+
* @returns {number} return.avgWriteTime - Average write time in milliseconds
|
|
2005
|
+
*/
|
|
1476
2006
|
metrics() {
|
|
1477
|
-
const
|
|
2007
|
+
const reads = this._metrics.reads || 0;
|
|
2008
|
+
const writes = this._metrics.writes || 0;
|
|
2009
|
+
const totalReadTime = this._metrics.totalReadTime || 0;
|
|
2010
|
+
const totalWriteTime = this._metrics.totalWriteTime || 0;
|
|
2011
|
+
|
|
1478
2012
|
return {
|
|
1479
|
-
|
|
2013
|
+
reads,
|
|
2014
|
+
writes,
|
|
2015
|
+
deletes: this._metrics.deletes || 0,
|
|
1480
2016
|
federations: this._metrics.federations || 0,
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
2017
|
+
subscriptions: this._metrics.subscriptions || 0,
|
|
2018
|
+
avgReadTime: reads > 0 ? totalReadTime / reads : 0,
|
|
2019
|
+
avgWriteTime: writes > 0 ? totalWriteTime / writes : 0,
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/**
|
|
2024
|
+
* Returns the current status of optimistic caches for debugging.
|
|
2025
|
+
* Useful for understanding what data is pending sync or marked as deleted.
|
|
2026
|
+
*
|
|
2027
|
+
* @returns {Object} Cache status object
|
|
2028
|
+
* @returns {number} return.writeCacheSize - Number of items in write cache (pending sync)
|
|
2029
|
+
* @returns {number} return.deleteCacheSize - Number of items in delete cache (pending delete sync)
|
|
2030
|
+
* @returns {Array<Object>} return.writeCacheEntries - Array of write cache entries with path and age
|
|
2031
|
+
* @returns {Array<string>} return.deleteCachePaths - Array of paths marked as deleted
|
|
2032
|
+
*/
|
|
2033
|
+
getCacheStatus() {
|
|
2034
|
+
const now = Date.now();
|
|
2035
|
+
const writeCacheEntries = [];
|
|
2036
|
+
|
|
2037
|
+
for (const [path, entry] of this._writeCache.entries()) {
|
|
2038
|
+
writeCacheEntries.push({
|
|
2039
|
+
path,
|
|
2040
|
+
dataId: entry.data?.id,
|
|
2041
|
+
age: `${now - entry.timestamp}ms`,
|
|
2042
|
+
timestamp: new Date(entry.timestamp).toISOString()
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
return {
|
|
2047
|
+
writeCacheSize: this._writeCache.size,
|
|
2048
|
+
deleteCacheSize: this._deleteCache.size,
|
|
2049
|
+
writeCacheEntries,
|
|
2050
|
+
deleteCachePaths: Array.from(this._deleteCache)
|
|
1487
2051
|
};
|
|
1488
2052
|
}
|
|
1489
2053
|
|
|
1490
|
-
|
|
1491
|
-
|
|
2054
|
+
/**
|
|
2055
|
+
* Clears the optimistic caches. Useful for testing or resetting state.
|
|
2056
|
+
* Warning: This may cause inconsistencies if there are pending syncs.
|
|
2057
|
+
*/
|
|
2058
|
+
clearCaches() {
|
|
2059
|
+
const writeSize = this._writeCache.size;
|
|
2060
|
+
const deleteSize = this._deleteCache.size;
|
|
2061
|
+
this._writeCache.clear();
|
|
2062
|
+
this._deleteCache.clear();
|
|
2063
|
+
this._log('WARN', '🧹 CACHES CLEARED', {
|
|
2064
|
+
writeCacheCleared: writeSize,
|
|
2065
|
+
deleteCacheCleared: deleteSize
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
// === Aliases ===
|
|
1492
2070
|
|
|
1493
2071
|
/**
|
|
1494
|
-
* Alias for write
|
|
1495
|
-
* @
|
|
2072
|
+
* Alias for {@link HoloSphereBase#write}.
|
|
2073
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
2074
|
+
* @param {string} lensName - Name of the lens
|
|
2075
|
+
* @param {Object} data - Data to write
|
|
2076
|
+
* @param {Object} [options] - Write options
|
|
2077
|
+
* @returns {Promise<boolean>}
|
|
1496
2078
|
*/
|
|
1497
2079
|
async put(holonId, lensName, data, options = {}) {
|
|
1498
2080
|
return this.write(holonId, lensName, data, options);
|
|
1499
2081
|
}
|
|
1500
2082
|
|
|
1501
2083
|
/**
|
|
1502
|
-
* Alias for read
|
|
1503
|
-
* @
|
|
2084
|
+
* Alias for {@link HoloSphereBase#read}.
|
|
2085
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
2086
|
+
* @param {string} lensName - Name of the lens
|
|
2087
|
+
* @param {string|null} [dataId] - Specific data ID or null
|
|
2088
|
+
* @param {Object} [options] - Read options
|
|
2089
|
+
* @returns {Promise<Object|Array|null>}
|
|
1504
2090
|
*/
|
|
1505
2091
|
async get(holonId, lensName, dataId = null, options = {}) {
|
|
1506
2092
|
return this.read(holonId, lensName, dataId, options);
|
|
1507
2093
|
}
|
|
1508
2094
|
|
|
1509
2095
|
/**
|
|
1510
|
-
* Alias for delete
|
|
1511
|
-
* @
|
|
2096
|
+
* Alias for {@link HoloSphereBase#delete}.
|
|
2097
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
2098
|
+
* @param {string} lensName - Name of the lens
|
|
2099
|
+
* @param {string} dataId - Data ID to delete
|
|
2100
|
+
* @param {Object} [options] - Delete options
|
|
2101
|
+
* @returns {Promise<boolean>}
|
|
1512
2102
|
*/
|
|
1513
2103
|
async remove(holonId, lensName, dataId, options = {}) {
|
|
1514
2104
|
return this.delete(holonId, lensName, dataId, options);
|
|
1515
2105
|
}
|
|
1516
2106
|
|
|
1517
2107
|
/**
|
|
1518
|
-
* Alias for writeGlobal
|
|
1519
|
-
* @
|
|
2108
|
+
* Alias for {@link HoloSphereBase#writeGlobal}.
|
|
2109
|
+
* @param {string} table - Global table name
|
|
2110
|
+
* @param {Object} data - Data to write
|
|
2111
|
+
* @returns {Promise<boolean>}
|
|
1520
2112
|
*/
|
|
1521
2113
|
async putGlobal(table, data) {
|
|
1522
2114
|
return this.writeGlobal(table, data);
|
|
1523
2115
|
}
|
|
1524
2116
|
|
|
1525
2117
|
/**
|
|
1526
|
-
* Alias for readGlobal
|
|
1527
|
-
* @
|
|
2118
|
+
* Alias for {@link HoloSphereBase#readGlobal}.
|
|
2119
|
+
* @param {string} table - Global table name
|
|
2120
|
+
* @param {string|null} [key] - Key to read
|
|
2121
|
+
* @returns {Promise<Object|Array|null>}
|
|
1528
2122
|
*/
|
|
1529
2123
|
async getGlobal(table, key = null) {
|
|
1530
2124
|
return this.readGlobal(table, key);
|
|
1531
2125
|
}
|
|
1532
2126
|
|
|
1533
2127
|
/**
|
|
1534
|
-
* Alias for deleteGlobal
|
|
1535
|
-
* @
|
|
2128
|
+
* Alias for {@link HoloSphereBase#deleteGlobal}.
|
|
2129
|
+
* @param {string} table - Global table name
|
|
2130
|
+
* @param {string} key - Key to delete
|
|
2131
|
+
* @returns {Promise<boolean>}
|
|
1536
2132
|
*/
|
|
1537
2133
|
async removeGlobal(table, key) {
|
|
1538
2134
|
return this.deleteGlobal(table, key);
|
|
1539
2135
|
}
|
|
1540
2136
|
|
|
1541
2137
|
/**
|
|
1542
|
-
* Alias for setSchema
|
|
1543
|
-
* @
|
|
2138
|
+
* Alias for {@link HoloSphereBase#setSchema}.
|
|
2139
|
+
* @param {string} lensName - Lens name
|
|
2140
|
+
* @param {Object|string} schemaObj - Schema object or URI
|
|
2141
|
+
* @param {boolean} [strict] - Strict mode
|
|
2142
|
+
* @returns {Promise<void>}
|
|
1544
2143
|
*/
|
|
1545
2144
|
async defineSchema(lensName, schemaObj, strict = false) {
|
|
1546
2145
|
return this.setSchema(lensName, schemaObj, strict);
|
|
1547
2146
|
}
|
|
1548
2147
|
|
|
1549
2148
|
/**
|
|
1550
|
-
* Alias for getSchema
|
|
1551
|
-
* @
|
|
2149
|
+
* Alias for {@link HoloSphereBase#getSchema}.
|
|
2150
|
+
* @param {string} lensName - Lens name
|
|
2151
|
+
* @returns {Promise<Object|null>}
|
|
1552
2152
|
*/
|
|
1553
2153
|
async fetchSchema(lensName) {
|
|
1554
2154
|
return this.getSchema(lensName);
|
|
1555
2155
|
}
|
|
1556
2156
|
|
|
1557
2157
|
/**
|
|
1558
|
-
* Alias for clearSchema
|
|
1559
|
-
* @
|
|
2158
|
+
* Alias for {@link HoloSphereBase#clearSchema}.
|
|
2159
|
+
* @param {string} lensName - Lens name
|
|
2160
|
+
* @returns {Promise<void>}
|
|
1560
2161
|
*/
|
|
1561
2162
|
async removeSchema(lensName) {
|
|
1562
2163
|
return this.clearSchema(lensName);
|
|
1563
2164
|
}
|
|
1564
2165
|
|
|
1565
2166
|
/**
|
|
1566
|
-
*
|
|
1567
|
-
* @
|
|
2167
|
+
* Alias for {@link HoloSphereBase#write}.
|
|
2168
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
2169
|
+
* @param {string} lensName - Name of the lens
|
|
2170
|
+
* @param {Object} data - Data to write
|
|
2171
|
+
* @param {Object} [options] - Write options
|
|
2172
|
+
* @returns {Promise<boolean>}
|
|
1568
2173
|
*/
|
|
1569
2174
|
async store(holonId, lensName, data, options = {}) {
|
|
1570
2175
|
return this.write(holonId, lensName, data, options);
|
|
1571
2176
|
}
|
|
1572
2177
|
|
|
1573
2178
|
/**
|
|
1574
|
-
*
|
|
1575
|
-
* @
|
|
2179
|
+
* Alias for {@link HoloSphereBase#read}.
|
|
2180
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
2181
|
+
* @param {string} lensName - Name of the lens
|
|
2182
|
+
* @param {string|null} [dataId] - Specific data ID or null
|
|
2183
|
+
* @param {Object} [options] - Read options
|
|
2184
|
+
* @returns {Promise<Object|Array|null>}
|
|
1576
2185
|
*/
|
|
1577
2186
|
async fetch(holonId, lensName, dataId = null, options = {}) {
|
|
1578
2187
|
return this.read(holonId, lensName, dataId, options);
|
|
1579
2188
|
}
|
|
1580
2189
|
|
|
1581
2190
|
/**
|
|
1582
|
-
*
|
|
1583
|
-
* @
|
|
2191
|
+
* Alias for {@link HoloSphereBase#write}.
|
|
2192
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
2193
|
+
* @param {string} lensName - Name of the lens
|
|
2194
|
+
* @param {Object} data - Data to write
|
|
2195
|
+
* @param {Object} [options] - Write options
|
|
2196
|
+
* @returns {Promise<boolean>}
|
|
1584
2197
|
*/
|
|
1585
2198
|
async save(holonId, lensName, data, options = {}) {
|
|
1586
2199
|
return this.write(holonId, lensName, data, options);
|
|
1587
2200
|
}
|
|
1588
2201
|
|
|
1589
2202
|
/**
|
|
1590
|
-
*
|
|
1591
|
-
* @
|
|
2203
|
+
* Alias for {@link HoloSphereBase#read}.
|
|
2204
|
+
* @param {string} holonId - H3 cell ID for the holon
|
|
2205
|
+
* @param {string} lensName - Name of the lens
|
|
2206
|
+
* @param {string|null} [dataId] - Specific data ID or null
|
|
2207
|
+
* @param {Object} [options] - Read options
|
|
2208
|
+
* @returns {Promise<Object|Array|null>}
|
|
1592
2209
|
*/
|
|
1593
2210
|
async load(holonId, lensName, dataId = null, options = {}) {
|
|
1594
2211
|
return this.read(holonId, lensName, dataId, options);
|
|
1595
2212
|
}
|
|
1596
2213
|
|
|
2214
|
+
// === Cache ===
|
|
2215
|
+
|
|
1597
2216
|
/**
|
|
1598
|
-
*
|
|
1599
|
-
*
|
|
1600
|
-
* @param {string} [pattern] -
|
|
2217
|
+
* Clears the internal cache.
|
|
2218
|
+
*
|
|
2219
|
+
* @param {string|null} [pattern=null] - If provided, only clear cache entries matching this pattern
|
|
1601
2220
|
*/
|
|
1602
2221
|
clearCache(pattern = null) {
|
|
1603
|
-
if (
|
|
1604
|
-
this.
|
|
2222
|
+
if (pattern) {
|
|
2223
|
+
for (const key of this._cache.keys()) {
|
|
2224
|
+
if (key.includes(pattern)) {
|
|
2225
|
+
this._cache.delete(key);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
} else {
|
|
2229
|
+
this._cache.clear();
|
|
1605
2230
|
}
|
|
1606
2231
|
}
|
|
2232
|
+
}
|
|
1607
2233
|
|
|
1608
|
-
|
|
2234
|
+
/**
|
|
2235
|
+
* Main HoloSphere class - composed from base + all mixins
|
|
2236
|
+
*/
|
|
2237
|
+
export const HoloSphere = withContractMethods(
|
|
2238
|
+
withFederationMethods(
|
|
2239
|
+
withAIMethods(HoloSphereBase)
|
|
2240
|
+
)
|
|
2241
|
+
);
|
|
1609
2242
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
*/
|
|
1614
|
-
_requireAI() {
|
|
1615
|
-
if (!this._ai) {
|
|
1616
|
-
throw new Error('AI services not initialized. Provide openaiKey in config.');
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
2243
|
+
// Export error classes
|
|
2244
|
+
export { AuthorizationError };
|
|
2245
|
+
export { ValidationError };
|
|
1619
2246
|
|
|
1620
|
-
|
|
2247
|
+
// Re-export AI module classes
|
|
2248
|
+
export {
|
|
2249
|
+
LLMService,
|
|
2250
|
+
SchemaExtractor,
|
|
2251
|
+
JSONOps,
|
|
2252
|
+
Embeddings,
|
|
2253
|
+
Council,
|
|
2254
|
+
TTS,
|
|
2255
|
+
VOICES,
|
|
2256
|
+
MODELS,
|
|
2257
|
+
NLQuery,
|
|
2258
|
+
Classifier,
|
|
2259
|
+
SpatialAnalysis,
|
|
2260
|
+
SmartAggregation,
|
|
2261
|
+
FederationAdvisor,
|
|
2262
|
+
RelationshipDiscovery,
|
|
2263
|
+
TaskBreakdown,
|
|
2264
|
+
H3AI,
|
|
2265
|
+
};
|
|
1621
2266
|
|
|
1622
|
-
|
|
1623
|
-
|
|
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
|
-
}
|
|
2267
|
+
// Re-export types and utilities
|
|
2268
|
+
export { spatial, storage, schema, federation, handshake, crypto, nostrUtils, social, subscriptions, hierarchical };
|
|
1632
2269
|
|
|
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,
|
|
2471
|
-
}
|
|
2472
|
-
);
|
|
2473
|
-
}
|
|
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
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
|
|
2596
|
-
// Re-export ValidationError from validator module
|
|
2597
|
-
export { ValidationError } from './schema/validator.js';
|
|
2598
|
-
|
|
2599
|
-
// Export utilities
|
|
2600
|
-
export { nostrAsync };
|
|
2601
|
-
|
|
2602
|
-
// Export hologram helper functions
|
|
2603
|
-
export {
|
|
2604
|
-
isHologram,
|
|
2605
|
-
isResolvedHologram,
|
|
2606
|
-
getHologramSource,
|
|
2607
|
-
updateHologramOverrides,
|
|
2608
|
-
addActiveHologram,
|
|
2609
|
-
removeActiveHologram,
|
|
2610
|
-
refreshActiveHolograms,
|
|
2611
|
-
deleteHologram,
|
|
2612
|
-
propagateData,
|
|
2613
|
-
createHologram,
|
|
2614
|
-
resolveHologram,
|
|
2615
|
-
cleanupCircularHolograms,
|
|
2616
|
-
cleanupCircularHologramsByIds
|
|
2617
|
-
} from './federation/hologram.js';
|
|
2618
|
-
|
|
2619
|
-
// Export federation registry functions
|
|
2620
|
-
export {
|
|
2621
|
-
getFederationRegistry,
|
|
2622
|
-
addFederatedPartner,
|
|
2623
|
-
removeFederatedPartner,
|
|
2624
|
-
getFederatedAuthors,
|
|
2625
|
-
getCapabilityForAuthor,
|
|
2626
|
-
storeInboundCapability,
|
|
2627
|
-
storeOutboundCapability,
|
|
2628
|
-
cleanupExpiredCapabilities
|
|
2629
|
-
} from './federation/registry.js';
|
|
2630
|
-
|
|
2631
|
-
// Export Nostr discovery protocol functions
|
|
2632
|
-
export {
|
|
2633
|
-
sendFederationRequest,
|
|
2634
|
-
subscribeFederationRequests,
|
|
2635
|
-
acceptFederationRequest,
|
|
2636
|
-
declineFederationRequest,
|
|
2637
|
-
subscribeFederationAcceptances,
|
|
2638
|
-
subscribeFederationDeclines,
|
|
2639
|
-
getPendingFederationRequests
|
|
2640
|
-
} from './federation/discovery.js';
|
|
2641
|
-
|
|
2642
|
-
// Export scope matching utility
|
|
2270
|
+
// Re-export specific utilities used in tests
|
|
2643
2271
|
export { matchScope } from './crypto/secp256k1.js';
|
|
2272
|
+
export { createHologram } from './federation/hologram.js';
|
|
2273
|
+
|
|
2274
|
+
// Export AI factory function
|
|
2275
|
+
export { createAIServices } from './ai/index.js';
|
|
2644
2276
|
|
|
2645
|
-
// Export
|
|
2277
|
+
// Export Contracts module classes for standalone use
|
|
2646
2278
|
export {
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
NLQuery,
|
|
2656
|
-
Classifier,
|
|
2657
|
-
SpatialAnalysis,
|
|
2658
|
-
SmartAggregation,
|
|
2659
|
-
FederationAdvisor,
|
|
2660
|
-
RelationshipDiscovery,
|
|
2661
|
-
TaskBreakdown,
|
|
2662
|
-
H3AI
|
|
2279
|
+
ChainManager,
|
|
2280
|
+
ContractDeployer,
|
|
2281
|
+
HolonContracts,
|
|
2282
|
+
ContractOperations,
|
|
2283
|
+
ContractABIs,
|
|
2284
|
+
EventListener,
|
|
2285
|
+
ContractQueries,
|
|
2286
|
+
SANKEY_EVENTS
|
|
2663
2287
|
};
|
|
2664
2288
|
|
|
2665
|
-
// Export
|
|
2666
|
-
export {
|
|
2289
|
+
// Export network utilities
|
|
2290
|
+
export {
|
|
2291
|
+
NETWORKS,
|
|
2292
|
+
getNetwork,
|
|
2293
|
+
getNetworksByType,
|
|
2294
|
+
listNetworks,
|
|
2295
|
+
isNetworkSupported,
|
|
2296
|
+
getTxUrl,
|
|
2297
|
+
getAddressUrl
|
|
2298
|
+
} from './contracts/networks.js';
|
|
2299
|
+
|
|
2300
|
+
// Export contracts namespace
|
|
2301
|
+
export { networks };
|
|
2667
2302
|
|
|
2668
2303
|
// Default export for backward compatibility
|
|
2669
2304
|
export default HoloSphere;
|