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