holosphere 2.0.0-alpha8 → 2.0.0-alpha9
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 +446 -0
- package/FEATURES.md +431 -0
- package/LICENSE +29 -166
- package/LICENSE-AGPL.md +180 -0
- package/dist/cdn/holosphere.min.js +55 -0
- package/dist/cdn/holosphere.min.js.map +1 -0
- package/dist/cjs/holosphere.cjs +1 -1
- package/dist/esm/holosphere.js +1 -1
- package/dist/{index-CKffQDmQ.cjs → index-DDGt_V9o.cjs} +2 -2
- package/dist/{index-CKffQDmQ.cjs.map → index-DDGt_V9o.cjs.map} +1 -1
- package/dist/{index-4XHHKe6S.js → index-DJXftyvB.js} +1905 -337
- package/dist/index-DJXftyvB.js.map +1 -0
- package/dist/{index-Dz5kOZMI.cjs → index-DMbdcMtK.cjs} +17 -4
- package/dist/index-DMbdcMtK.cjs.map +1 -0
- package/dist/{index-BjP1TXGz.js → index-DeZ1xz_s.js} +2 -2
- package/dist/{index-BjP1TXGz.js.map → index-DeZ1xz_s.js.map} +1 -1
- package/dist/{indexeddb-storage-lExjjFlV.js → indexeddb-storage-BFt6hMeF.js} +48 -4
- package/dist/indexeddb-storage-BFt6hMeF.js.map +1 -0
- package/dist/{indexeddb-storage-DD7EFBVc.cjs → indexeddb-storage-BK5tv4Sh.cjs} +2 -2
- package/dist/indexeddb-storage-BK5tv4Sh.cjs.map +1 -0
- package/dist/{memory-storage-C68adso2.js → memory-storage-C9HuoL2E.js} +44 -4
- package/dist/memory-storage-C9HuoL2E.js.map +1 -0
- package/dist/{memory-storage-DD_6yyXT.cjs → memory-storage-Dao7jfYG.cjs} +2 -2
- package/dist/memory-storage-Dao7jfYG.cjs.map +1 -0
- package/dist/{secp256k1-DYELiqgx.cjs → secp256k1-BbKzbLtD.cjs} +2 -2
- package/dist/{secp256k1-DYELiqgx.cjs.map → secp256k1-BbKzbLtD.cjs.map} +1 -1
- package/dist/{secp256k1-OM8siPyy.js → secp256k1-CreY7Pcl.js} +2 -2
- package/dist/{secp256k1-OM8siPyy.js.map → secp256k1-CreY7Pcl.js.map} +1 -1
- 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/jsdoc.json +26 -0
- package/package.json +14 -3
- 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/content/social-protocols.js +34 -25
- package/src/contracts/chain-manager.js +68 -40
- package/src/contracts/deployer.js +70 -42
- package/src/contracts/event-listener.js +61 -29
- package/src/contracts/holon-contracts.js +46 -31
- package/src/contracts/index.js +5 -6
- package/src/contracts/networks.js +19 -14
- package/src/contracts/operations.js +58 -41
- package/src/contracts/queries.js +54 -20
- package/src/core/holosphere.js +35 -6
- package/src/crypto/nostr-utils.js +82 -76
- package/src/crypto/secp256k1.js +7 -2
- package/src/federation/handshake.js +7 -7
- package/src/federation/hologram.js +9 -1
- package/src/hierarchical/upcast.js +34 -20
- package/src/index.js +655 -5
- package/src/lib/ai-methods.js +352 -3
- package/src/lib/contract-methods.js +152 -3
- package/src/lib/errors.js +31 -1
- package/src/lib/federation-methods.js +110 -3
- package/src/lib/index.js +9 -5
- package/src/schema/validator.js +22 -3
- package/src/spatial/h3-operations.js +17 -1
- 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 +29 -2
- 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 +7 -2
- package/src/storage/gun-async.js +20 -11
- package/src/storage/gun-auth.js +15 -4
- package/src/storage/gun-federation.js +14 -5
- package/src/storage/gun-references.js +16 -5
- package/src/storage/gun-schema.js +25 -10
- package/src/storage/gun-wrapper.js +99 -36
- package/src/storage/indexeddb-storage.js +65 -4
- package/src/storage/key-storage-simple.js +32 -9
- package/src/storage/key-storage.js +45 -13
- package/src/storage/memory-storage.js +65 -4
- package/src/storage/migration.js +20 -7
- package/src/storage/nostr-async.js +157 -67
- package/src/storage/nostr-client.js +173 -49
- package/src/storage/nostr-wrapper.js +6 -2
- package/src/storage/outbox-queue.js +55 -18
- package/src/storage/persistent-storage.js +56 -13
- package/src/storage/sync-service.js +51 -17
- package/src/storage/unified-storage.js +7 -2
- package/src/subscriptions/manager.js +33 -16
- package/dist/index-4XHHKe6S.js.map +0 -1
- package/dist/index-Dz5kOZMI.cjs.map +0 -1
- package/dist/indexeddb-storage-DD7EFBVc.cjs.map +0 -1
- package/dist/indexeddb-storage-lExjjFlV.js.map +0 -1
- package/dist/memory-storage-C68adso2.js.map +0 -1
- package/dist/memory-storage-DD_6yyXT.cjs.map +0 -1
- /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
|
@@ -0,0 +1,1076 @@
|
|
|
1
|
+
<!DOCTYPE html><html lang="en" style="font-size:16px"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Source: storage/nostr-async.js</title><!--[if lt IE 9]>
|
|
2
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
3
|
+
<![endif]--><script src="scripts/third-party/hljs.js" defer="defer"></script><script src="scripts/third-party/hljs-line-num.js" defer="defer"></script><script src="scripts/third-party/popper.js" defer="defer"></script><script src="scripts/third-party/tippy.js" defer="defer"></script><script src="scripts/third-party/tocbot.min.js"></script><script>var baseURL="/",locationPathname="";baseURL=(locationPathname=document.location.pathname).substr(0,locationPathname.lastIndexOf("/")+1)</script><link rel="stylesheet" href="styles/clean-jsdoc-theme.min.css"><svg aria-hidden="true" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none"><defs><symbol id="copy-icon" viewbox="0 0 488.3 488.3"><g><path d="M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z"/><path d="M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z"/></g></symbol><symbol id="search-icon" viewBox="0 0 512 512"><g><g><path d="M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z"/></g></g><g><g><path d="M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z"/></g></g></symbol><symbol id="font-size-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol id="add-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></symbol><symbol id="minus-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 11h14v2H5z"/></symbol><symbol id="dark-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></symbol><symbol id="light-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></symbol><symbol id="reset-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z"/></symbol><symbol id="down-icon" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></symbol><symbol id="codepen-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16.5 13.202L13 15.535v3.596L19.197 15 16.5 13.202zM14.697 12L12 10.202 9.303 12 12 13.798 14.697 12zM20 10.869L18.303 12 20 13.131V10.87zM19.197 9L13 4.869v3.596l3.5 2.333L19.197 9zM7.5 10.798L11 8.465V4.869L4.803 9 7.5 10.798zM4.803 15L11 19.131v-3.596l-3.5-2.333L4.803 15zM4 13.131L5.697 12 4 10.869v2.262zM2 9a1 1 0 0 1 .445-.832l9-6a1 1 0 0 1 1.11 0l9 6A1 1 0 0 1 22 9v6a1 1 0 0 1-.445.832l-9 6a1 1 0 0 1-1.11 0l-9-6A1 1 0 0 1 2 15V9z"/></symbol><symbol id="close-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></symbol><symbol id="menu-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z"/></symbol></defs></svg></head><body data-theme="dark"><div class="sidebar-container"><div class="sidebar" id="sidebar"><div class="sidebar-items-container"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-modules"><div>Modules</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="module-ai.html">ai</a></div><div class="sidebar-section-children"><a href="module-ai_aggregation.html">ai/aggregation</a></div><div class="sidebar-section-children"><a href="module-ai_breakdown.html">ai/breakdown</a></div><div class="sidebar-section-children"><a href="module-ai_classifier.html">ai/classifier</a></div><div class="sidebar-section-children"><a href="module-ai_council.html">ai/council</a></div><div class="sidebar-section-children"><a href="module-ai_embeddings.html">ai/embeddings</a></div><div class="sidebar-section-children"><a href="module-ai_federation-ai.html">ai/federation-ai</a></div><div class="sidebar-section-children"><a href="module-ai_h3-ai.html">ai/h3-ai</a></div><div class="sidebar-section-children"><a href="module-ai_json-ops.html">ai/json-ops</a></div><div class="sidebar-section-children"><a href="module-ai_llm-service.html">ai/llm-service</a></div><div class="sidebar-section-children"><a href="module-ai_nl-query.html">ai/nl-query</a></div><div class="sidebar-section-children"><a href="module-ai_relationships.html">ai/relationships</a></div><div class="sidebar-section-children"><a href="module-ai_schema-extractor.html">ai/schema-extractor</a></div><div class="sidebar-section-children"><a href="module-ai_spatial.html">ai/spatial</a></div><div class="sidebar-section-children"><a href="module-ai_tts.html">ai/tts</a></div><div class="sidebar-section-children"><a href="module-content_social-protocols.html">content/social-protocols</a></div><div class="sidebar-section-children"><a href="module-core_holosphere.html">core/holosphere</a></div><div class="sidebar-section-children"><a href="module-crypto_nostr-utils.html">crypto/nostr-utils</a></div><div class="sidebar-section-children"><a href="module-crypto_secp256k1.html">crypto/secp256k1</a></div><div class="sidebar-section-children"><a href="module-federation_hologram.html">federation/hologram</a></div><div class="sidebar-section-children"><a href="module-hierarchical_upcast.html">hierarchical/upcast</a></div><div class="sidebar-section-children"><a href="module-holosphere.html">holosphere</a></div><div class="sidebar-section-children"><a href="module-lib_ai-methods.html">lib/ai-methods</a></div><div class="sidebar-section-children"><a href="module-lib_contract-methods.html">lib/contract-methods</a></div><div class="sidebar-section-children"><a href="module-lib_errors.html">lib/errors</a></div><div class="sidebar-section-children"><a href="module-lib_federation-methods.html">lib/federation-methods</a></div><div class="sidebar-section-children"><a href="module-lib_index.html">lib/index</a></div><div class="sidebar-section-children"><a href="module-schema_validator.html">schema/validator</a></div><div class="sidebar-section-children"><a href="module-spatial_h3-operations.html">spatial/h3-operations</a></div><div class="sidebar-section-children"><a href="module-storage_backend-factory.html">storage/backend-factory</a></div><div class="sidebar-section-children"><a href="module-storage_backend-interface.html">storage/backend-interface</a></div><div class="sidebar-section-children"><a href="module-storage_backends_activitypub-backend.html">storage/backends/activitypub-backend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_activitypub_server.html">storage/backends/activitypub/server</a></div><div class="sidebar-section-children"><a href="module-storage_backends_gundb-backend.html">storage/backends/gundb-backend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_nostr-backend.html">storage/backends/nostr-backend</a></div><div class="sidebar-section-children"><a href="module-storage_filesystem-storage.html">storage/filesystem-storage</a></div><div class="sidebar-section-children"><a href="module-storage_filesystem-storage-browser.html">storage/filesystem-storage-browser</a></div><div class="sidebar-section-children"><a href="module-storage_global-tables.html">storage/global-tables</a></div><div class="sidebar-section-children"><a href="module-storage_gun-async.html">storage/gun-async</a></div><div class="sidebar-section-children"><a href="module-storage_gun-auth.html">storage/gun-auth</a></div><div class="sidebar-section-children"><a href="module-storage_gun-federation.html">storage/gun-federation</a></div><div class="sidebar-section-children"><a href="module-storage_gun-references.html">storage/gun-references</a></div><div class="sidebar-section-children"><a href="module-storage_gun-schema.html">storage/gun-schema</a></div><div class="sidebar-section-children"><a href="module-storage_gun-wrapper.html">storage/gun-wrapper</a></div><div class="sidebar-section-children"><a href="module-storage_indexeddb-storage.html">storage/indexeddb-storage</a></div><div class="sidebar-section-children"><a href="module-storage_key-storage.html">storage/key-storage</a></div><div class="sidebar-section-children"><a href="module-storage_key-storage-simple.html">storage/key-storage-simple</a></div><div class="sidebar-section-children"><a href="module-storage_memory-storage.html">storage/memory-storage</a></div><div class="sidebar-section-children"><a href="module-storage_migration.html">storage/migration</a></div><div class="sidebar-section-children"><a href="module-storage_nostr-async.html">storage/nostr-async</a></div><div class="sidebar-section-children"><a href="module-storage_nostr-client.html">storage/nostr-client</a></div><div class="sidebar-section-children"><a href="module-storage_nostr-wrapper.html">storage/nostr-wrapper</a></div><div class="sidebar-section-children"><a href="module-storage_outbox-queue.html">storage/outbox-queue</a></div><div class="sidebar-section-children"><a href="module-storage_persistent-storage.html">storage/persistent-storage</a></div><div class="sidebar-section-children"><a href="module-storage_sync-service.html">storage/sync-service</a></div><div class="sidebar-section-children"><a href="module-storage_unified-storage.html">storage/unified-storage</a></div><div class="sidebar-section-children"><a href="module-subscriptions_manager.html">subscriptions/manager</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="module-ai_aggregation.SmartAggregation.html">SmartAggregation</a></div><div class="sidebar-section-children"><a href="module-ai_aggregation-SmartAggregation.html">SmartAggregation</a></div><div class="sidebar-section-children"><a href="module-ai_breakdown.TaskBreakdown.html">TaskBreakdown</a></div><div class="sidebar-section-children"><a href="module-ai_breakdown-TaskBreakdown.html">TaskBreakdown</a></div><div class="sidebar-section-children"><a href="module-ai_classifier.Classifier.html">Classifier</a></div><div class="sidebar-section-children"><a href="module-ai_classifier-Classifier.html">Classifier</a></div><div class="sidebar-section-children"><a href="module-ai_council.Council.html">Council</a></div><div class="sidebar-section-children"><a href="module-ai_council-Council.html">Council</a></div><div class="sidebar-section-children"><a href="module-ai_embeddings.Embeddings.html">Embeddings</a></div><div class="sidebar-section-children"><a href="module-ai_embeddings-Embeddings.html">Embeddings</a></div><div class="sidebar-section-children"><a href="module-ai_federation-ai.FederationAdvisor.html">FederationAdvisor</a></div><div class="sidebar-section-children"><a href="module-ai_federation-ai-FederationAdvisor.html">FederationAdvisor</a></div><div class="sidebar-section-children"><a href="module-ai_h3-ai.H3AI.html">H3AI</a></div><div class="sidebar-section-children"><a href="module-ai_h3-ai-H3AI.html">H3AI</a></div><div class="sidebar-section-children"><a href="module-ai_json-ops.JSONOps.html">JSONOps</a></div><div class="sidebar-section-children"><a href="module-ai_json-ops-JSONOps.html">JSONOps</a></div><div class="sidebar-section-children"><a href="module-ai_llm-service.LLMService.html">LLMService</a></div><div class="sidebar-section-children"><a href="module-ai_llm-service-LLMService.html">LLMService</a></div><div class="sidebar-section-children"><a href="module-ai_nl-query.NLQuery.html">NLQuery</a></div><div class="sidebar-section-children"><a href="module-ai_nl-query-NLQuery.html">NLQuery</a></div><div class="sidebar-section-children"><a href="module-ai_relationships.RelationshipDiscovery.html">RelationshipDiscovery</a></div><div class="sidebar-section-children"><a href="module-ai_relationships-RelationshipDiscovery.html">RelationshipDiscovery</a></div><div class="sidebar-section-children"><a href="module-ai_schema-extractor.SchemaExtractor.html">SchemaExtractor</a></div><div class="sidebar-section-children"><a href="module-ai_schema-extractor-SchemaExtractor.html">SchemaExtractor</a></div><div class="sidebar-section-children"><a href="module-ai_spatial.SpatialAnalysis.html">SpatialAnalysis</a></div><div class="sidebar-section-children"><a href="module-ai_spatial-SpatialAnalysis.html">SpatialAnalysis</a></div><div class="sidebar-section-children"><a href="module-ai_tts.TTS.html">TTS</a></div><div class="sidebar-section-children"><a href="module-ai_tts-TTS.html">TTS</a></div><div class="sidebar-section-children"><a href="module-core_holosphere.HoloSphere.html">HoloSphere</a></div><div class="sidebar-section-children"><a href="module-core_holosphere-HoloSphere.html">HoloSphere</a></div><div class="sidebar-section-children"><a href="module-holosphere-HoloSphereBase.html">HoloSphereBase</a></div><div class="sidebar-section-children"><a href="module-holosphere-HoloSphereBase.html">HoloSphereBase</a></div><div class="sidebar-section-children"><a href="module-lib_errors.AuthorizationError.html">AuthorizationError</a></div><div class="sidebar-section-children"><a href="module-lib_errors.ValidationError.html">ValidationError</a></div><div class="sidebar-section-children"><a href="module-lib_errors-AuthorizationError.html">AuthorizationError</a></div><div class="sidebar-section-children"><a href="module-lib_errors-ValidationError.html">ValidationError</a></div><div class="sidebar-section-children"><a href="module-schema_validator.ValidationError.html">ValidationError</a></div><div class="sidebar-section-children"><a href="module-schema_validator-ValidationError.html">ValidationError</a></div><div class="sidebar-section-children"><a href="module-storage_backend-factory.BackendFactory.html">BackendFactory</a></div><div class="sidebar-section-children"><a href="module-storage_backend-interface.StorageBackend.html">StorageBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backend-interface-StorageBackend.html">StorageBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_activitypub-backend.ActivityPubBackend.html">ActivityPubBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_activitypub-backend-ActivityPubBackend.html">ActivityPubBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_activitypub_server.ActivityPubServer.html">ActivityPubServer</a></div><div class="sidebar-section-children"><a href="module-storage_backends_activitypub_server-ActivityPubServer.html">ActivityPubServer</a></div><div class="sidebar-section-children"><a href="module-storage_backends_gundb-backend.GunDBBackend.html">GunDBBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_gundb-backend-GunDBBackend.html">GunDBBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_nostr-backend.NostrBackend.html">NostrBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_nostr-backend-NostrBackend.html">NostrBackend</a></div><div class="sidebar-section-children"><a href="module-storage_filesystem-storage-browser.FileSystemStorage.html">FileSystemStorage</a></div><div class="sidebar-section-children"><a href="module-storage_filesystem-storage-browser-FileSystemStorage.html">FileSystemStorage</a></div><div class="sidebar-section-children"><a href="module-storage_filesystem-storage.FileSystemStorage.html">FileSystemStorage</a></div><div class="sidebar-section-children"><a href="module-storage_filesystem-storage-FileSystemStorage.html">FileSystemStorage</a></div><div class="sidebar-section-children"><a href="module-storage_gun-auth.GunAuth.html">GunAuth</a></div><div class="sidebar-section-children"><a href="module-storage_gun-auth-GunAuth.html">GunAuth</a></div><div class="sidebar-section-children"><a href="module-storage_gun-references.GunReferenceHandler.html">GunReferenceHandler</a></div><div class="sidebar-section-children"><a href="module-storage_gun-references-GunReferenceHandler.html">GunReferenceHandler</a></div><div class="sidebar-section-children"><a href="module-storage_gun-schema.GunSchemaValidator.html">GunSchemaValidator</a></div><div class="sidebar-section-children"><a href="module-storage_gun-schema-GunSchemaValidator.html">GunSchemaValidator</a></div><div class="sidebar-section-children"><a href="module-storage_indexeddb-storage.IndexedDBStorage.html">IndexedDBStorage</a></div><div class="sidebar-section-children"><a href="module-storage_indexeddb-storage-IndexedDBStorage.html">IndexedDBStorage</a></div><div class="sidebar-section-children"><a href="module-storage_memory-storage.MemoryStorage.html">MemoryStorage</a></div><div class="sidebar-section-children"><a href="module-storage_memory-storage-MemoryStorage.html">MemoryStorage</a></div><div class="sidebar-section-children"><a href="module-storage_migration.MigrationTool.html">MigrationTool</a></div><div class="sidebar-section-children"><a href="module-storage_migration-MigrationTool.html">MigrationTool</a></div><div class="sidebar-section-children"><a href="module-storage_nostr-client.NostrClient.html">NostrClient</a></div><div class="sidebar-section-children"><a href="module-storage_nostr-client-LRUCache.html">LRUCache</a></div><div class="sidebar-section-children"><a href="module-storage_nostr-client-NostrClient.html">NostrClient</a></div><div class="sidebar-section-children"><a href="module-storage_outbox-queue.OutboxQueue.html">OutboxQueue</a></div><div class="sidebar-section-children"><a href="module-storage_outbox-queue-OutboxQueue.html">OutboxQueue</a></div><div class="sidebar-section-children"><a href="module-storage_persistent-storage-PersistentStorage.html">PersistentStorage</a></div><div class="sidebar-section-children"><a href="module-storage_sync-service.SyncService.html">SyncService</a></div><div class="sidebar-section-children"><a href="module-storage_sync-service-SyncService.html">SyncService</a></div><div class="sidebar-section-children"><a href="module-subscriptions_manager.SubscriptionRegistry.html">SubscriptionRegistry</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#FederationRequestPayload">FederationRequestPayload</a></div><div class="sidebar-section-children"><a href="global.html#FederationResponsePayload">FederationResponsePayload</a></div><div class="sidebar-section-children"><a href="global.html#acceptFederationRequest">acceptFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#acceptFederationRequest">acceptFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#addFederatedPartner">addFederatedPartner</a></div><div class="sidebar-section-children"><a href="global.html#cleanupExpiredCapabilities">cleanupExpiredCapabilities</a></div><div class="sidebar-section-children"><a href="global.html#createFederationRequest">createFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#createFederationResponse">createFederationResponse</a></div><div class="sidebar-section-children"><a href="global.html#declineFederationRequest">declineFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#getCapabilityForAuthor">getCapabilityForAuthor</a></div><div class="sidebar-section-children"><a href="global.html#getFederatedAuthors">getFederatedAuthors</a></div><div class="sidebar-section-children"><a href="global.html#getFederatedAuthorsForScope">getFederatedAuthorsForScope</a></div><div class="sidebar-section-children"><a href="global.html#getFederatedPartner">getFederatedPartner</a></div><div class="sidebar-section-children"><a href="global.html#getFederationRegistry">getFederationRegistry</a></div><div class="sidebar-section-children"><a href="global.html#getPendingFederationRequests">getPendingFederationRequests</a></div><div class="sidebar-section-children"><a href="global.html#initiateFederationHandshake">initiateFederationHandshake</a></div><div class="sidebar-section-children"><a href="global.html#isFederationRequest">isFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#isFederationResponse">isFederationResponse</a></div><div class="sidebar-section-children"><a href="global.html#rejectFederationRequest">rejectFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#removeFederatedPartner">removeFederatedPartner</a></div><div class="sidebar-section-children"><a href="global.html#revokeOutboundCapability">revokeOutboundCapability</a></div><div class="sidebar-section-children"><a href="global.html#saveFederationRegistry">saveFederationRegistry</a></div><div class="sidebar-section-children"><a href="global.html#sendFederationRequest">sendFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#sendFederationRequest">sendFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#sendFederationResponse">sendFederationResponse</a></div><div class="sidebar-section-children"><a href="global.html#storeInboundCapability">storeInboundCapability</a></div><div class="sidebar-section-children"><a href="global.html#storeOutboundCapability">storeOutboundCapability</a></div><div class="sidebar-section-children"><a href="global.html#subscribeFederationAcceptances">subscribeFederationAcceptances</a></div><div class="sidebar-section-children"><a href="global.html#subscribeFederationDeclines">subscribeFederationDeclines</a></div><div class="sidebar-section-children"><a href="global.html#subscribeFederationRequests">subscribeFederationRequests</a></div><div class="sidebar-section-children"><a href="global.html#subscribeToFederationDMs">subscribeToFederationDMs</a></div><div class="sidebar-section-children"><a href="global.html#updateDiscoverySettings">updateDiscoverySettings</a></div></div></div></div></div><div class="navbar-container" id="VuAckcnZhf"><nav class="navbar"><div class="navbar-left-items"></div><div class="navbar-right-items"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#light-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div><nav></nav></nav></div><div class="toc-container"><div class="toc-content"><span class="bold">On this page</span><div id="eed4d2a0bfd64539bb9df78095dec881"></div></div></div><div class="body-wrapper"><div class="main-content"><div class="main-wrapper"><section id="source-page" class="source-page"><header><h1 id="title" class="has-anchor">storage_nostr-async.js</h1></header><article><pre class="prettyprint source lang-js"><code>/**
|
|
4
|
+
* @fileoverview Nostr Async Utilities.
|
|
5
|
+
*
|
|
6
|
+
* Provides Promise-based wrappers and async patterns for Nostr operations.
|
|
7
|
+
* Includes local-first data access, query deduplication, subscription management,
|
|
8
|
+
* and background refresh capabilities for optimal performance.
|
|
9
|
+
*
|
|
10
|
+
* @module storage/nostr-async
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Global subscription manager to prevent duplicate subscriptions.
|
|
15
|
+
* Maps: subscriptionKey -> subscription object
|
|
16
|
+
* @private
|
|
17
|
+
*/
|
|
18
|
+
const globalSubscriptions = new Map();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Single-path subscription manager (for nostrSubscribe).
|
|
22
|
+
* Maps: subscriptionKey -> { subscription, callbacks: [] }
|
|
23
|
+
* @private
|
|
24
|
+
*/
|
|
25
|
+
const singlePathSubscriptions = new Map();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Query deduplication for nostrGet - prevents duplicate relay queries.
|
|
29
|
+
* Maps: queryKey -> { promise, timestamp, callbacks: [] }
|
|
30
|
+
* @private
|
|
31
|
+
*/
|
|
32
|
+
const pendingQueries = new Map();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Time window for query deduplication (2 seconds).
|
|
36
|
+
* @private
|
|
37
|
+
* @constant {number}
|
|
38
|
+
*/
|
|
39
|
+
const QUERY_DEDUP_WINDOW = 2000;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Write data as Nostr event (parameterized replaceable event).
|
|
43
|
+
*
|
|
44
|
+
* @param {Object} client - NostrClient instance
|
|
45
|
+
* @param {string} path - Path identifier (encoded in d-tag)
|
|
46
|
+
* @param {Object} data - Data to store
|
|
47
|
+
* @param {number} [kind=30000] - Event kind (default: 30000 for parameterized replaceable)
|
|
48
|
+
* @returns {Promise<Object>} Published event result with relay responses
|
|
49
|
+
* @example
|
|
50
|
+
* const result = await nostrPut(client, 'myapp/holon123/items/item1', { name: 'Test' });
|
|
51
|
+
* console.log(result.event.id); // Event ID
|
|
52
|
+
*/
|
|
53
|
+
export async function nostrPut(client, path, data, kind = 30000) {
|
|
54
|
+
const dataEvent = {
|
|
55
|
+
kind,
|
|
56
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
57
|
+
tags: [
|
|
58
|
+
['d', path], // d-tag for parameterized replaceable events
|
|
59
|
+
],
|
|
60
|
+
content: JSON.stringify(data),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const result = await client.publish(dataEvent);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Read data from Nostr (query by d-tag).
|
|
69
|
+
*
|
|
70
|
+
* LOCAL-FIRST: Checks persistent storage first, never blocks on network.
|
|
71
|
+
* Uses query deduplication to prevent duplicate relay queries within a time window.
|
|
72
|
+
*
|
|
73
|
+
* @param {Object} client - NostrClient instance
|
|
74
|
+
* @param {string} path - Path identifier
|
|
75
|
+
* @param {number} [kind=30000] - Event kind (default: 30000)
|
|
76
|
+
* @param {Object} [options={}] - Query options
|
|
77
|
+
* @param {string[]} [options.authors] - Array of public keys to query (default: [client.publicKey])
|
|
78
|
+
* @param {boolean} [options.includeAuthor=false] - If true, adds _author field to returned data
|
|
79
|
+
* @param {boolean} [options.skipPersistent=false] - If true, skip persistent storage check
|
|
80
|
+
* @param {number} [options.timeout=30000] - Query timeout in milliseconds
|
|
81
|
+
* @returns {Promise<Object|null>} Data or null if not found
|
|
82
|
+
* @example
|
|
83
|
+
* const data = await nostrGet(client, 'myapp/holon1/items/item1');
|
|
84
|
+
* if (data) {
|
|
85
|
+
* console.log(data.name);
|
|
86
|
+
* }
|
|
87
|
+
*/
|
|
88
|
+
export async function nostrGet(client, path, kind = 30000, options = {}) {
|
|
89
|
+
const timeout = options.timeout !== undefined ? options.timeout : 30000;
|
|
90
|
+
const authors = options.authors || [client.publicKey];
|
|
91
|
+
|
|
92
|
+
// LOCAL-FIRST: Check persistent storage FIRST (never blocks on network)
|
|
93
|
+
if (!options.skipPersistent && client.persistentGet) {
|
|
94
|
+
const persistedEvent = await client.persistentGet(path);
|
|
95
|
+
if (persistedEvent && persistedEvent.content) {
|
|
96
|
+
// Verify author is in requested authors list (persistent storage may have cached events from other authors)
|
|
97
|
+
if (!authors.includes(persistedEvent.pubkey)) {
|
|
98
|
+
// Author mismatch - fall through to relay query
|
|
99
|
+
} else {
|
|
100
|
+
try {
|
|
101
|
+
const data = JSON.parse(persistedEvent.content);
|
|
102
|
+
|
|
103
|
+
// Skip null/undefined or deleted items
|
|
104
|
+
if (!data || data._deleted) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Optionally include author information
|
|
109
|
+
if (options.includeAuthor) {
|
|
110
|
+
data._author = persistedEvent.pubkey;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Trigger background refresh from relays (fire-and-forget)
|
|
114
|
+
if (client.refreshPathInBackground) {
|
|
115
|
+
client.refreshPathInBackground(path, kind, { authors, timeout });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return data;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
// Fall through to relay query if parsing fails
|
|
121
|
+
console.warn('[nostrGet] Failed to parse persisted event:', error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Query deduplication: Check if same query is already pending
|
|
128
|
+
const queryKey = `get:${client.publicKey}:${kind}:${path}:${authors.join(',')}`;
|
|
129
|
+
const pending = pendingQueries.get(queryKey);
|
|
130
|
+
|
|
131
|
+
if (pending && Date.now() - pending.timestamp < QUERY_DEDUP_WINDOW) {
|
|
132
|
+
// Reuse pending query result
|
|
133
|
+
return pending.promise;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Create new query with deduplication
|
|
137
|
+
const queryPromise = _executeNostrGet(client, path, kind, authors, timeout, options);
|
|
138
|
+
|
|
139
|
+
pendingQueries.set(queryKey, {
|
|
140
|
+
promise: queryPromise,
|
|
141
|
+
timestamp: Date.now(),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Clean up after promise resolves
|
|
145
|
+
queryPromise.finally(() => {
|
|
146
|
+
// Remove from pending after a short delay to allow coalescing
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
const current = pendingQueries.get(queryKey);
|
|
149
|
+
if (current && current.promise === queryPromise) {
|
|
150
|
+
pendingQueries.delete(queryKey);
|
|
151
|
+
}
|
|
152
|
+
}, QUERY_DEDUP_WINDOW);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return queryPromise;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Internal function to execute nostrGet query.
|
|
160
|
+
*
|
|
161
|
+
* @private
|
|
162
|
+
* @param {Object} client - NostrClient instance
|
|
163
|
+
* @param {string} path - Path identifier
|
|
164
|
+
* @param {number} kind - Event kind
|
|
165
|
+
* @param {string[]} authors - Array of author public keys
|
|
166
|
+
* @param {number} timeout - Query timeout
|
|
167
|
+
* @param {Object} options - Query options
|
|
168
|
+
* @returns {Promise<Object|null>} Data or null
|
|
169
|
+
*/
|
|
170
|
+
async function _executeNostrGet(client, path, kind, authors, timeout, options) {
|
|
171
|
+
const filter = {
|
|
172
|
+
kinds: [kind],
|
|
173
|
+
authors: authors, // Support multiple authors for cross-holosphere queries
|
|
174
|
+
'#d': [path],
|
|
175
|
+
limit: authors.length, // Increase limit to get events from all authors
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const events = await client.query(filter, { timeout });
|
|
179
|
+
|
|
180
|
+
// Filter by author (relays may not respect authors filter)
|
|
181
|
+
const authoredEvents = events.filter(event => authors.includes(event.pubkey));
|
|
182
|
+
|
|
183
|
+
if (authoredEvents.length === 0) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Get most recent event (across allowed authors)
|
|
188
|
+
const event = authoredEvents.sort((a, b) => b.created_at - a.created_at)[0];
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const data = JSON.parse(event.content);
|
|
192
|
+
// Skip null/undefined or deleted items
|
|
193
|
+
if (!data || data._deleted) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
// Optionally include author information
|
|
197
|
+
if (options.includeAuthor) {
|
|
198
|
+
data._author = event.pubkey;
|
|
199
|
+
}
|
|
200
|
+
return data;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
// Silent - return null for unparseable events
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Query all events under a path prefix.
|
|
209
|
+
*
|
|
210
|
+
* LOCAL-FIRST: Checks persistent storage first, never blocks on network.
|
|
211
|
+
* Uses query deduplication to prevent duplicate relay queries within a time window.
|
|
212
|
+
*
|
|
213
|
+
* @param {Object} client - NostrClient instance
|
|
214
|
+
* @param {string} pathPrefix - Path prefix to match
|
|
215
|
+
* @param {number} [kind=30000] - Event kind (default: 30000)
|
|
216
|
+
* @param {Object} [options={}] - Query options
|
|
217
|
+
* @param {string[]} [options.authors] - Array of public keys to query (default: [client.publicKey])
|
|
218
|
+
* @param {boolean} [options.includeAuthor=false] - If true, adds _author field to returned data
|
|
219
|
+
* @param {boolean} [options.skipPersistent=false] - If true, skip persistent storage check
|
|
220
|
+
* @param {number} [options.timeout=30000] - Query timeout in milliseconds
|
|
221
|
+
* @param {number} [options.limit=1000] - Maximum number of events to retrieve
|
|
222
|
+
* @returns {Promise<Array>} Array of data objects
|
|
223
|
+
* @example
|
|
224
|
+
* const items = await nostrGetAll(client, 'myapp/holon1/items/');
|
|
225
|
+
* console.log(`Found ${items.length} items`);
|
|
226
|
+
*/
|
|
227
|
+
export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}) {
|
|
228
|
+
const timeout = options.timeout !== undefined ? options.timeout : 30000;
|
|
229
|
+
const limit = options.limit || 1000; // Increased limit to get more events
|
|
230
|
+
const authors = options.authors || [client.publicKey];
|
|
231
|
+
|
|
232
|
+
// LOCAL-FIRST: Check persistent storage FIRST (never blocks on network)
|
|
233
|
+
if (!options.skipPersistent && client.persistentGetAll) {
|
|
234
|
+
const persistedEvents = await client.persistentGetAll(pathPrefix);
|
|
235
|
+
if (persistedEvents.length > 0) {
|
|
236
|
+
// Parse content and group by d-tag (keep latest only)
|
|
237
|
+
const byPath = new Map();
|
|
238
|
+
for (const event of persistedEvents) {
|
|
239
|
+
if (!event || !event.tags) continue;
|
|
240
|
+
|
|
241
|
+
// Verify author is in requested authors list (persistent storage may have cached events from other authors)
|
|
242
|
+
if (!authors.includes(event.pubkey)) continue;
|
|
243
|
+
|
|
244
|
+
const dTag = event.tags.find(t => t[0] === 'd');
|
|
245
|
+
if (!dTag || !dTag[1] || !dTag[1].startsWith(pathPrefix)) continue;
|
|
246
|
+
|
|
247
|
+
const path = dTag[1];
|
|
248
|
+
const existing = byPath.get(path);
|
|
249
|
+
|
|
250
|
+
if (!existing || event.created_at > existing.created_at) {
|
|
251
|
+
try {
|
|
252
|
+
const data = JSON.parse(event.content);
|
|
253
|
+
|
|
254
|
+
// Handle null/undefined or deleted items - remove from map if this is newer
|
|
255
|
+
if (!data || data._deleted) {
|
|
256
|
+
byPath.delete(path);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Optionally include author information
|
|
261
|
+
if (options.includeAuthor) {
|
|
262
|
+
data._author = event.pubkey;
|
|
263
|
+
}
|
|
264
|
+
byPath.set(path, { data, created_at: event.created_at });
|
|
265
|
+
} catch (error) {
|
|
266
|
+
// Skip invalid events
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Trigger background refresh from relays (fire-and-forget)
|
|
272
|
+
if (client.refreshPrefixInBackground) {
|
|
273
|
+
client.refreshPrefixInBackground(pathPrefix, kind, { authors, timeout, limit });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Return array of data objects
|
|
277
|
+
return Array.from(byPath.values()).map(item => item.data);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Query deduplication: Check if same query is already pending
|
|
282
|
+
const queryKey = `getAll:${client.publicKey}:${kind}:${pathPrefix}:${authors.join(',')}`;
|
|
283
|
+
const pending = pendingQueries.get(queryKey);
|
|
284
|
+
|
|
285
|
+
if (pending && Date.now() - pending.timestamp < QUERY_DEDUP_WINDOW) {
|
|
286
|
+
// Reuse pending query result
|
|
287
|
+
return pending.promise;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Create new query with deduplication
|
|
291
|
+
const queryPromise = _executeNostrGetAll(client, pathPrefix, kind, authors, timeout, limit, options);
|
|
292
|
+
|
|
293
|
+
pendingQueries.set(queryKey, {
|
|
294
|
+
promise: queryPromise,
|
|
295
|
+
timestamp: Date.now(),
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Clean up after promise resolves
|
|
299
|
+
queryPromise.finally(() => {
|
|
300
|
+
setTimeout(() => {
|
|
301
|
+
const current = pendingQueries.get(queryKey);
|
|
302
|
+
if (current && current.promise === queryPromise) {
|
|
303
|
+
pendingQueries.delete(queryKey);
|
|
304
|
+
}
|
|
305
|
+
}, QUERY_DEDUP_WINDOW);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
return queryPromise;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Internal function to execute nostrGetAll query.
|
|
313
|
+
*
|
|
314
|
+
* @private
|
|
315
|
+
* @param {Object} client - NostrClient instance
|
|
316
|
+
* @param {string} pathPrefix - Path prefix to match
|
|
317
|
+
* @param {number} kind - Event kind
|
|
318
|
+
* @param {string[]} authors - Array of author public keys
|
|
319
|
+
* @param {number} timeout - Query timeout
|
|
320
|
+
* @param {number} limit - Maximum results
|
|
321
|
+
* @param {Object} options - Query options
|
|
322
|
+
* @returns {Promise<Array>} Array of data objects
|
|
323
|
+
*/
|
|
324
|
+
async function _executeNostrGetAll(client, pathPrefix, kind, authors, timeout, limit, options) {
|
|
325
|
+
const filter = {
|
|
326
|
+
kinds: [kind],
|
|
327
|
+
authors: authors,
|
|
328
|
+
limit,
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const events = await client.query(filter, { timeout });
|
|
332
|
+
|
|
333
|
+
// Filter by path prefix AND verify author (relays may not respect authors filter)
|
|
334
|
+
const matching = events.filter(event => {
|
|
335
|
+
const dTag = event.tags.find(t => t[0] === 'd');
|
|
336
|
+
const pathMatches = dTag && dTag[1] && dTag[1].startsWith(pathPrefix);
|
|
337
|
+
const authorAllowed = authors.includes(event.pubkey);
|
|
338
|
+
return pathMatches && authorAllowed;
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Parse content and group by d-tag (keep latest only, across all authors)
|
|
342
|
+
const byPath = new Map();
|
|
343
|
+
for (const event of matching) {
|
|
344
|
+
const dTag = event.tags.find(t => t[0] === 'd')[1];
|
|
345
|
+
const existing = byPath.get(dTag);
|
|
346
|
+
|
|
347
|
+
if (!existing || event.created_at > existing.created_at) {
|
|
348
|
+
try {
|
|
349
|
+
const data = JSON.parse(event.content);
|
|
350
|
+
|
|
351
|
+
// Handle null/undefined or deleted items - remove from map if this is newer
|
|
352
|
+
if (!data || data._deleted) {
|
|
353
|
+
byPath.delete(dTag);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Optionally include author information
|
|
358
|
+
if (options.includeAuthor) {
|
|
359
|
+
data._author = event.pubkey;
|
|
360
|
+
}
|
|
361
|
+
byPath.set(dTag, { data, created_at: event.created_at });
|
|
362
|
+
} catch (error) {
|
|
363
|
+
// Silent - skip unparseable events
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Return array of data objects
|
|
369
|
+
return Array.from(byPath.values()).map(item => item.data);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Query all events under a path prefix (HYBRID MODE - local + relay).
|
|
374
|
+
*
|
|
375
|
+
* Checks local cache first, then merges with relay data.
|
|
376
|
+
*
|
|
377
|
+
* @param {Object} client - NostrClient instance
|
|
378
|
+
* @param {string} pathPrefix - Path prefix to match
|
|
379
|
+
* @param {number} [kind=30000] - Event kind (default: 30000)
|
|
380
|
+
* @param {Object} [options={}] - Query options
|
|
381
|
+
* @param {string[]} [options.authors] - Array of public keys to query (default: [client.publicKey])
|
|
382
|
+
* @param {boolean} [options.includeAuthor=false] - If true, adds _author field to returned data
|
|
383
|
+
* @param {number} [options.timeout=30000] - Query timeout in milliseconds
|
|
384
|
+
* @param {number} [options.limit=1000] - Maximum number of events to retrieve
|
|
385
|
+
* @returns {Promise<Array>} Array of data objects (merged from local + relay)
|
|
386
|
+
*/
|
|
387
|
+
export async function nostrGetAllHybrid(client, pathPrefix, kind = 30000, options = {}) {
|
|
388
|
+
const timeout = options.timeout !== undefined ? options.timeout : 30000;
|
|
389
|
+
const limit = options.limit || 1000;
|
|
390
|
+
const authors = options.authors || [client.publicKey];
|
|
391
|
+
|
|
392
|
+
// Use hybrid query if available
|
|
393
|
+
const queryMethod = client.queryHybrid || client.query;
|
|
394
|
+
|
|
395
|
+
const filter = {
|
|
396
|
+
kinds: [kind],
|
|
397
|
+
authors: authors,
|
|
398
|
+
limit,
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const events = await queryMethod.call(client, filter, { timeout });
|
|
402
|
+
|
|
403
|
+
// Filter by path prefix AND verify author (relays may not respect authors filter)
|
|
404
|
+
const matching = events.filter(event => {
|
|
405
|
+
const dTag = event.tags.find(t => t[0] === 'd');
|
|
406
|
+
const pathMatches = dTag && dTag[1] && dTag[1].startsWith(pathPrefix);
|
|
407
|
+
const authorAllowed = authors.includes(event.pubkey);
|
|
408
|
+
return pathMatches && authorAllowed;
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Parse content and group by d-tag (keep latest only)
|
|
412
|
+
const byPath = new Map();
|
|
413
|
+
for (const event of matching) {
|
|
414
|
+
const dTag = event.tags.find(t => t[0] === 'd')[1];
|
|
415
|
+
const existing = byPath.get(dTag);
|
|
416
|
+
|
|
417
|
+
if (!existing || event.created_at > existing.created_at) {
|
|
418
|
+
try {
|
|
419
|
+
const data = JSON.parse(event.content);
|
|
420
|
+
// Skip null or non-object content
|
|
421
|
+
if (data === null || typeof data !== 'object') {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
// Handle deleted items - remove from map if this is newer
|
|
425
|
+
if (data._deleted) {
|
|
426
|
+
byPath.delete(dTag);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
// Optionally include author information
|
|
430
|
+
if (options.includeAuthor) {
|
|
431
|
+
data._author = event.pubkey;
|
|
432
|
+
}
|
|
433
|
+
byPath.set(dTag, { data, created_at: event.created_at });
|
|
434
|
+
} catch (error) {
|
|
435
|
+
// Silent - skip unparseable events
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Return array of data objects
|
|
441
|
+
return Array.from(byPath.values()).map(item => item.data);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Delete data (publish deletion event - NIP-09).
|
|
446
|
+
*
|
|
447
|
+
* Publishes a tombstone event and a NIP-09 deletion event to mark data as deleted.
|
|
448
|
+
*
|
|
449
|
+
* @param {Object} client - NostrClient instance
|
|
450
|
+
* @param {string} path - Path identifier
|
|
451
|
+
* @param {number} [kind=30000] - Original event kind (default: 30000)
|
|
452
|
+
* @returns {Promise<Object>} Deletion event result with status
|
|
453
|
+
* @example
|
|
454
|
+
* const result = await nostrDelete(client, 'myapp/holon1/items/item1');
|
|
455
|
+
* if (result.reason !== 'not_found') {
|
|
456
|
+
* console.log('Item deleted successfully');
|
|
457
|
+
* }
|
|
458
|
+
*/
|
|
459
|
+
export async function nostrDelete(client, path, kind = 30000) {
|
|
460
|
+
// Read existing data first
|
|
461
|
+
const existing = await nostrGet(client, path, kind);
|
|
462
|
+
|
|
463
|
+
if (!existing) {
|
|
464
|
+
return { reason: 'not_found', results: [] };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Create minimal tombstone - just the deleted flag, no original data
|
|
468
|
+
const tombstone = {
|
|
469
|
+
_deleted: true,
|
|
470
|
+
_deletedAt: Date.now()
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// Publish tombstone as replacement event
|
|
474
|
+
// This ensures reads will see _deleted flag
|
|
475
|
+
const result = await nostrPut(client, path, tombstone, kind);
|
|
476
|
+
|
|
477
|
+
// Also delete from persistent storage to prevent resurrection on restart
|
|
478
|
+
if (client.persistentStorage) {
|
|
479
|
+
try {
|
|
480
|
+
await client.persistentStorage.delete(path);
|
|
481
|
+
} catch (e) {
|
|
482
|
+
// Ignore storage deletion errors
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Clear from memory cache
|
|
487
|
+
if (client.clearCache) {
|
|
488
|
+
client.clearCache(path);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Also publish NIP-09 deletion event for relay compliance
|
|
492
|
+
const coordinate = `${kind}:${client.publicKey}:${path}`;
|
|
493
|
+
const deletionEvent = {
|
|
494
|
+
kind: 5, // NIP-09 deletion event
|
|
495
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
496
|
+
tags: [
|
|
497
|
+
['a', coordinate], // 'a' tag for parameterized replaceable events
|
|
498
|
+
],
|
|
499
|
+
content: '', // Optional deletion reason/note
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// Publish deletion event (best effort, don't block on failure)
|
|
503
|
+
try {
|
|
504
|
+
await client.publish(deletionEvent);
|
|
505
|
+
// Silent success - deletion queued
|
|
506
|
+
} catch (error) {
|
|
507
|
+
// Silent - deletion will be retried
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Delete all data with path prefix (publish deletion events - NIP-09).
|
|
515
|
+
*
|
|
516
|
+
* @param {Object} client - NostrClient instance
|
|
517
|
+
* @param {string} pathPrefix - Path prefix to delete all items under
|
|
518
|
+
* @param {number} [kind=30000] - Original event kind (default: 30000)
|
|
519
|
+
* @returns {Promise<Object>} Deletion results with success count
|
|
520
|
+
* @example
|
|
521
|
+
* const result = await nostrDeleteAll(client, 'myapp/holon1/items/');
|
|
522
|
+
* console.log(`Deleted ${result.count} items`);
|
|
523
|
+
*/
|
|
524
|
+
export async function nostrDeleteAll(client, pathPrefix, kind = 30000) {
|
|
525
|
+
// Query events from relay
|
|
526
|
+
const filter = {
|
|
527
|
+
kinds: [kind],
|
|
528
|
+
authors: [client.publicKey],
|
|
529
|
+
limit: 1000,
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
const relayEvents = await client.query(filter, { timeout: 30000 });
|
|
533
|
+
|
|
534
|
+
// Also get events from persistent storage (may have events not yet synced to relay)
|
|
535
|
+
let persistentEvents = [];
|
|
536
|
+
if (client.persistentStorage) {
|
|
537
|
+
try {
|
|
538
|
+
persistentEvents = await client.persistentStorage.getAll(pathPrefix);
|
|
539
|
+
} catch (e) {
|
|
540
|
+
// Ignore storage read errors
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Merge events from both sources, using d-tag as key
|
|
545
|
+
const eventsByDTag = new Map();
|
|
546
|
+
|
|
547
|
+
// Add relay events
|
|
548
|
+
for (const event of relayEvents) {
|
|
549
|
+
const dTag = event.tags?.find(t => t[0] === 'd');
|
|
550
|
+
if (dTag && dTag[1] && dTag[1].startsWith(pathPrefix)) {
|
|
551
|
+
eventsByDTag.set(dTag[1], event);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Add persistent storage events (these might be newer or only exist locally)
|
|
556
|
+
for (const event of persistentEvents) {
|
|
557
|
+
const dTag = event.tags?.find(t => t[0] === 'd');
|
|
558
|
+
if (dTag && dTag[1] && dTag[1].startsWith(pathPrefix)) {
|
|
559
|
+
const existing = eventsByDTag.get(dTag[1]);
|
|
560
|
+
// Use the persistent event if it's newer or doesn't exist in relay results
|
|
561
|
+
if (!existing || event.created_at > existing.created_at) {
|
|
562
|
+
eventsByDTag.set(dTag[1], event);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const matching = Array.from(eventsByDTag.values());
|
|
568
|
+
|
|
569
|
+
if (matching.length === 0) {
|
|
570
|
+
return { success: true, count: 0, results: [] };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Publish tombstone events for each matching item
|
|
574
|
+
const tombstoneResults = [];
|
|
575
|
+
for (const event of matching) {
|
|
576
|
+
const dTag = event.tags.find(t => t[0] === 'd')[1];
|
|
577
|
+
|
|
578
|
+
try {
|
|
579
|
+
// Create minimal tombstone - just the deleted flag, no original data
|
|
580
|
+
const tombstone = {
|
|
581
|
+
_deleted: true,
|
|
582
|
+
_deletedAt: Date.now()
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
// Publish tombstone replacement
|
|
586
|
+
const result = await nostrPut(client, dTag, tombstone, kind);
|
|
587
|
+
tombstoneResults.push(result);
|
|
588
|
+
|
|
589
|
+
// Also delete from persistent storage to prevent resurrection on restart
|
|
590
|
+
if (client.persistentStorage) {
|
|
591
|
+
try {
|
|
592
|
+
await client.persistentStorage.delete(dTag);
|
|
593
|
+
} catch (e) {
|
|
594
|
+
// Ignore storage deletion errors
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Clear from memory cache
|
|
599
|
+
if (client.clearCache) {
|
|
600
|
+
client.clearCache(dTag);
|
|
601
|
+
}
|
|
602
|
+
} catch (error) {
|
|
603
|
+
console.error(`Failed to delete item ${dTag}:`, error);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Collect all coordinates for NIP-09 deletion event
|
|
608
|
+
const coordinates = matching.map(event => {
|
|
609
|
+
const dTag = event.tags.find(t => t[0] === 'd')[1];
|
|
610
|
+
return `${kind}:${client.publicKey}:${dTag}`;
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// Publish a single kind 5 deletion event with all coordinates
|
|
614
|
+
// This is for relay compliance (best effort)
|
|
615
|
+
try {
|
|
616
|
+
const deletionEvent = {
|
|
617
|
+
kind: 5, // NIP-09 deletion event
|
|
618
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
619
|
+
tags: coordinates.map(coord => ['a', coord]),
|
|
620
|
+
content: '', // Optional deletion reason/note
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
await client.publish(deletionEvent);
|
|
624
|
+
// Silent success - bulk deletion queued
|
|
625
|
+
} catch (error) {
|
|
626
|
+
// Silent - bulk deletion will be retried
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return {
|
|
630
|
+
success: true,
|
|
631
|
+
count: matching.length,
|
|
632
|
+
results: tombstoneResults,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Subscribe to path changes.
|
|
638
|
+
*
|
|
639
|
+
* Uses subscription deduplication - multiple subscribers to same path share one relay subscription.
|
|
640
|
+
*
|
|
641
|
+
* @param {Object} client - NostrClient instance
|
|
642
|
+
* @param {string} path - Path to subscribe to
|
|
643
|
+
* @param {Function} callback - Callback function (data, event) => void
|
|
644
|
+
* @param {Object} [options={}] - Subscription options
|
|
645
|
+
* @param {number} [options.kind=30000] - Event kind to subscribe to
|
|
646
|
+
* @returns {Object} Subscription object with unsubscribe method
|
|
647
|
+
* @example
|
|
648
|
+
* const sub = nostrSubscribe(client, 'myapp/holon1/items/item1', (data, event) => {
|
|
649
|
+
* console.log('Item updated:', data);
|
|
650
|
+
* });
|
|
651
|
+
* // Later: sub.unsubscribe();
|
|
652
|
+
*/
|
|
653
|
+
export function nostrSubscribe(client, path, callback, options = {}) {
|
|
654
|
+
const kind = options.kind || 30000;
|
|
655
|
+
|
|
656
|
+
// Create unique key for this subscription
|
|
657
|
+
const subscriptionKey = `single:${client.publicKey}:${kind}:${path}`;
|
|
658
|
+
|
|
659
|
+
// Check if we already have an active subscription for this path
|
|
660
|
+
const existing = singlePathSubscriptions.get(subscriptionKey);
|
|
661
|
+
if (existing) {
|
|
662
|
+
// Add callback to existing subscription
|
|
663
|
+
existing.callbacks.push(callback);
|
|
664
|
+
|
|
665
|
+
// Return wrapper that removes only this callback on unsubscribe
|
|
666
|
+
return {
|
|
667
|
+
unsubscribe: () => {
|
|
668
|
+
const idx = existing.callbacks.indexOf(callback);
|
|
669
|
+
if (idx > -1) {
|
|
670
|
+
existing.callbacks.splice(idx, 1);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// If no more callbacks, unsubscribe the whole thing
|
|
674
|
+
if (existing.callbacks.length === 0) {
|
|
675
|
+
existing.subscription.unsubscribe();
|
|
676
|
+
singlePathSubscriptions.delete(subscriptionKey);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Create new subscription
|
|
683
|
+
const callbacks = [callback];
|
|
684
|
+
const subscriptionInfo = {
|
|
685
|
+
callbacks,
|
|
686
|
+
subscription: null,
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
// Store before creating subscription to handle race conditions
|
|
690
|
+
singlePathSubscriptions.set(subscriptionKey, subscriptionInfo);
|
|
691
|
+
|
|
692
|
+
const filter = {
|
|
693
|
+
kinds: [kind],
|
|
694
|
+
authors: [client.publicKey], // Filter by our public key
|
|
695
|
+
'#d': [path],
|
|
696
|
+
limit: 10, // Limit results for single item subscription
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
const subscription = client.subscribe(
|
|
700
|
+
filter,
|
|
701
|
+
(event) => {
|
|
702
|
+
// Verify event is from our public key (relay may not respect author filter)
|
|
703
|
+
if (event.pubkey !== client.publicKey) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
const data = JSON.parse(event.content);
|
|
709
|
+
|
|
710
|
+
// Skip null/undefined or deleted items - don't send tombstones to subscribers
|
|
711
|
+
if (!data || data._deleted) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Dispatch to all registered callbacks
|
|
716
|
+
for (const cb of callbacks) {
|
|
717
|
+
try {
|
|
718
|
+
cb(data, event);
|
|
719
|
+
} catch (err) {
|
|
720
|
+
console.error('Subscription callback error:', err);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
} catch (error) {
|
|
724
|
+
console.error('Failed to parse event in subscription:', error);
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
onEOSE: () => {
|
|
729
|
+
// EOSE received
|
|
730
|
+
},
|
|
731
|
+
}
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
subscriptionInfo.subscription = subscription;
|
|
735
|
+
|
|
736
|
+
return {
|
|
737
|
+
unsubscribe: () => {
|
|
738
|
+
const idx = callbacks.indexOf(callback);
|
|
739
|
+
if (idx > -1) {
|
|
740
|
+
callbacks.splice(idx, 1);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// If no more callbacks, unsubscribe the whole thing
|
|
744
|
+
if (callbacks.length === 0) {
|
|
745
|
+
subscription.unsubscribe();
|
|
746
|
+
singlePathSubscriptions.delete(subscriptionKey);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Subscribe to path prefix (multiple paths).
|
|
754
|
+
*
|
|
755
|
+
* Subscribes to data events and uses Page Visibility API to refresh when tab becomes visible.
|
|
756
|
+
* Note: Nostr relays do not broadcast replaceable event updates to active subscriptions.
|
|
757
|
+
*
|
|
758
|
+
* @param {Object} client - NostrClient instance
|
|
759
|
+
* @param {string} pathPrefix - Path prefix to match
|
|
760
|
+
* @param {Function} callback - Callback function (data, path, event) => void
|
|
761
|
+
* @param {Object} [options={}] - Subscription options
|
|
762
|
+
* @param {number} [options.kind=30000] - Event kind to subscribe to
|
|
763
|
+
* @returns {Promise<Object>} Subscription object with unsubscribe method
|
|
764
|
+
* @example
|
|
765
|
+
* const sub = await nostrSubscribeMany(client, 'myapp/holon1/items/', (data, path, event) => {
|
|
766
|
+
* console.log('Item event:', data);
|
|
767
|
+
* });
|
|
768
|
+
* // Later: sub.unsubscribe();
|
|
769
|
+
*/
|
|
770
|
+
export async function nostrSubscribeMany(client, pathPrefix, callback, options = {}) {
|
|
771
|
+
const kind = options.kind || 30000;
|
|
772
|
+
|
|
773
|
+
// Create unique key for this subscription
|
|
774
|
+
const subscriptionKey = `${client.publicKey}:${kind}:${pathPrefix}`;
|
|
775
|
+
|
|
776
|
+
// Check if we already have an active subscription for this path
|
|
777
|
+
const existingSub = globalSubscriptions.get(subscriptionKey);
|
|
778
|
+
if (existingSub) {
|
|
779
|
+
// Register this callback with the existing subscription
|
|
780
|
+
existingSub.callbacks.push(callback);
|
|
781
|
+
|
|
782
|
+
// Return a wrapper that removes only this callback on unsubscribe
|
|
783
|
+
return {
|
|
784
|
+
unsubscribe: () => {
|
|
785
|
+
const idx = existingSub.callbacks.indexOf(callback);
|
|
786
|
+
if (idx > -1) {
|
|
787
|
+
existingSub.callbacks.splice(idx, 1);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// If no more callbacks, unsubscribe the whole thing
|
|
791
|
+
if (existingSub.callbacks.length === 0) {
|
|
792
|
+
existingSub.actualSubscription.unsubscribe();
|
|
793
|
+
globalSubscriptions.delete(subscriptionKey);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const seenEventIds = new Set(); // Track event IDs to prevent duplicate callbacks
|
|
800
|
+
const callbacks = [callback]; // Array of callbacks to notify
|
|
801
|
+
|
|
802
|
+
// Track rejected events for metrics
|
|
803
|
+
let rejectedCount = 0;
|
|
804
|
+
let acceptedCount = 0;
|
|
805
|
+
let lastLogTime = Date.now();
|
|
806
|
+
const LOG_INTERVAL = 10000; // Log summary every 10 seconds
|
|
807
|
+
|
|
808
|
+
// Handler for data events (kind 30000)
|
|
809
|
+
const handleDataEvent = (event) => {
|
|
810
|
+
// Skip duplicates
|
|
811
|
+
if (seenEventIds.has(event.id)) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
seenEventIds.add(event.id);
|
|
815
|
+
|
|
816
|
+
// Check if event is from a different author (relay not respecting filter)
|
|
817
|
+
if (event.pubkey !== client.publicKey) {
|
|
818
|
+
rejectedCount++;
|
|
819
|
+
|
|
820
|
+
// Only log periodically to avoid spam
|
|
821
|
+
const now = Date.now();
|
|
822
|
+
if (now - lastLogTime > LOG_INTERVAL) {
|
|
823
|
+
console.warn('[nostrSubscribeMany] ⚠️ Relay not respecting authors filter!', {
|
|
824
|
+
rejectedCount,
|
|
825
|
+
acceptedCount,
|
|
826
|
+
expected: client.publicKey,
|
|
827
|
+
message: 'Consider using a different relay or implementing private relay'
|
|
828
|
+
});
|
|
829
|
+
lastLogTime = now;
|
|
830
|
+
}
|
|
831
|
+
return; // Skip events from other authors
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Extract d-tag
|
|
835
|
+
const dTagArray = event.tags.find(t => t[0] === 'd');
|
|
836
|
+
const dTag = dTagArray?.[1];
|
|
837
|
+
|
|
838
|
+
// Client-side prefix filter
|
|
839
|
+
if (dTag && dTag.startsWith(pathPrefix)) {
|
|
840
|
+
try {
|
|
841
|
+
const data = JSON.parse(event.content);
|
|
842
|
+
|
|
843
|
+
// Skip null/undefined data or deleted items - don't send tombstones to subscribers
|
|
844
|
+
if (!data || data._deleted) {
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
acceptedCount++;
|
|
849
|
+
// Notify all registered callbacks
|
|
850
|
+
for (const cb of callbacks) {
|
|
851
|
+
cb(data, dTag, event);
|
|
852
|
+
}
|
|
853
|
+
} catch (error) {
|
|
854
|
+
console.error('[nostrSubscribeMany] Failed to parse event:', error);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
// Subscribe to data events (kind 30000) for initial load
|
|
860
|
+
// Use 'since' to only get events from subscription time onward for real-time updates
|
|
861
|
+
const subscriptionStartTime = Math.floor(Date.now() / 1000);
|
|
862
|
+
const dataFilter = {
|
|
863
|
+
kinds: [kind],
|
|
864
|
+
authors: [client.publicKey],
|
|
865
|
+
since: subscriptionStartTime // Only get new events after subscription
|
|
866
|
+
};
|
|
867
|
+
const dataSubscription = await client.subscribe(dataFilter, handleDataEvent);
|
|
868
|
+
|
|
869
|
+
// Use Page Visibility API to refresh when tab becomes visible
|
|
870
|
+
let isDocumentHidden = typeof document !== 'undefined' && document.hidden;
|
|
871
|
+
let lastVisibilityCheck = subscriptionStartTime;
|
|
872
|
+
|
|
873
|
+
const handleVisibilityChange = async () => {
|
|
874
|
+
if (typeof document === 'undefined') return;
|
|
875
|
+
|
|
876
|
+
const wasHidden = isDocumentHidden;
|
|
877
|
+
isDocumentHidden = document.hidden;
|
|
878
|
+
|
|
879
|
+
// Tab became visible - only check for new events since we were hidden
|
|
880
|
+
if (wasHidden && !isDocumentHidden) {
|
|
881
|
+
const now = Math.floor(Date.now() / 1000);
|
|
882
|
+
try {
|
|
883
|
+
const visibilityFilter = {
|
|
884
|
+
...dataFilter,
|
|
885
|
+
since: lastVisibilityCheck, // Only events since last check
|
|
886
|
+
until: now
|
|
887
|
+
};
|
|
888
|
+
const events = await client.query(visibilityFilter, { timeout: 30000 });
|
|
889
|
+
|
|
890
|
+
for (const event of events) {
|
|
891
|
+
handleDataEvent(event);
|
|
892
|
+
}
|
|
893
|
+
lastVisibilityCheck = now;
|
|
894
|
+
} catch (error) {
|
|
895
|
+
// Silently handle visibility refresh errors
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
// Only add listener in browser environment
|
|
901
|
+
if (typeof document !== 'undefined') {
|
|
902
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Store subscription info for reuse
|
|
906
|
+
const actualSubscription = {
|
|
907
|
+
unsubscribe: () => {
|
|
908
|
+
if (dataSubscription && dataSubscription.unsubscribe) {
|
|
909
|
+
dataSubscription.unsubscribe();
|
|
910
|
+
}
|
|
911
|
+
if (typeof document !== 'undefined') {
|
|
912
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
913
|
+
}
|
|
914
|
+
globalSubscriptions.delete(subscriptionKey);
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
const subscriptionInfo = {
|
|
919
|
+
callbacks,
|
|
920
|
+
actualSubscription
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
globalSubscriptions.set(subscriptionKey, subscriptionInfo);
|
|
924
|
+
|
|
925
|
+
return actualSubscription;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Update data (merge with existing).
|
|
930
|
+
*
|
|
931
|
+
* @param {Object} client - NostrClient instance
|
|
932
|
+
* @param {string} path - Path identifier
|
|
933
|
+
* @param {Object} updates - Fields to update
|
|
934
|
+
* @param {number} [kind=30000] - Event kind (default: 30000)
|
|
935
|
+
* @returns {Promise<Object>} Updated event result
|
|
936
|
+
* @throws {Error} If no data found at path
|
|
937
|
+
* @example
|
|
938
|
+
* await nostrUpdate(client, 'myapp/holon1/items/item1', { status: 'completed' });
|
|
939
|
+
*/
|
|
940
|
+
export async function nostrUpdate(client, path, updates, kind = 30000) {
|
|
941
|
+
// Read existing data
|
|
942
|
+
const existing = await nostrGet(client, path, kind);
|
|
943
|
+
|
|
944
|
+
if (!existing) {
|
|
945
|
+
throw new Error(`No data found at path: ${path}`);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Merge updates
|
|
949
|
+
const merged = { ...existing, ...updates };
|
|
950
|
+
|
|
951
|
+
// Publish updated event
|
|
952
|
+
return nostrPut(client, path, merged, kind);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Batch read multiple paths.
|
|
957
|
+
*
|
|
958
|
+
* @param {Object} client - NostrClient instance
|
|
959
|
+
* @param {string[]} paths - Array of paths
|
|
960
|
+
* @param {number} [kind=30000] - Event kind (default: 30000)
|
|
961
|
+
* @returns {Promise<Object>} Object mapping paths to data
|
|
962
|
+
*/
|
|
963
|
+
export async function nostrBatchGet(client, paths, kind = 30000) {
|
|
964
|
+
const filter = {
|
|
965
|
+
kinds: [kind],
|
|
966
|
+
'#d': paths,
|
|
967
|
+
limit: paths.length,
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
const events = await client.query(filter);
|
|
971
|
+
|
|
972
|
+
// Group by d-tag (keep latest only)
|
|
973
|
+
const byPath = new Map();
|
|
974
|
+
for (const event of events) {
|
|
975
|
+
const dTag = event.tags.find(t => t[0] === 'd');
|
|
976
|
+
if (!dTag || !dTag[1]) continue;
|
|
977
|
+
|
|
978
|
+
const path = dTag[1];
|
|
979
|
+
const existing = byPath.get(path);
|
|
980
|
+
|
|
981
|
+
if (!existing || event.created_at > existing.created_at) {
|
|
982
|
+
try {
|
|
983
|
+
const data = JSON.parse(event.content);
|
|
984
|
+
byPath.set(path, data);
|
|
985
|
+
} catch (error) {
|
|
986
|
+
console.error('Failed to parse event content:', error);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// Return object mapping paths to data
|
|
992
|
+
const result = {};
|
|
993
|
+
for (const path of paths) {
|
|
994
|
+
result[path] = byPath.get(path) || null;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
return result;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Batch write multiple paths.
|
|
1002
|
+
*
|
|
1003
|
+
* @param {Object} client - NostrClient instance
|
|
1004
|
+
* @param {Object} pathDataMap - Object mapping paths to data
|
|
1005
|
+
* @param {number} [kind=30000] - Event kind (default: 30000)
|
|
1006
|
+
* @returns {Promise<Array>} Array of publish results
|
|
1007
|
+
*/
|
|
1008
|
+
export async function nostrBatchPut(client, pathDataMap, kind = 30000) {
|
|
1009
|
+
const promises = Object.entries(pathDataMap).map(([path, data]) =>
|
|
1010
|
+
nostrPut(client, path, data, kind)
|
|
1011
|
+
);
|
|
1012
|
+
|
|
1013
|
+
return Promise.all(promises);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Retry operation with exponential backoff.
|
|
1018
|
+
*
|
|
1019
|
+
* @param {Function} operation - Async function to retry
|
|
1020
|
+
* @param {number} [maxRetries=3] - Max retry attempts
|
|
1021
|
+
* @param {number} [baseDelay=100] - Base delay in ms
|
|
1022
|
+
* @returns {Promise<any>} Promise resolving to operation result
|
|
1023
|
+
* @throws {Error} Last error if all retries fail
|
|
1024
|
+
*/
|
|
1025
|
+
export async function nostrRetry(operation, maxRetries = 3, baseDelay = 100) {
|
|
1026
|
+
let lastError;
|
|
1027
|
+
|
|
1028
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1029
|
+
try {
|
|
1030
|
+
return await operation();
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
lastError = error;
|
|
1033
|
+
if (attempt < maxRetries) {
|
|
1034
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
1035
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
throw lastError;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Wait for specific condition on data.
|
|
1045
|
+
*
|
|
1046
|
+
* @param {Object} client - NostrClient instance
|
|
1047
|
+
* @param {string} path - Path to watch
|
|
1048
|
+
* @param {Function} predicate - Condition function (data) => boolean
|
|
1049
|
+
* @param {number} [timeout=5000] - Timeout in ms
|
|
1050
|
+
* @returns {Promise<any>} Promise resolving when condition is met
|
|
1051
|
+
* @throws {Error} If timeout occurs before condition is met
|
|
1052
|
+
*/
|
|
1053
|
+
export async function nostrWaitFor(client, path, predicate, timeout = 5000) {
|
|
1054
|
+
return new Promise((resolve, reject) => {
|
|
1055
|
+
let timeoutId;
|
|
1056
|
+
let subscription;
|
|
1057
|
+
|
|
1058
|
+
const cleanup = () => {
|
|
1059
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1060
|
+
if (subscription) subscription.unsubscribe();
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
subscription = nostrSubscribe(client, path, (data) => {
|
|
1064
|
+
if (predicate(data)) {
|
|
1065
|
+
cleanup();
|
|
1066
|
+
resolve(data);
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
timeoutId = setTimeout(() => {
|
|
1071
|
+
cleanup();
|
|
1072
|
+
reject(new Error('Timeout waiting for condition'));
|
|
1073
|
+
}, timeout);
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
</code></pre></article></section></div></div></div><div class="search-container" id="PkfLWpAbet" style="display:none"><div class="wrapper" id="iCxFxjkHbP"><button class="icon-button search-close-button" id="VjLlGakifb" aria-label="close search"><svg><use xlink:href="#close-icon"></use></svg></button><div class="search-box-c"><svg><use xlink:href="#search-icon"></use></svg> <input type="text" id="vpcKVYIppa" class="search-input" placeholder="Search..." autofocus></div><div class="search-result-c" id="fWwVHRuDuN"><span class="search-result-c-text">Type anything to view search result</span></div></div></div><div class="mobile-menu-icon-container"><button class="icon-button" id="mobile-menu" data-isopen="false" aria-label="menu"><svg><use xlink:href="#menu-icon"></use></svg></button></div><div id="mobile-sidebar" class="mobile-sidebar-container"><div class="mobile-sidebar-wrapper"><div class="mobile-nav-links"></div><div class="mobile-sidebar-items-c"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-modules"><div>Modules</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="module-ai.html">ai</a></div><div class="sidebar-section-children"><a href="module-ai_aggregation.html">ai/aggregation</a></div><div class="sidebar-section-children"><a href="module-ai_breakdown.html">ai/breakdown</a></div><div class="sidebar-section-children"><a href="module-ai_classifier.html">ai/classifier</a></div><div class="sidebar-section-children"><a href="module-ai_council.html">ai/council</a></div><div class="sidebar-section-children"><a href="module-ai_embeddings.html">ai/embeddings</a></div><div class="sidebar-section-children"><a href="module-ai_federation-ai.html">ai/federation-ai</a></div><div class="sidebar-section-children"><a href="module-ai_h3-ai.html">ai/h3-ai</a></div><div class="sidebar-section-children"><a href="module-ai_json-ops.html">ai/json-ops</a></div><div class="sidebar-section-children"><a href="module-ai_llm-service.html">ai/llm-service</a></div><div class="sidebar-section-children"><a href="module-ai_nl-query.html">ai/nl-query</a></div><div class="sidebar-section-children"><a href="module-ai_relationships.html">ai/relationships</a></div><div class="sidebar-section-children"><a href="module-ai_schema-extractor.html">ai/schema-extractor</a></div><div class="sidebar-section-children"><a href="module-ai_spatial.html">ai/spatial</a></div><div class="sidebar-section-children"><a href="module-ai_tts.html">ai/tts</a></div><div class="sidebar-section-children"><a href="module-content_social-protocols.html">content/social-protocols</a></div><div class="sidebar-section-children"><a href="module-core_holosphere.html">core/holosphere</a></div><div class="sidebar-section-children"><a href="module-crypto_nostr-utils.html">crypto/nostr-utils</a></div><div class="sidebar-section-children"><a href="module-crypto_secp256k1.html">crypto/secp256k1</a></div><div class="sidebar-section-children"><a href="module-federation_hologram.html">federation/hologram</a></div><div class="sidebar-section-children"><a href="module-hierarchical_upcast.html">hierarchical/upcast</a></div><div class="sidebar-section-children"><a href="module-holosphere.html">holosphere</a></div><div class="sidebar-section-children"><a href="module-lib_ai-methods.html">lib/ai-methods</a></div><div class="sidebar-section-children"><a href="module-lib_contract-methods.html">lib/contract-methods</a></div><div class="sidebar-section-children"><a href="module-lib_errors.html">lib/errors</a></div><div class="sidebar-section-children"><a href="module-lib_federation-methods.html">lib/federation-methods</a></div><div class="sidebar-section-children"><a href="module-lib_index.html">lib/index</a></div><div class="sidebar-section-children"><a href="module-schema_validator.html">schema/validator</a></div><div class="sidebar-section-children"><a href="module-spatial_h3-operations.html">spatial/h3-operations</a></div><div class="sidebar-section-children"><a href="module-storage_backend-factory.html">storage/backend-factory</a></div><div class="sidebar-section-children"><a href="module-storage_backend-interface.html">storage/backend-interface</a></div><div class="sidebar-section-children"><a href="module-storage_backends_activitypub-backend.html">storage/backends/activitypub-backend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_activitypub_server.html">storage/backends/activitypub/server</a></div><div class="sidebar-section-children"><a href="module-storage_backends_gundb-backend.html">storage/backends/gundb-backend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_nostr-backend.html">storage/backends/nostr-backend</a></div><div class="sidebar-section-children"><a href="module-storage_filesystem-storage.html">storage/filesystem-storage</a></div><div class="sidebar-section-children"><a href="module-storage_filesystem-storage-browser.html">storage/filesystem-storage-browser</a></div><div class="sidebar-section-children"><a href="module-storage_global-tables.html">storage/global-tables</a></div><div class="sidebar-section-children"><a href="module-storage_gun-async.html">storage/gun-async</a></div><div class="sidebar-section-children"><a href="module-storage_gun-auth.html">storage/gun-auth</a></div><div class="sidebar-section-children"><a href="module-storage_gun-federation.html">storage/gun-federation</a></div><div class="sidebar-section-children"><a href="module-storage_gun-references.html">storage/gun-references</a></div><div class="sidebar-section-children"><a href="module-storage_gun-schema.html">storage/gun-schema</a></div><div class="sidebar-section-children"><a href="module-storage_gun-wrapper.html">storage/gun-wrapper</a></div><div class="sidebar-section-children"><a href="module-storage_indexeddb-storage.html">storage/indexeddb-storage</a></div><div class="sidebar-section-children"><a href="module-storage_key-storage.html">storage/key-storage</a></div><div class="sidebar-section-children"><a href="module-storage_key-storage-simple.html">storage/key-storage-simple</a></div><div class="sidebar-section-children"><a href="module-storage_memory-storage.html">storage/memory-storage</a></div><div class="sidebar-section-children"><a href="module-storage_migration.html">storage/migration</a></div><div class="sidebar-section-children"><a href="module-storage_nostr-async.html">storage/nostr-async</a></div><div class="sidebar-section-children"><a href="module-storage_nostr-client.html">storage/nostr-client</a></div><div class="sidebar-section-children"><a href="module-storage_nostr-wrapper.html">storage/nostr-wrapper</a></div><div class="sidebar-section-children"><a href="module-storage_outbox-queue.html">storage/outbox-queue</a></div><div class="sidebar-section-children"><a href="module-storage_persistent-storage.html">storage/persistent-storage</a></div><div class="sidebar-section-children"><a href="module-storage_sync-service.html">storage/sync-service</a></div><div class="sidebar-section-children"><a href="module-storage_unified-storage.html">storage/unified-storage</a></div><div class="sidebar-section-children"><a href="module-subscriptions_manager.html">subscriptions/manager</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="module-ai_aggregation.SmartAggregation.html">SmartAggregation</a></div><div class="sidebar-section-children"><a href="module-ai_aggregation-SmartAggregation.html">SmartAggregation</a></div><div class="sidebar-section-children"><a href="module-ai_breakdown.TaskBreakdown.html">TaskBreakdown</a></div><div class="sidebar-section-children"><a href="module-ai_breakdown-TaskBreakdown.html">TaskBreakdown</a></div><div class="sidebar-section-children"><a href="module-ai_classifier.Classifier.html">Classifier</a></div><div class="sidebar-section-children"><a href="module-ai_classifier-Classifier.html">Classifier</a></div><div class="sidebar-section-children"><a href="module-ai_council.Council.html">Council</a></div><div class="sidebar-section-children"><a href="module-ai_council-Council.html">Council</a></div><div class="sidebar-section-children"><a href="module-ai_embeddings.Embeddings.html">Embeddings</a></div><div class="sidebar-section-children"><a href="module-ai_embeddings-Embeddings.html">Embeddings</a></div><div class="sidebar-section-children"><a href="module-ai_federation-ai.FederationAdvisor.html">FederationAdvisor</a></div><div class="sidebar-section-children"><a href="module-ai_federation-ai-FederationAdvisor.html">FederationAdvisor</a></div><div class="sidebar-section-children"><a href="module-ai_h3-ai.H3AI.html">H3AI</a></div><div class="sidebar-section-children"><a href="module-ai_h3-ai-H3AI.html">H3AI</a></div><div class="sidebar-section-children"><a href="module-ai_json-ops.JSONOps.html">JSONOps</a></div><div class="sidebar-section-children"><a href="module-ai_json-ops-JSONOps.html">JSONOps</a></div><div class="sidebar-section-children"><a href="module-ai_llm-service.LLMService.html">LLMService</a></div><div class="sidebar-section-children"><a href="module-ai_llm-service-LLMService.html">LLMService</a></div><div class="sidebar-section-children"><a href="module-ai_nl-query.NLQuery.html">NLQuery</a></div><div class="sidebar-section-children"><a href="module-ai_nl-query-NLQuery.html">NLQuery</a></div><div class="sidebar-section-children"><a href="module-ai_relationships.RelationshipDiscovery.html">RelationshipDiscovery</a></div><div class="sidebar-section-children"><a href="module-ai_relationships-RelationshipDiscovery.html">RelationshipDiscovery</a></div><div class="sidebar-section-children"><a href="module-ai_schema-extractor.SchemaExtractor.html">SchemaExtractor</a></div><div class="sidebar-section-children"><a href="module-ai_schema-extractor-SchemaExtractor.html">SchemaExtractor</a></div><div class="sidebar-section-children"><a href="module-ai_spatial.SpatialAnalysis.html">SpatialAnalysis</a></div><div class="sidebar-section-children"><a href="module-ai_spatial-SpatialAnalysis.html">SpatialAnalysis</a></div><div class="sidebar-section-children"><a href="module-ai_tts.TTS.html">TTS</a></div><div class="sidebar-section-children"><a href="module-ai_tts-TTS.html">TTS</a></div><div class="sidebar-section-children"><a href="module-core_holosphere.HoloSphere.html">HoloSphere</a></div><div class="sidebar-section-children"><a href="module-core_holosphere-HoloSphere.html">HoloSphere</a></div><div class="sidebar-section-children"><a href="module-holosphere-HoloSphereBase.html">HoloSphereBase</a></div><div class="sidebar-section-children"><a href="module-holosphere-HoloSphereBase.html">HoloSphereBase</a></div><div class="sidebar-section-children"><a href="module-lib_errors.AuthorizationError.html">AuthorizationError</a></div><div class="sidebar-section-children"><a href="module-lib_errors.ValidationError.html">ValidationError</a></div><div class="sidebar-section-children"><a href="module-lib_errors-AuthorizationError.html">AuthorizationError</a></div><div class="sidebar-section-children"><a href="module-lib_errors-ValidationError.html">ValidationError</a></div><div class="sidebar-section-children"><a href="module-schema_validator.ValidationError.html">ValidationError</a></div><div class="sidebar-section-children"><a href="module-schema_validator-ValidationError.html">ValidationError</a></div><div class="sidebar-section-children"><a href="module-storage_backend-factory.BackendFactory.html">BackendFactory</a></div><div class="sidebar-section-children"><a href="module-storage_backend-interface.StorageBackend.html">StorageBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backend-interface-StorageBackend.html">StorageBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_activitypub-backend.ActivityPubBackend.html">ActivityPubBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_activitypub-backend-ActivityPubBackend.html">ActivityPubBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_activitypub_server.ActivityPubServer.html">ActivityPubServer</a></div><div class="sidebar-section-children"><a href="module-storage_backends_activitypub_server-ActivityPubServer.html">ActivityPubServer</a></div><div class="sidebar-section-children"><a href="module-storage_backends_gundb-backend.GunDBBackend.html">GunDBBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_gundb-backend-GunDBBackend.html">GunDBBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_nostr-backend.NostrBackend.html">NostrBackend</a></div><div class="sidebar-section-children"><a href="module-storage_backends_nostr-backend-NostrBackend.html">NostrBackend</a></div><div class="sidebar-section-children"><a href="module-storage_filesystem-storage-browser.FileSystemStorage.html">FileSystemStorage</a></div><div class="sidebar-section-children"><a href="module-storage_filesystem-storage-browser-FileSystemStorage.html">FileSystemStorage</a></div><div class="sidebar-section-children"><a href="module-storage_filesystem-storage.FileSystemStorage.html">FileSystemStorage</a></div><div class="sidebar-section-children"><a href="module-storage_filesystem-storage-FileSystemStorage.html">FileSystemStorage</a></div><div class="sidebar-section-children"><a href="module-storage_gun-auth.GunAuth.html">GunAuth</a></div><div class="sidebar-section-children"><a href="module-storage_gun-auth-GunAuth.html">GunAuth</a></div><div class="sidebar-section-children"><a href="module-storage_gun-references.GunReferenceHandler.html">GunReferenceHandler</a></div><div class="sidebar-section-children"><a href="module-storage_gun-references-GunReferenceHandler.html">GunReferenceHandler</a></div><div class="sidebar-section-children"><a href="module-storage_gun-schema.GunSchemaValidator.html">GunSchemaValidator</a></div><div class="sidebar-section-children"><a href="module-storage_gun-schema-GunSchemaValidator.html">GunSchemaValidator</a></div><div class="sidebar-section-children"><a href="module-storage_indexeddb-storage.IndexedDBStorage.html">IndexedDBStorage</a></div><div class="sidebar-section-children"><a href="module-storage_indexeddb-storage-IndexedDBStorage.html">IndexedDBStorage</a></div><div class="sidebar-section-children"><a href="module-storage_memory-storage.MemoryStorage.html">MemoryStorage</a></div><div class="sidebar-section-children"><a href="module-storage_memory-storage-MemoryStorage.html">MemoryStorage</a></div><div class="sidebar-section-children"><a href="module-storage_migration.MigrationTool.html">MigrationTool</a></div><div class="sidebar-section-children"><a href="module-storage_migration-MigrationTool.html">MigrationTool</a></div><div class="sidebar-section-children"><a href="module-storage_nostr-client.NostrClient.html">NostrClient</a></div><div class="sidebar-section-children"><a href="module-storage_nostr-client-LRUCache.html">LRUCache</a></div><div class="sidebar-section-children"><a href="module-storage_nostr-client-NostrClient.html">NostrClient</a></div><div class="sidebar-section-children"><a href="module-storage_outbox-queue.OutboxQueue.html">OutboxQueue</a></div><div class="sidebar-section-children"><a href="module-storage_outbox-queue-OutboxQueue.html">OutboxQueue</a></div><div class="sidebar-section-children"><a href="module-storage_persistent-storage-PersistentStorage.html">PersistentStorage</a></div><div class="sidebar-section-children"><a href="module-storage_sync-service.SyncService.html">SyncService</a></div><div class="sidebar-section-children"><a href="module-storage_sync-service-SyncService.html">SyncService</a></div><div class="sidebar-section-children"><a href="module-subscriptions_manager.SubscriptionRegistry.html">SubscriptionRegistry</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-global"><div>Global</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="global.html#FederationRequestPayload">FederationRequestPayload</a></div><div class="sidebar-section-children"><a href="global.html#FederationResponsePayload">FederationResponsePayload</a></div><div class="sidebar-section-children"><a href="global.html#acceptFederationRequest">acceptFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#acceptFederationRequest">acceptFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#addFederatedPartner">addFederatedPartner</a></div><div class="sidebar-section-children"><a href="global.html#cleanupExpiredCapabilities">cleanupExpiredCapabilities</a></div><div class="sidebar-section-children"><a href="global.html#createFederationRequest">createFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#createFederationResponse">createFederationResponse</a></div><div class="sidebar-section-children"><a href="global.html#declineFederationRequest">declineFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#getCapabilityForAuthor">getCapabilityForAuthor</a></div><div class="sidebar-section-children"><a href="global.html#getFederatedAuthors">getFederatedAuthors</a></div><div class="sidebar-section-children"><a href="global.html#getFederatedAuthorsForScope">getFederatedAuthorsForScope</a></div><div class="sidebar-section-children"><a href="global.html#getFederatedPartner">getFederatedPartner</a></div><div class="sidebar-section-children"><a href="global.html#getFederationRegistry">getFederationRegistry</a></div><div class="sidebar-section-children"><a href="global.html#getPendingFederationRequests">getPendingFederationRequests</a></div><div class="sidebar-section-children"><a href="global.html#initiateFederationHandshake">initiateFederationHandshake</a></div><div class="sidebar-section-children"><a href="global.html#isFederationRequest">isFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#isFederationResponse">isFederationResponse</a></div><div class="sidebar-section-children"><a href="global.html#rejectFederationRequest">rejectFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#removeFederatedPartner">removeFederatedPartner</a></div><div class="sidebar-section-children"><a href="global.html#revokeOutboundCapability">revokeOutboundCapability</a></div><div class="sidebar-section-children"><a href="global.html#saveFederationRegistry">saveFederationRegistry</a></div><div class="sidebar-section-children"><a href="global.html#sendFederationRequest">sendFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#sendFederationRequest">sendFederationRequest</a></div><div class="sidebar-section-children"><a href="global.html#sendFederationResponse">sendFederationResponse</a></div><div class="sidebar-section-children"><a href="global.html#storeInboundCapability">storeInboundCapability</a></div><div class="sidebar-section-children"><a href="global.html#storeOutboundCapability">storeOutboundCapability</a></div><div class="sidebar-section-children"><a href="global.html#subscribeFederationAcceptances">subscribeFederationAcceptances</a></div><div class="sidebar-section-children"><a href="global.html#subscribeFederationDeclines">subscribeFederationDeclines</a></div><div class="sidebar-section-children"><a href="global.html#subscribeFederationRequests">subscribeFederationRequests</a></div><div class="sidebar-section-children"><a href="global.html#subscribeToFederationDMs">subscribeToFederationDMs</a></div><div class="sidebar-section-children"><a href="global.html#updateDiscoverySettings">updateDiscoverySettings</a></div></div></div><div class="mobile-navbar-actions"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#light-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div></div></div><script type="text/javascript" src="scripts/core.min.js"></script><script src="scripts/search.min.js" defer="defer"></script><script src="scripts/third-party/fuse.js" defer="defer"></script><script type="text/javascript">var tocbotInstance=tocbot.init({tocSelector:"#eed4d2a0bfd64539bb9df78095dec881",contentSelector:".main-content",headingSelector:"h1, h2, h3",hasInnerContainers:!0,scrollContainer:".main-content",headingsOffset:130,onClick:bringLinkToView})</script></body></html>
|