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.
Files changed (321) hide show
  1. package/CHANGELOG.md +446 -0
  2. package/FEATURES.md +431 -0
  3. package/LICENSE +29 -166
  4. package/LICENSE-AGPL.md +180 -0
  5. package/dist/cdn/holosphere.min.js +55 -0
  6. package/dist/cdn/holosphere.min.js.map +1 -0
  7. package/dist/cjs/holosphere.cjs +1 -1
  8. package/dist/esm/holosphere.js +1 -1
  9. package/dist/{index-CKffQDmQ.cjs → index-DDGt_V9o.cjs} +2 -2
  10. package/dist/{index-CKffQDmQ.cjs.map → index-DDGt_V9o.cjs.map} +1 -1
  11. package/dist/{index-4XHHKe6S.js → index-DJXftyvB.js} +1905 -337
  12. package/dist/index-DJXftyvB.js.map +1 -0
  13. package/dist/{index-Dz5kOZMI.cjs → index-DMbdcMtK.cjs} +17 -4
  14. package/dist/index-DMbdcMtK.cjs.map +1 -0
  15. package/dist/{index-BjP1TXGz.js → index-DeZ1xz_s.js} +2 -2
  16. package/dist/{index-BjP1TXGz.js.map → index-DeZ1xz_s.js.map} +1 -1
  17. package/dist/{indexeddb-storage-lExjjFlV.js → indexeddb-storage-BFt6hMeF.js} +48 -4
  18. package/dist/indexeddb-storage-BFt6hMeF.js.map +1 -0
  19. package/dist/{indexeddb-storage-DD7EFBVc.cjs → indexeddb-storage-BK5tv4Sh.cjs} +2 -2
  20. package/dist/indexeddb-storage-BK5tv4Sh.cjs.map +1 -0
  21. package/dist/{memory-storage-C68adso2.js → memory-storage-C9HuoL2E.js} +44 -4
  22. package/dist/memory-storage-C9HuoL2E.js.map +1 -0
  23. package/dist/{memory-storage-DD_6yyXT.cjs → memory-storage-Dao7jfYG.cjs} +2 -2
  24. package/dist/memory-storage-Dao7jfYG.cjs.map +1 -0
  25. package/dist/{secp256k1-DYELiqgx.cjs → secp256k1-BbKzbLtD.cjs} +2 -2
  26. package/dist/{secp256k1-DYELiqgx.cjs.map → secp256k1-BbKzbLtD.cjs.map} +1 -1
  27. package/dist/{secp256k1-OM8siPyy.js → secp256k1-CreY7Pcl.js} +2 -2
  28. package/dist/{secp256k1-OM8siPyy.js.map → secp256k1-CreY7Pcl.js.map} +1 -1
  29. package/docs/api/ai_aggregation.js.html +333 -0
  30. package/docs/api/ai_breakdown.js.html +524 -0
  31. package/docs/api/ai_classifier.js.html +231 -0
  32. package/docs/api/ai_council.js.html +246 -0
  33. package/docs/api/ai_embeddings.js.html +304 -0
  34. package/docs/api/ai_federation-ai.js.html +338 -0
  35. package/docs/api/ai_h3-ai.js.html +970 -0
  36. package/docs/api/ai_index.js.html +124 -0
  37. package/docs/api/ai_json-ops.js.html +241 -0
  38. package/docs/api/ai_llm-service.js.html +239 -0
  39. package/docs/api/ai_nl-query.js.html +236 -0
  40. package/docs/api/ai_relationships.js.html +367 -0
  41. package/docs/api/ai_schema-extractor.js.html +235 -0
  42. package/docs/api/ai_spatial.js.html +307 -0
  43. package/docs/api/ai_tts.js.html +214 -0
  44. package/docs/api/content_social-protocols.js.html +180 -0
  45. package/docs/api/core_holosphere.js.html +757 -0
  46. package/docs/api/crypto_nostr-utils.js.html +306 -0
  47. package/docs/api/crypto_secp256k1.js.html +267 -0
  48. package/docs/api/data/search.json +1 -0
  49. package/docs/api/federation_discovery.js.html +337 -0
  50. package/docs/api/federation_handshake.js.html +478 -0
  51. package/docs/api/federation_hologram.js.html +1053 -0
  52. package/docs/api/federation_registry.js.html +389 -0
  53. package/docs/api/fonts/Inconsolata-Regular.ttf +0 -0
  54. package/docs/api/fonts/OpenSans-Regular.ttf +0 -0
  55. package/docs/api/fonts/WorkSans-Bold.ttf +0 -0
  56. package/docs/api/global.html +3 -0
  57. package/docs/api/hierarchical_upcast.js.html +128 -0
  58. package/docs/api/index.html +265 -0
  59. package/docs/api/index.js.html +1868 -0
  60. package/docs/api/lib_ai-methods.js.html +660 -0
  61. package/docs/api/lib_contract-methods.js.html +445 -0
  62. package/docs/api/lib_errors.js.html +56 -0
  63. package/docs/api/lib_federation-methods.js.html +348 -0
  64. package/docs/api/lib_index.js.html +33 -0
  65. package/docs/api/module-ai.html +5 -0
  66. package/docs/api/module-ai_aggregation-SmartAggregation.html +6 -0
  67. package/docs/api/module-ai_aggregation.SmartAggregation.html +3 -0
  68. package/docs/api/module-ai_aggregation.html +3 -0
  69. package/docs/api/module-ai_breakdown-TaskBreakdown.html +5 -0
  70. package/docs/api/module-ai_breakdown.TaskBreakdown.html +3 -0
  71. package/docs/api/module-ai_breakdown.html +3 -0
  72. package/docs/api/module-ai_classifier-Classifier.html +6 -0
  73. package/docs/api/module-ai_classifier.Classifier.html +3 -0
  74. package/docs/api/module-ai_classifier.html +3 -0
  75. package/docs/api/module-ai_council-Council.html +6 -0
  76. package/docs/api/module-ai_council.Council.html +3 -0
  77. package/docs/api/module-ai_council.html +3 -0
  78. package/docs/api/module-ai_embeddings-Embeddings.html +5 -0
  79. package/docs/api/module-ai_embeddings.Embeddings.html +3 -0
  80. package/docs/api/module-ai_embeddings.html +3 -0
  81. package/docs/api/module-ai_federation-ai-FederationAdvisor.html +6 -0
  82. package/docs/api/module-ai_federation-ai.FederationAdvisor.html +3 -0
  83. package/docs/api/module-ai_federation-ai.html +3 -0
  84. package/docs/api/module-ai_h3-ai-H3AI.html +6 -0
  85. package/docs/api/module-ai_h3-ai.H3AI.html +3 -0
  86. package/docs/api/module-ai_h3-ai.html +3 -0
  87. package/docs/api/module-ai_json-ops-JSONOps.html +5 -0
  88. package/docs/api/module-ai_json-ops.JSONOps.html +3 -0
  89. package/docs/api/module-ai_json-ops.html +3 -0
  90. package/docs/api/module-ai_llm-service-LLMService.html +5 -0
  91. package/docs/api/module-ai_llm-service.LLMService.html +3 -0
  92. package/docs/api/module-ai_llm-service.html +3 -0
  93. package/docs/api/module-ai_nl-query-NLQuery.html +5 -0
  94. package/docs/api/module-ai_nl-query.NLQuery.html +3 -0
  95. package/docs/api/module-ai_nl-query.html +3 -0
  96. package/docs/api/module-ai_relationships-RelationshipDiscovery.html +6 -0
  97. package/docs/api/module-ai_relationships.RelationshipDiscovery.html +3 -0
  98. package/docs/api/module-ai_relationships.html +3 -0
  99. package/docs/api/module-ai_schema-extractor-SchemaExtractor.html +5 -0
  100. package/docs/api/module-ai_schema-extractor.SchemaExtractor.html +3 -0
  101. package/docs/api/module-ai_schema-extractor.html +3 -0
  102. package/docs/api/module-ai_spatial-SpatialAnalysis.html +6 -0
  103. package/docs/api/module-ai_spatial.SpatialAnalysis.html +3 -0
  104. package/docs/api/module-ai_spatial.html +3 -0
  105. package/docs/api/module-ai_tts-TTS.html +5 -0
  106. package/docs/api/module-ai_tts.TTS.html +3 -0
  107. package/docs/api/module-ai_tts.html +3 -0
  108. package/docs/api/module-content_social-protocols.html +3 -0
  109. package/docs/api/module-core_holosphere-HoloSphere.html +6 -0
  110. package/docs/api/module-core_holosphere.HoloSphere.html +3 -0
  111. package/docs/api/module-core_holosphere.html +3 -0
  112. package/docs/api/module-crypto_nostr-utils.html +3 -0
  113. package/docs/api/module-crypto_secp256k1.html +3 -0
  114. package/docs/api/module-federation_hologram.html +3 -0
  115. package/docs/api/module-hierarchical_upcast.html +3 -0
  116. package/docs/api/module-holosphere-HoloSphereBase.html +3 -0
  117. package/docs/api/module-holosphere.html +3 -0
  118. package/docs/api/module-lib_ai-methods.html +3 -0
  119. package/docs/api/module-lib_contract-methods.html +3 -0
  120. package/docs/api/module-lib_errors-AuthorizationError.html +3 -0
  121. package/docs/api/module-lib_errors-ValidationError.html +3 -0
  122. package/docs/api/module-lib_errors.AuthorizationError.html +3 -0
  123. package/docs/api/module-lib_errors.ValidationError.html +3 -0
  124. package/docs/api/module-lib_errors.html +3 -0
  125. package/docs/api/module-lib_federation-methods.html +3 -0
  126. package/docs/api/module-lib_index.html +3 -0
  127. package/docs/api/module-schema_validator-ValidationError.html +3 -0
  128. package/docs/api/module-schema_validator.ValidationError.html +3 -0
  129. package/docs/api/module-schema_validator.html +3 -0
  130. package/docs/api/module-spatial_h3-operations.html +4 -0
  131. package/docs/api/module-storage_backend-factory.BackendFactory.html +3 -0
  132. package/docs/api/module-storage_backend-factory.html +3 -0
  133. package/docs/api/module-storage_backend-interface-StorageBackend.html +3 -0
  134. package/docs/api/module-storage_backend-interface.StorageBackend.html +3 -0
  135. package/docs/api/module-storage_backend-interface.html +3 -0
  136. package/docs/api/module-storage_backends_activitypub-backend-ActivityPubBackend.html +7 -0
  137. package/docs/api/module-storage_backends_activitypub-backend.ActivityPubBackend.html +3 -0
  138. package/docs/api/module-storage_backends_activitypub-backend.html +3 -0
  139. package/docs/api/module-storage_backends_activitypub_server-ActivityPubServer.html +8 -0
  140. package/docs/api/module-storage_backends_activitypub_server.ActivityPubServer.html +3 -0
  141. package/docs/api/module-storage_backends_activitypub_server.html +3 -0
  142. package/docs/api/module-storage_backends_gundb-backend-GunDBBackend.html +7 -0
  143. package/docs/api/module-storage_backends_gundb-backend.GunDBBackend.html +3 -0
  144. package/docs/api/module-storage_backends_gundb-backend.html +3 -0
  145. package/docs/api/module-storage_backends_nostr-backend-NostrBackend.html +8 -0
  146. package/docs/api/module-storage_backends_nostr-backend.NostrBackend.html +3 -0
  147. package/docs/api/module-storage_backends_nostr-backend.html +3 -0
  148. package/docs/api/module-storage_filesystem-storage-FileSystemStorage.html +5 -0
  149. package/docs/api/module-storage_filesystem-storage-browser-FileSystemStorage.html +3 -0
  150. package/docs/api/module-storage_filesystem-storage-browser.FileSystemStorage.html +3 -0
  151. package/docs/api/module-storage_filesystem-storage-browser.html +3 -0
  152. package/docs/api/module-storage_filesystem-storage.FileSystemStorage.html +3 -0
  153. package/docs/api/module-storage_filesystem-storage.html +3 -0
  154. package/docs/api/module-storage_global-tables.html +3 -0
  155. package/docs/api/module-storage_gun-async.html +3 -0
  156. package/docs/api/module-storage_gun-auth-GunAuth.html +5 -0
  157. package/docs/api/module-storage_gun-auth.GunAuth.html +3 -0
  158. package/docs/api/module-storage_gun-auth.html +3 -0
  159. package/docs/api/module-storage_gun-federation.html +3 -0
  160. package/docs/api/module-storage_gun-references-GunReferenceHandler.html +5 -0
  161. package/docs/api/module-storage_gun-references.GunReferenceHandler.html +3 -0
  162. package/docs/api/module-storage_gun-references.html +3 -0
  163. package/docs/api/module-storage_gun-schema-GunSchemaValidator.html +5 -0
  164. package/docs/api/module-storage_gun-schema.GunSchemaValidator.html +3 -0
  165. package/docs/api/module-storage_gun-schema.html +3 -0
  166. package/docs/api/module-storage_gun-wrapper.html +11 -0
  167. package/docs/api/module-storage_indexeddb-storage-IndexedDBStorage.html +5 -0
  168. package/docs/api/module-storage_indexeddb-storage.IndexedDBStorage.html +3 -0
  169. package/docs/api/module-storage_indexeddb-storage.html +3 -0
  170. package/docs/api/module-storage_key-storage-simple.html +3 -0
  171. package/docs/api/module-storage_key-storage.html +4 -0
  172. package/docs/api/module-storage_memory-storage-MemoryStorage.html +5 -0
  173. package/docs/api/module-storage_memory-storage.MemoryStorage.html +3 -0
  174. package/docs/api/module-storage_memory-storage.html +3 -0
  175. package/docs/api/module-storage_migration-MigrationTool.html +6 -0
  176. package/docs/api/module-storage_migration.MigrationTool.html +3 -0
  177. package/docs/api/module-storage_migration.html +3 -0
  178. package/docs/api/module-storage_nostr-async.html +18 -0
  179. package/docs/api/module-storage_nostr-client-LRUCache.html +3 -0
  180. package/docs/api/module-storage_nostr-client-NostrClient.html +7 -0
  181. package/docs/api/module-storage_nostr-client.NostrClient.html +15 -0
  182. package/docs/api/module-storage_nostr-client.html +6 -0
  183. package/docs/api/module-storage_nostr-wrapper.html +3 -0
  184. package/docs/api/module-storage_outbox-queue-OutboxQueue.html +4 -0
  185. package/docs/api/module-storage_outbox-queue.OutboxQueue.html +3 -0
  186. package/docs/api/module-storage_outbox-queue.html +3 -0
  187. package/docs/api/module-storage_persistent-storage-PersistentStorage.html +3 -0
  188. package/docs/api/module-storage_persistent-storage.html +4 -0
  189. package/docs/api/module-storage_sync-service-SyncService.html +5 -0
  190. package/docs/api/module-storage_sync-service.SyncService.html +3 -0
  191. package/docs/api/module-storage_sync-service.html +3 -0
  192. package/docs/api/module-storage_unified-storage.html +3 -0
  193. package/docs/api/module-subscriptions_manager.SubscriptionRegistry.html +3 -0
  194. package/docs/api/module-subscriptions_manager.html +3 -0
  195. package/docs/api/schema_validator.js.html +113 -0
  196. package/docs/api/scripts/core.js +726 -0
  197. package/docs/api/scripts/core.min.js +23 -0
  198. package/docs/api/scripts/resize.js +90 -0
  199. package/docs/api/scripts/search.js +265 -0
  200. package/docs/api/scripts/search.min.js +6 -0
  201. package/docs/api/scripts/third-party/Apache-License-2.0.txt +202 -0
  202. package/docs/api/scripts/third-party/fuse.js +9 -0
  203. package/docs/api/scripts/third-party/hljs-line-num-original.js +369 -0
  204. package/docs/api/scripts/third-party/hljs-line-num.js +1 -0
  205. package/docs/api/scripts/third-party/hljs-original.js +5171 -0
  206. package/docs/api/scripts/third-party/hljs.js +1 -0
  207. package/docs/api/scripts/third-party/popper.js +5 -0
  208. package/docs/api/scripts/third-party/tippy.js +1 -0
  209. package/docs/api/scripts/third-party/tocbot.js +672 -0
  210. package/docs/api/scripts/third-party/tocbot.min.js +1 -0
  211. package/docs/api/spatial_h3-operations.js.html +129 -0
  212. package/docs/api/storage_backend-factory.js.html +133 -0
  213. package/docs/api/storage_backend-interface.js.html +164 -0
  214. package/docs/api/storage_backends_activitypub-backend.js.html +298 -0
  215. package/docs/api/storage_backends_activitypub_server.js.html +678 -0
  216. package/docs/api/storage_backends_gundb-backend.js.html +878 -0
  217. package/docs/api/storage_backends_nostr-backend.js.html +254 -0
  218. package/docs/api/storage_filesystem-storage-browser.js.html +83 -0
  219. package/docs/api/storage_filesystem-storage.js.html +207 -0
  220. package/docs/api/storage_global-tables.js.html +116 -0
  221. package/docs/api/storage_gun-async.js.html +344 -0
  222. package/docs/api/storage_gun-auth.js.html +376 -0
  223. package/docs/api/storage_gun-federation.js.html +788 -0
  224. package/docs/api/storage_gun-references.js.html +212 -0
  225. package/docs/api/storage_gun-schema.js.html +309 -0
  226. package/docs/api/storage_gun-wrapper.js.html +645 -0
  227. package/docs/api/storage_indexeddb-storage.js.html +224 -0
  228. package/docs/api/storage_key-storage-simple.js.html +102 -0
  229. package/docs/api/storage_key-storage.js.html +171 -0
  230. package/docs/api/storage_memory-storage.js.html +128 -0
  231. package/docs/api/storage_migration.js.html +354 -0
  232. package/docs/api/storage_nostr-async.js.html +1076 -0
  233. package/docs/api/storage_nostr-client.js.html +1598 -0
  234. package/docs/api/storage_nostr-wrapper.js.html +218 -0
  235. package/docs/api/storage_outbox-queue.js.html +248 -0
  236. package/docs/api/storage_persistent-storage.js.html +160 -0
  237. package/docs/api/storage_sync-service.js.html +201 -0
  238. package/docs/api/storage_unified-storage.js.html +157 -0
  239. package/docs/api/styles/clean-jsdoc-theme-base.css +1159 -0
  240. package/docs/api/styles/clean-jsdoc-theme-dark.css +412 -0
  241. package/docs/api/styles/clean-jsdoc-theme-light.css +482 -0
  242. package/docs/api/styles/clean-jsdoc-theme-scrollbar.css +30 -0
  243. package/docs/api/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
  244. package/docs/api/styles/clean-jsdoc-theme.min.css +1 -0
  245. package/docs/api/subscriptions_manager.js.html +162 -0
  246. package/jsdoc.json +26 -0
  247. package/package.json +14 -3
  248. package/src/ai/aggregation.js +13 -2
  249. package/src/ai/breakdown.js +12 -2
  250. package/src/ai/classifier.js +14 -3
  251. package/src/ai/council.js +22 -7
  252. package/src/ai/embeddings.js +37 -15
  253. package/src/ai/federation-ai.js +13 -2
  254. package/src/ai/h3-ai.js +14 -2
  255. package/src/ai/index.js +16 -7
  256. package/src/ai/json-ops.js +18 -5
  257. package/src/ai/llm-service.js +62 -31
  258. package/src/ai/nl-query.js +12 -2
  259. package/src/ai/relationships.js +13 -2
  260. package/src/ai/schema-extractor.js +24 -10
  261. package/src/ai/spatial.js +13 -2
  262. package/src/ai/tts.js +25 -8
  263. package/src/content/social-protocols.js +34 -25
  264. package/src/contracts/chain-manager.js +68 -40
  265. package/src/contracts/deployer.js +70 -42
  266. package/src/contracts/event-listener.js +61 -29
  267. package/src/contracts/holon-contracts.js +46 -31
  268. package/src/contracts/index.js +5 -6
  269. package/src/contracts/networks.js +19 -14
  270. package/src/contracts/operations.js +58 -41
  271. package/src/contracts/queries.js +54 -20
  272. package/src/core/holosphere.js +35 -6
  273. package/src/crypto/nostr-utils.js +82 -76
  274. package/src/crypto/secp256k1.js +7 -2
  275. package/src/federation/handshake.js +7 -7
  276. package/src/federation/hologram.js +9 -1
  277. package/src/hierarchical/upcast.js +34 -20
  278. package/src/index.js +655 -5
  279. package/src/lib/ai-methods.js +352 -3
  280. package/src/lib/contract-methods.js +152 -3
  281. package/src/lib/errors.js +31 -1
  282. package/src/lib/federation-methods.js +110 -3
  283. package/src/lib/index.js +9 -5
  284. package/src/schema/validator.js +22 -3
  285. package/src/spatial/h3-operations.js +17 -1
  286. package/src/storage/backend-factory.js +7 -2
  287. package/src/storage/backend-interface.js +21 -2
  288. package/src/storage/backends/activitypub/server.js +25 -3
  289. package/src/storage/backends/activitypub-backend.js +25 -2
  290. package/src/storage/backends/gundb-backend.js +29 -2
  291. package/src/storage/backends/nostr-backend.js +116 -1
  292. package/src/storage/filesystem-storage-browser.js +42 -2
  293. package/src/storage/filesystem-storage.js +72 -5
  294. package/src/storage/global-tables.js +7 -2
  295. package/src/storage/gun-async.js +20 -11
  296. package/src/storage/gun-auth.js +15 -4
  297. package/src/storage/gun-federation.js +14 -5
  298. package/src/storage/gun-references.js +16 -5
  299. package/src/storage/gun-schema.js +25 -10
  300. package/src/storage/gun-wrapper.js +99 -36
  301. package/src/storage/indexeddb-storage.js +65 -4
  302. package/src/storage/key-storage-simple.js +32 -9
  303. package/src/storage/key-storage.js +45 -13
  304. package/src/storage/memory-storage.js +65 -4
  305. package/src/storage/migration.js +20 -7
  306. package/src/storage/nostr-async.js +157 -67
  307. package/src/storage/nostr-client.js +173 -49
  308. package/src/storage/nostr-wrapper.js +6 -2
  309. package/src/storage/outbox-queue.js +55 -18
  310. package/src/storage/persistent-storage.js +56 -13
  311. package/src/storage/sync-service.js +51 -17
  312. package/src/storage/unified-storage.js +7 -2
  313. package/src/subscriptions/manager.js +33 -16
  314. package/dist/index-4XHHKe6S.js.map +0 -1
  315. package/dist/index-Dz5kOZMI.cjs.map +0 -1
  316. package/dist/indexeddb-storage-DD7EFBVc.cjs.map +0 -1
  317. package/dist/indexeddb-storage-lExjjFlV.js.map +0 -1
  318. package/dist/memory-storage-C68adso2.js.map +0 -1
  319. package/dist/memory-storage-DD_6yyXT.cjs.map +0 -1
  320. /package/{cleanup-test-data.js → scripts/cleanup-test-data.js} +0 -0
  321. /package/{test-ai-real-api.js → scripts/test-ai-real-api.js} +0 -0
@@ -0,0 +1,1598 @@
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-client.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-client.js</h1></header><article><pre class="prettyprint source lang-js"><code>/**
4
+ * @fileoverview Nostr Client - SimplePool wrapper for relay management.
5
+ *
6
+ * Provides connection pooling, relay management, event caching, persistent storage,
7
+ * outbox queue for guaranteed delivery, and background sync services.
8
+ *
9
+ * @module storage/nostr-client
10
+ */
11
+
12
+ import { SimplePool, finalizeEvent, getPublicKey } from 'nostr-tools';
13
+ import { OutboxQueue } from './outbox-queue.js';
14
+ import { SyncService } from './sync-service.js';
15
+
16
+ /**
17
+ * Global pool singleton - reuse connections across NostrClient instances.
18
+ * @private
19
+ */
20
+ let globalPool = null;
21
+
22
+ /**
23
+ * Set of relays used by the global pool.
24
+ * @private
25
+ */
26
+ let globalPoolRelays = new Set();
27
+
28
+ /**
29
+ * Get or create global SimplePool singleton.
30
+ * This ensures WebSocket connections are reused across all operations.
31
+ *
32
+ * @private
33
+ * @param {Object} [config={}] - Pool configuration
34
+ * @param {boolean} [config.enableReconnect=true] - Enable auto-reconnect
35
+ * @param {boolean} [config.enablePing=true] - Enable ping/pong
36
+ * @returns {SimplePool} The global SimplePool instance
37
+ */
38
+ function getGlobalPool(config = {}) {
39
+ if (!globalPool) {
40
+ globalPool = new SimplePool({
41
+ enableReconnect: config.enableReconnect !== false,
42
+ enablePing: config.enablePing !== false,
43
+ });
44
+ }
45
+ return globalPool;
46
+ }
47
+
48
+ /**
49
+ * Global pending queries map for deduplication.
50
+ * Key: JSON-stringified filter, Value: { promise, timestamp }
51
+ * @private
52
+ */
53
+ const pendingQueries = new Map();
54
+
55
+ /**
56
+ * Pending query timeout (5 seconds).
57
+ * @private
58
+ * @constant {number}
59
+ */
60
+ const PENDING_QUERY_TIMEOUT = 5000;
61
+
62
+ /**
63
+ * Global active subscriptions map for subscription deduplication.
64
+ * Key: JSON-stringified filter, Value: { subscription, callbacks: Set, refCount }
65
+ * @private
66
+ */
67
+ const activeSubscriptions = new Map();
68
+
69
+ /**
70
+ * Throttle background refreshes to avoid flooding the relay.
71
+ * Key: path, Value: timestamp of last refresh
72
+ * @private
73
+ */
74
+ const backgroundRefreshThrottle = new Map();
75
+
76
+ /**
77
+ * Background refresh interval (30 seconds).
78
+ * @private
79
+ * @constant {number}
80
+ */
81
+ const BACKGROUND_REFRESH_INTERVAL = 30000;
82
+
83
+ /**
84
+ * Write debouncing for rapid updates to the same path.
85
+ * Key: d-tag path, Value: { event, timer, resolve, reject }
86
+ * @private
87
+ */
88
+ const pendingWrites = new Map();
89
+
90
+ /**
91
+ * Write debounce window (500ms).
92
+ * @private
93
+ * @constant {number}
94
+ */
95
+ const WRITE_DEBOUNCE_MS = 500;
96
+
97
+ /**
98
+ * Long-lived subscription manager - keeps ONE subscription per author for real-time updates.
99
+ * Key: pubkey, Value: { subscription, lastEventTime, initialized }
100
+ * @private
101
+ */
102
+ const authorSubscriptions = new Map();
103
+
104
+ /**
105
+ * Author subscription initialization timeout (5 seconds).
106
+ * @private
107
+ * @constant {number}
108
+ */
109
+ const AUTHOR_SUB_INIT_TIMEOUT = 5000;
110
+
111
+ /**
112
+ * Simple LRU Cache implementation.
113
+ * Automatically evicts least recently used entries when max size is reached.
114
+ *
115
+ * @private
116
+ * @class LRUCache
117
+ */
118
+ class LRUCache {
119
+ /**
120
+ * Create a new LRU cache.
121
+ *
122
+ * @param {number} [maxSize=500] - Maximum number of entries
123
+ */
124
+ constructor(maxSize = 500) {
125
+ this.maxSize = maxSize;
126
+ this.cache = new Map();
127
+ }
128
+
129
+ /**
130
+ * Get an entry from the cache.
131
+ *
132
+ * @param {string} key - Cache key
133
+ * @returns {*} Cached value or undefined
134
+ */
135
+ get(key) {
136
+ if (!this.cache.has(key)) return undefined;
137
+
138
+ // Move to end (most recently used)
139
+ const value = this.cache.get(key);
140
+ this.cache.delete(key);
141
+ this.cache.set(key, value);
142
+ return value;
143
+ }
144
+
145
+ set(key, value) {
146
+ // If key exists, delete it first to update position
147
+ if (this.cache.has(key)) {
148
+ this.cache.delete(key);
149
+ }
150
+
151
+ this.cache.set(key, value);
152
+
153
+ // Evict oldest entries if over capacity
154
+ while (this.cache.size > this.maxSize) {
155
+ const oldestKey = this.cache.keys().next().value;
156
+ this.cache.delete(oldestKey);
157
+ }
158
+ }
159
+
160
+ has(key) {
161
+ return this.cache.has(key);
162
+ }
163
+
164
+ delete(key) {
165
+ return this.cache.delete(key);
166
+ }
167
+
168
+ clear() {
169
+ this.cache.clear();
170
+ }
171
+
172
+ get size() {
173
+ return this.cache.size;
174
+ }
175
+
176
+ keys() {
177
+ return this.cache.keys();
178
+ }
179
+
180
+ values() {
181
+ return this.cache.values();
182
+ }
183
+
184
+ entries() {
185
+ return this.cache.entries();
186
+ }
187
+
188
+ forEach(callback) {
189
+ this.cache.forEach(callback);
190
+ }
191
+ }
192
+
193
+ // Lazy-load WebSocket polyfill for Node.js environment
194
+ let webSocketPolyfillPromise = null;
195
+ function ensureWebSocket() {
196
+ if (typeof globalThis.WebSocket !== 'undefined') {
197
+ return Promise.resolve();
198
+ }
199
+
200
+ if (!webSocketPolyfillPromise) {
201
+ webSocketPolyfillPromise = (async () => {
202
+ try {
203
+ const { default: WebSocket } = await import('ws');
204
+ globalThis.WebSocket = WebSocket;
205
+ } catch (e) {
206
+ // WebSocket polyfill not available, will use mock pool
207
+ }
208
+ })();
209
+ }
210
+
211
+ return webSocketPolyfillPromise;
212
+ }
213
+ import { createPersistentStorage } from './persistent-storage.js';
214
+
215
+ /**
216
+ * NostrClient - Manages connections to Nostr relays.
217
+ *
218
+ * Provides event publishing, querying, subscription management, persistent caching,
219
+ * and guaranteed delivery via outbox queue.
220
+ *
221
+ * @class NostrClient
222
+ * @example
223
+ * const client = new NostrClient({
224
+ * relays: ['wss://relay.example.com'],
225
+ * appName: 'myapp',
226
+ * persistence: true
227
+ * });
228
+ */
229
+ export class NostrClient {
230
+ /**
231
+ * Create a new NostrClient.
232
+ *
233
+ * @param {Object} [config={}] - Configuration options
234
+ * @param {string[]} [config.relays=[]] - Array of relay URLs
235
+ * @param {string} [config.privateKey] - Private key for signing (hex format)
236
+ * @param {boolean} [config.enableReconnect=true] - Auto-reconnect on disconnect
237
+ * @param {boolean} [config.enablePing=true] - Auto-ping relays
238
+ * @param {string} [config.appName] - Application name for storage namespace
239
+ * @param {boolean} [config.persistence=true] - Enable persistent storage
240
+ * @param {number} [config.cacheSize=500] - Maximum cache size
241
+ * @param {number} [config.persistBatchMs=100] - Batch persist writes within this window
242
+ * @param {boolean} [config.backgroundSync=true] - Enable background sync service
243
+ * @param {string} [config.dataDir] - Data directory for persistent storage
244
+ * @throws {Error} If relays is not an array
245
+ */
246
+ constructor(config = {}) {
247
+ if (config.relays &amp;&amp; !Array.isArray(config.relays)) {
248
+ throw new Error('Relays must be an array');
249
+ }
250
+
251
+ this.relays = config.relays || [];
252
+
253
+ // Generate or use provided private key
254
+ // IMPORTANT: If you want keys to persist across restarts, you MUST provide
255
+ // a privateKey in config. Otherwise, a new key is generated each time.
256
+ this.privateKey = config.privateKey || this._generatePrivateKey();
257
+ this.publicKey = getPublicKey(this.privateKey);
258
+ this.config = config;
259
+
260
+ this._subscriptions = new Map();
261
+ this._eventCache = new LRUCache(config.cacheSize || 500); // LRU cache for recent events
262
+ this._cacheIndex = new Map(); // Reverse index: kind -> Set of cache keys affected by that kind
263
+ this.persistentStorage = null;
264
+
265
+ // Batched persistent writes for better I/O performance
266
+ this._persistQueue = new Map(); // path -> event
267
+ this._persistTimer = null;
268
+ this._persistBatchMs = config.persistBatchMs || 100; // Batch writes within 100ms window
269
+
270
+ // Initialize pool and storage asynchronously
271
+ this._initReady = this._initialize();
272
+ }
273
+
274
+ /**
275
+ * Initialize WebSocket polyfill and pool.
276
+ *
277
+ * @private
278
+ * @returns {Promise&lt;void>}
279
+ */
280
+ async _initialize() {
281
+ // Ensure WebSocket is available before initializing pool
282
+ await ensureWebSocket();
283
+
284
+ // Initialize SimplePool with options (only if relays exist)
285
+ if (this.relays.length > 0) {
286
+ // Use global pool singleton to reuse WebSocket connections
287
+ this.pool = getGlobalPool(this.config);
288
+
289
+ // Track relays used by this client
290
+ this.relays.forEach(r => globalPoolRelays.add(r));
291
+ } else {
292
+ // Mock pool for testing - returns mock promise that resolves immediately
293
+ this.pool = {
294
+ publish: (relays, event) => [Promise.resolve()],
295
+ querySync: (relays, filter, options) => Promise.resolve([]),
296
+ subscribeMany: (relays, filters, opts) => ({ close: () => {} }),
297
+ close: (relays) => {},
298
+ };
299
+ }
300
+
301
+ // Initialize persistent storage
302
+ await this._initPersistentStorage();
303
+
304
+ // Start long-lived subscription for real-time cache updates
305
+ if (this.relays.length > 0) {
306
+ this._initLongLivedSubscription();
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Initialize a long-lived subscription to keep cache fresh
312
+ * This replaces polling with a single persistent subscription
313
+ * @private
314
+ */
315
+ _initLongLivedSubscription() {
316
+ const subKey = this.publicKey;
317
+
318
+ // Check if subscription already exists for this author
319
+ if (authorSubscriptions.has(subKey)) {
320
+ return;
321
+ }
322
+
323
+ const subInfo = {
324
+ subscription: null,
325
+ initialized: false,
326
+ initPromise: null,
327
+ initResolve: null,
328
+ };
329
+
330
+ // Create promise for initial load completion
331
+ subInfo.initPromise = new Promise(resolve => {
332
+ subInfo.initResolve = resolve;
333
+ });
334
+
335
+ authorSubscriptions.set(subKey, subInfo);
336
+
337
+ // Subscribe to ALL events for this author (kind 30000)
338
+ // This single subscription replaces all the polling queries
339
+ const filter = {
340
+ kinds: [30000],
341
+ authors: [this.publicKey],
342
+ };
343
+
344
+ const sub = this.pool.subscribeMany(
345
+ this.relays,
346
+ [filter],
347
+ {
348
+ onevent: (event) => {
349
+ // Verify author (relay may not respect filter)
350
+ if (event.pubkey !== this.publicKey) {
351
+ return;
352
+ }
353
+
354
+ // Cache the event - this keeps our local cache in sync
355
+ this._cacheEvent(event);
356
+ },
357
+ oneose: () => {
358
+ // End of stored events - initial load complete
359
+ if (!subInfo.initialized) {
360
+ subInfo.initialized = true;
361
+ subInfo.initResolve();
362
+ }
363
+ },
364
+ }
365
+ );
366
+
367
+ subInfo.subscription = sub;
368
+
369
+ // Set timeout for initial load in case EOSE never arrives
370
+ setTimeout(() => {
371
+ if (!subInfo.initialized) {
372
+ subInfo.initialized = true;
373
+ subInfo.initResolve();
374
+ }
375
+ }, AUTHOR_SUB_INIT_TIMEOUT);
376
+ }
377
+
378
+ /**
379
+ * Wait for long-lived subscription to complete initial load
380
+ * @private
381
+ */
382
+ async _waitForSubscriptionInit() {
383
+ const subInfo = authorSubscriptions.get(this.publicKey);
384
+ if (subInfo &amp;&amp; subInfo.initPromise) {
385
+ await subInfo.initPromise;
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Initialize persistent storage
391
+ * @private
392
+ */
393
+ async _initPersistentStorage() {
394
+ // Only enable persistence if explicitly requested or radisk is true
395
+ if (this.config.persistence !== false &amp;&amp; (this.config.radisk || this.config.appName)) {
396
+ try {
397
+ const namespace = this.config.appName || 'holosphere_default';
398
+ this.persistentStorage = await createPersistentStorage(namespace, {
399
+ dataDir: this.config.dataDir
400
+ });
401
+
402
+ // Load cached events from persistent storage
403
+ await this._loadFromPersistentStorage();
404
+
405
+ // Initialize outbox queue for guaranteed delivery
406
+ this.outboxQueue = new OutboxQueue(this.persistentStorage, {
407
+ maxRetries: this.config.maxRetries || 5,
408
+ baseDelay: this.config.retryBaseDelay || 1000,
409
+ maxDelay: this.config.retryMaxDelay || 60000,
410
+ failedTTL: this.config.failedTTL || 24 * 60 * 60 * 1000, // 24 hours
411
+ });
412
+
413
+ // Start background sync service
414
+ if (this.config.backgroundSync !== false) {
415
+ this.syncService = new SyncService(this, {
416
+ interval: this.config.syncInterval || 10000, // 10 seconds
417
+ autoStart: true,
418
+ });
419
+ }
420
+ } catch (error) {
421
+ console.warn('Failed to initialize persistent storage:', error);
422
+ // Continue without persistence
423
+ }
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Load events from persistent storage into cache
429
+ * @private
430
+ */
431
+ async _loadFromPersistentStorage() {
432
+ if (!this.persistentStorage) return;
433
+
434
+ try {
435
+ // Load all events (excluding outbox queue entries)
436
+ const events = await this.persistentStorage.getAll('');
437
+ for (const event of events) {
438
+ // Skip outbox queue entries
439
+ if (event &amp;&amp; event.id &amp;&amp; !event.status) {
440
+ // Add to cache
441
+ this._cacheEventSync(event);
442
+ }
443
+ }
444
+ } catch (error) {
445
+ console.warn('Failed to load from persistent storage:', error);
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Get a single event from persistent storage by path (d-tag)
451
+ * @param {string} path - The d-tag path to look up
452
+ * @returns {Promise&lt;Object|null>} The event or null if not found
453
+ */
454
+ async persistentGet(path) {
455
+ await this._initReady;
456
+ if (!this.persistentStorage) return null;
457
+ try {
458
+ const event = await this.persistentStorage.get(path);
459
+ return event || null;
460
+ } catch (error) {
461
+ console.warn('[nostr] Persistent storage read failed:', error);
462
+ return null;
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Get all events from persistent storage matching a prefix
468
+ * @param {string} prefix - The path prefix to match
469
+ * @returns {Promise&lt;Array>} Array of events
470
+ */
471
+ async persistentGetAll(prefix) {
472
+ await this._initReady;
473
+ if (!this.persistentStorage) return [];
474
+ try {
475
+ const events = await this.persistentStorage.getAll(prefix);
476
+ // Filter out outbox queue entries and null values
477
+ return events.filter(e => e &amp;&amp; e.id &amp;&amp; !e.status);
478
+ } catch (error) {
479
+ console.warn('[nostr] Persistent storage read failed:', error);
480
+ return [];
481
+ }
482
+ }
483
+
484
+ /**
485
+ * Generate random private key
486
+ * @private
487
+ */
488
+ _generatePrivateKey() {
489
+ const bytes = new Uint8Array(32);
490
+ if (typeof window !== 'undefined' &amp;&amp; window.crypto) {
491
+ window.crypto.getRandomValues(bytes);
492
+ } else {
493
+ // Node.js - use dynamic import
494
+ try {
495
+ // Use crypto.randomFillSync if available
496
+ const crypto = globalThis.crypto || global.crypto;
497
+ if (crypto &amp;&amp; crypto.getRandomValues) {
498
+ crypto.getRandomValues(bytes);
499
+ } else {
500
+ // Fallback to Math.random (less secure, but works in testing)
501
+ for (let i = 0; i &lt; 32; i++) {
502
+ bytes[i] = Math.floor(Math.random() * 256);
503
+ }
504
+ }
505
+ } catch (e) {
506
+ // Fallback to Math.random
507
+ for (let i = 0; i &lt; 32; i++) {
508
+ bytes[i] = Math.floor(Math.random() * 256);
509
+ }
510
+ }
511
+ }
512
+ return Array.from(bytes)
513
+ .map(b => b.toString(16).padStart(2, '0'))
514
+ .join('');
515
+ }
516
+
517
+ /**
518
+ * Publish event to relays.
519
+ *
520
+ * Supports debouncing for replaceable events (kind 30000-39999) to avoid rapid updates.
521
+ *
522
+ * @param {Object} event - Unsigned event object
523
+ * @param {Object} [options={}] - Publish options
524
+ * @param {boolean} [options.waitForRelays=false] - Wait for relay confirmation
525
+ * @param {boolean} [options.debounce=true] - Debounce rapid writes to same d-tag
526
+ * @returns {Promise&lt;Object>} Signed event with relay publish results
527
+ * @example
528
+ * const result = await client.publish({
529
+ * kind: 30000,
530
+ * tags: [['d', 'mypath']],
531
+ * content: JSON.stringify({ name: 'Test' })
532
+ * });
533
+ */
534
+ async publish(event, options = {}) {
535
+ // Ensure initialization is complete
536
+ await this._initReady;
537
+
538
+ const waitForRelays = options.waitForRelays || false;
539
+
540
+ // For replaceable events, check if we should debounce
541
+ const isReplaceable = event.kind >= 30000 &amp;&amp; event.kind &lt; 40000;
542
+ const shouldDebounce = isReplaceable &amp;&amp; options.debounce !== false &amp;&amp; !waitForRelays;
543
+
544
+ if (shouldDebounce) {
545
+ const dTag = event.tags?.find(t => t[0] === 'd');
546
+ if (dTag &amp;&amp; dTag[1]) {
547
+ return this._debouncedPublish(event, dTag[1], options);
548
+ }
549
+ }
550
+
551
+ return this._doPublish(event, options);
552
+ }
553
+
554
+ /**
555
+ * Debounced publish - coalesces rapid writes to the same d-tag
556
+ * @private
557
+ */
558
+ _debouncedPublish(event, dTagPath, options) {
559
+ return new Promise((resolve, reject) => {
560
+ const existing = pendingWrites.get(dTagPath);
561
+
562
+ if (existing) {
563
+ // Cancel previous pending write and use the new one
564
+ clearTimeout(existing.timer);
565
+ // Resolve the previous promise with the new event (it will be superseded)
566
+ existing.resolve({
567
+ event: null,
568
+ results: [],
569
+ debounced: true,
570
+ supersededBy: event,
571
+ });
572
+ }
573
+
574
+ // Set up debounced write
575
+ const timer = setTimeout(async () => {
576
+ pendingWrites.delete(dTagPath);
577
+ try {
578
+ const result = await this._doPublish(event, options);
579
+ resolve(result);
580
+ } catch (err) {
581
+ reject(err);
582
+ }
583
+ }, WRITE_DEBOUNCE_MS);
584
+
585
+ pendingWrites.set(dTagPath, { event, timer, resolve, reject });
586
+
587
+ // Cache immediately for local-first reads (even before relay publish)
588
+ const signedEvent = finalizeEvent(event, this.privateKey);
589
+ this._cacheEvent(signedEvent);
590
+ });
591
+ }
592
+
593
+ /**
594
+ * Internal publish implementation
595
+ * @private
596
+ */
597
+ async _doPublish(event, options = {}) {
598
+ const waitForRelays = options.waitForRelays || false;
599
+
600
+ // Check if event is already signed (has id and sig)
601
+ // If so, use it as-is; otherwise sign it
602
+ const signedEvent = (event.id &amp;&amp; event.sig)
603
+ ? event
604
+ : finalizeEvent(event, this.privateKey);
605
+
606
+ // 1. Cache the event locally first (this makes reads instant)
607
+ await this._cacheEvent(signedEvent);
608
+
609
+ // 2. Enqueue for guaranteed delivery (if outbox queue available)
610
+ if (this.outboxQueue) {
611
+ await this.outboxQueue.enqueue(signedEvent, this.relays);
612
+ }
613
+
614
+ // 3. Publish to relays
615
+ if (waitForRelays) {
616
+ // Wait for relay confirmation if explicitly requested
617
+ const deliveryResult = await this._attemptDelivery(signedEvent, this.relays);
618
+
619
+ return {
620
+ event: signedEvent,
621
+ results: deliveryResult.results,
622
+ queued: !!this.outboxQueue,
623
+ };
624
+ } else {
625
+ // Fire and forget - publish in background, return immediately
626
+ this._attemptDelivery(signedEvent, this.relays).catch(() => {
627
+ // Silent - event is cached locally and queued for retry
628
+ });
629
+
630
+ // Return immediately (data is in local cache and queued for delivery)
631
+ return {
632
+ event: signedEvent,
633
+ results: [],
634
+ queued: !!this.outboxQueue,
635
+ };
636
+ }
637
+ }
638
+
639
+ /**
640
+ * Attempt to deliver event to relays
641
+ * Updates outbox queue with delivery status
642
+ * @param {Object} event - Signed event to deliver
643
+ * @param {string[]} relays - Target relay URLs
644
+ * @returns {Promise&lt;Object>} Delivery result with successful/failed relays
645
+ */
646
+ async _attemptDelivery(event, relays) {
647
+ const results = await Promise.allSettled(
648
+ this.pool.publish(relays, event)
649
+ );
650
+
651
+ const successful = [];
652
+ const failed = [];
653
+ const formattedResults = [];
654
+
655
+ results.forEach((r, i) => {
656
+ formattedResults.push({
657
+ relay: relays[i],
658
+ status: r.status,
659
+ value: r.value,
660
+ reason: r.reason,
661
+ });
662
+
663
+ if (r.status === 'fulfilled') {
664
+ successful.push(relays[i]);
665
+ } else {
666
+ failed.push(relays[i]);
667
+ }
668
+ });
669
+
670
+ // Update outbox queue with delivery status
671
+ if (this.outboxQueue) {
672
+ await this.outboxQueue.markSent(event.id, successful);
673
+ }
674
+
675
+ // Log failures only at debug level (common during network issues)
676
+ if (failed.length > 0 &amp;&amp; successful.length === 0) {
677
+ // Only warn if ALL relays failed (likely a real issue)
678
+ const reasons = results
679
+ .filter(r => r.status === 'rejected')
680
+ .map(f => f.reason?.message || f.reason || 'unknown')
681
+ .join(', ');
682
+ // Use debug level for timeout issues (very common)
683
+ if (reasons.includes('timed out')) {
684
+ // Silent - event is queued for retry anyway
685
+ } else {
686
+ console.warn(`[nostr] All relays failed for ${event.id.slice(0, 8)}: ${reasons}`);
687
+ }
688
+ }
689
+
690
+ return { successful, failed, results: formattedResults };
691
+ }
692
+
693
+ /**
694
+ * Query events from relays.
695
+ *
696
+ * Uses long-lived subscription for cache updates - avoids polling.
697
+ *
698
+ * @param {Object} filter - Nostr filter object
699
+ * @param {Object} [options={}] - Query options
700
+ * @param {number} [options.timeout=30000] - Query timeout in ms (set to 0 for no timeout)
701
+ * @param {boolean} [options.localFirst=true] - Return local cache immediately, refresh in background
702
+ * @param {boolean} [options.forceRelay=false] - Force relay query even if subscription cache is available
703
+ * @returns {Promise&lt;Array>} Array of events
704
+ * @example
705
+ * const events = await client.query({
706
+ * kinds: [30000],
707
+ * authors: [client.publicKey],
708
+ * '#d': ['mypath']
709
+ * });
710
+ */
711
+ async query(filter, options = {}) {
712
+ // Ensure initialization is complete
713
+ await this._initReady;
714
+
715
+ const timeout = options.timeout !== undefined ? options.timeout : 30000;
716
+ const localFirst = options.localFirst !== false; // Default to true for speed
717
+ const forceRelay = options.forceRelay === true;
718
+
719
+ // If no relays, query from cache only
720
+ if (this.relays.length === 0) {
721
+ const matchingEvents = this._getMatchingCachedEvents(filter);
722
+ return matchingEvents;
723
+ }
724
+
725
+ // Check if this query can be served from the long-lived subscription cache
726
+ // The subscription keeps ALL events for this author in cache, updated in real-time
727
+ const subInfo = authorSubscriptions.get(this.publicKey);
728
+ const isOwnDataQuery = filter.authors &amp;&amp;
729
+ filter.authors.length === 1 &amp;&amp;
730
+ filter.authors[0] === this.publicKey;
731
+
732
+ // If we have an initialized subscription for our own data, use cache
733
+ if (!forceRelay &amp;&amp; isOwnDataQuery &amp;&amp; subInfo &amp;&amp; subInfo.initialized) {
734
+ // Return matching events from cache - no relay query needed!
735
+ const matchingEvents = this._getMatchingCachedEvents(filter);
736
+ return matchingEvents;
737
+ }
738
+
739
+ // For first query before subscription initializes, wait briefly
740
+ if (isOwnDataQuery &amp;&amp; subInfo &amp;&amp; !subInfo.initialized) {
741
+ // Wait for subscription to initialize (up to timeout)
742
+ await Promise.race([
743
+ subInfo.initPromise,
744
+ new Promise(resolve => setTimeout(resolve, Math.min(timeout, AUTHOR_SUB_INIT_TIMEOUT)))
745
+ ]);
746
+
747
+ // Now try cache again
748
+ if (subInfo.initialized) {
749
+ const matchingEvents = this._getMatchingCachedEvents(filter);
750
+ return matchingEvents;
751
+ }
752
+ }
753
+
754
+ // Check d-tag cache first for single-item queries (most common case)
755
+ // This ensures recently written data is returned immediately
756
+ if (filter['#d'] &amp;&amp; filter['#d'].length === 1 &amp;&amp; filter.kinds &amp;&amp; filter.kinds.length === 1) {
757
+ const dTagKey = `d:${filter.kinds[0]}:${filter['#d'][0]}`;
758
+ const dTagCached = this._eventCache.get(dTagKey);
759
+ if (dTagCached &amp;&amp; Date.now() - dTagCached.timestamp &lt; 5000) {
760
+ return dTagCached.events;
761
+ }
762
+ }
763
+
764
+ const cacheKey = JSON.stringify(filter);
765
+ const cached = this._eventCache.get(cacheKey);
766
+
767
+ // Return fresh cache immediately
768
+ if (cached &amp;&amp; Date.now() - cached.timestamp &lt; 5000) {
769
+ return cached.events;
770
+ }
771
+
772
+ // If we have stale cache and localFirst is enabled, return it immediately
773
+ // and refresh in background
774
+ if (localFirst &amp;&amp; cached) {
775
+ // Refresh cache in background
776
+ this._refreshCacheInBackground(filter, cacheKey, timeout);
777
+ return cached.events;
778
+ }
779
+
780
+ // No cache available or localFirst disabled - must query relays
781
+ return this._queryRelaysAndCache(filter, cacheKey, timeout);
782
+ }
783
+
784
+ /**
785
+ * Query relays and update cache
786
+ * Uses global pending queries map to deduplicate identical concurrent queries
787
+ * @private
788
+ */
789
+ async _queryRelaysAndCache(filter, cacheKey, timeout) {
790
+ // Check if there's already a pending query for this exact filter
791
+ const pending = pendingQueries.get(cacheKey);
792
+ if (pending &amp;&amp; Date.now() - pending.timestamp &lt; PENDING_QUERY_TIMEOUT) {
793
+ // Reuse the pending query promise instead of creating a new one
794
+ return pending.promise;
795
+ }
796
+
797
+ // Create the query promise
798
+ const queryPromise = (async () => {
799
+ try {
800
+ let events = await this.pool.querySync(this.relays, filter, { timeout });
801
+
802
+ // CRITICAL: Filter out events from other authors (relay may not respect filter)
803
+ if (filter.authors &amp;&amp; filter.authors.length > 0) {
804
+ events = events.filter(event => filter.authors.includes(event.pubkey));
805
+ }
806
+
807
+ // Cache results
808
+ this._eventCache.set(cacheKey, {
809
+ events,
810
+ timestamp: Date.now(),
811
+ });
812
+
813
+ // Update reverse index for fast invalidation
814
+ this._indexCacheEntry(cacheKey, filter);
815
+
816
+ return events;
817
+ } finally {
818
+ // Clean up pending query after completion
819
+ pendingQueries.delete(cacheKey);
820
+ }
821
+ })();
822
+
823
+ // Store the pending query
824
+ pendingQueries.set(cacheKey, {
825
+ promise: queryPromise,
826
+ timestamp: Date.now(),
827
+ });
828
+
829
+ return queryPromise;
830
+ }
831
+
832
+ /**
833
+ * Limit cache size (called after cache operations)
834
+ * Note: LRU cache handles this automatically, kept for API compatibility
835
+ * @private
836
+ */
837
+ _limitCacheSize() {
838
+ // LRU cache handles size limiting automatically
839
+ }
840
+
841
+ /**
842
+ * Add cache entry to reverse index for fast invalidation
843
+ * @private
844
+ */
845
+ _indexCacheEntry(cacheKey, filter) {
846
+ // Index by kinds for fast lookup during invalidation
847
+ if (filter.kinds) {
848
+ for (const kind of filter.kinds) {
849
+ if (!this._cacheIndex.has(kind)) {
850
+ this._cacheIndex.set(kind, new Set());
851
+ }
852
+ this._cacheIndex.get(kind).add(cacheKey);
853
+ }
854
+ }
855
+ }
856
+
857
+ /**
858
+ * Remove cache entry from reverse index
859
+ * @private
860
+ */
861
+ _unindexCacheEntry(cacheKey) {
862
+ // Try to parse the filter from the cache key to remove from index
863
+ if (!cacheKey.startsWith('{')) return;
864
+
865
+ try {
866
+ const filter = JSON.parse(cacheKey);
867
+ if (filter.kinds) {
868
+ for (const kind of filter.kinds) {
869
+ const indexSet = this._cacheIndex.get(kind);
870
+ if (indexSet) {
871
+ indexSet.delete(cacheKey);
872
+ if (indexSet.size === 0) {
873
+ this._cacheIndex.delete(kind);
874
+ }
875
+ }
876
+ }
877
+ }
878
+ } catch {
879
+ // Not a valid filter key, skip
880
+ }
881
+ }
882
+
883
+ /**
884
+ * Refresh cache in background (fire and forget)
885
+ * @private
886
+ */
887
+ _refreshCacheInBackground(filter, cacheKey, timeout) {
888
+ this._queryRelaysAndCache(filter, cacheKey, timeout).catch(err => {
889
+ // Silently ignore background refresh errors - we have stale cache as fallback
890
+ console.debug('[nostr] Background cache refresh failed:', err.message);
891
+ });
892
+ }
893
+
894
+ /**
895
+ * Refresh a single path from relays in the background
896
+ * Used for persistent-first reads to update local data
897
+ * @param {string} path - The d-tag path to refresh
898
+ * @param {number} kind - Event kind (default: 30000)
899
+ * @param {Object} options - Query options
900
+ */
901
+ refreshPathInBackground(path, kind = 30000, options = {}) {
902
+ this._doBackgroundPathRefresh(path, kind, options).catch(err => {
903
+ console.debug('[nostr] Background path refresh failed:', err.message);
904
+ });
905
+ }
906
+
907
+ /**
908
+ * Internal method to refresh a path from relays
909
+ * Throttled to avoid flooding the relay with repeated requests
910
+ * @private
911
+ */
912
+ async _doBackgroundPathRefresh(path, kind, options) {
913
+ if (this.relays.length === 0) return;
914
+
915
+ // Throttle: Skip if we've refreshed this path recently
916
+ const lastRefresh = backgroundRefreshThrottle.get(path);
917
+ if (lastRefresh &amp;&amp; Date.now() - lastRefresh &lt; BACKGROUND_REFRESH_INTERVAL) {
918
+ return; // Skip - recently refreshed
919
+ }
920
+
921
+ // Mark as refreshed
922
+ backgroundRefreshThrottle.set(path, Date.now());
923
+
924
+ // Clean up old throttle entries periodically (keep map from growing)
925
+ if (backgroundRefreshThrottle.size > 1000) {
926
+ const cutoff = Date.now() - BACKGROUND_REFRESH_INTERVAL;
927
+ for (const [key, timestamp] of backgroundRefreshThrottle) {
928
+ if (timestamp &lt; cutoff) {
929
+ backgroundRefreshThrottle.delete(key);
930
+ }
931
+ }
932
+ }
933
+
934
+ const filter = {
935
+ kinds: [kind],
936
+ authors: options.authors || [this.publicKey],
937
+ '#d': [path],
938
+ limit: 1,
939
+ };
940
+ const cacheKey = JSON.stringify(filter);
941
+
942
+ // Use our query deduplication by calling query() instead of pool.querySync() directly
943
+ const timeout = options.timeout || 30000;
944
+ const events = await this._queryRelaysAndCache(filter, cacheKey, timeout);
945
+
946
+ // Filter by author (relays may not respect filter)
947
+ const authorFiltered = events.filter(e =>
948
+ (options.authors || [this.publicKey]).includes(e.pubkey)
949
+ );
950
+
951
+ if (authorFiltered.length > 0) {
952
+ // Update cache and persistent storage
953
+ await this._cacheEvent(authorFiltered[0]);
954
+ }
955
+ }
956
+
957
+ /**
958
+ * Refresh all paths with a prefix from relays in the background
959
+ * Used for persistent-first collection reads
960
+ * @param {string} prefix - The d-tag prefix to refresh
961
+ * @param {number} kind - Event kind (default: 30000)
962
+ * @param {Object} options - Query options
963
+ */
964
+ refreshPrefixInBackground(prefix, kind = 30000, options = {}) {
965
+ this._doBackgroundPrefixRefresh(prefix, kind, options).catch(err => {
966
+ console.debug('[nostr] Background prefix refresh failed:', err.message);
967
+ });
968
+ }
969
+
970
+ /**
971
+ * Internal method to refresh a prefix from relays
972
+ * Throttled to avoid flooding the relay with repeated requests
973
+ * @private
974
+ */
975
+ async _doBackgroundPrefixRefresh(prefix, kind, options) {
976
+ if (this.relays.length === 0) return;
977
+
978
+ // Throttle: Skip if we've refreshed this prefix recently
979
+ const throttleKey = `prefix:${prefix}`;
980
+ const lastRefresh = backgroundRefreshThrottle.get(throttleKey);
981
+ if (lastRefresh &amp;&amp; Date.now() - lastRefresh &lt; BACKGROUND_REFRESH_INTERVAL) {
982
+ return; // Skip - recently refreshed
983
+ }
984
+
985
+ // Mark as refreshed
986
+ backgroundRefreshThrottle.set(throttleKey, Date.now());
987
+
988
+ // Query with wildcard-ish filter (relays handle d-tag prefix matching)
989
+ const filter = {
990
+ kinds: [kind],
991
+ authors: options.authors || [this.publicKey],
992
+ limit: options.limit || 1000,
993
+ };
994
+ const cacheKey = JSON.stringify(filter);
995
+
996
+ // Use our query deduplication
997
+ const timeout = options.timeout || 30000;
998
+ let events = await this._queryRelaysAndCache(filter, cacheKey, timeout);
999
+
1000
+ // Filter by author (already done by _queryRelaysAndCache, but double-check)
1001
+ events = events.filter(e =>
1002
+ (options.authors || [this.publicKey]).includes(e.pubkey)
1003
+ );
1004
+
1005
+ // Filter by d-tag prefix (client-side since relays don't support prefix matching)
1006
+ events = events.filter(e => {
1007
+ const dTag = e.tags.find(t => t[0] === 'd');
1008
+ return dTag &amp;&amp; dTag[1] &amp;&amp; dTag[1].startsWith(prefix);
1009
+ });
1010
+
1011
+ // Update cache and persistent storage for each event
1012
+ for (const event of events) {
1013
+ await this._cacheEvent(event);
1014
+ }
1015
+ }
1016
+
1017
+ /**
1018
+ * Hybrid query: Check local cache first, then query relays for missing data
1019
+ * Merges local and relay results, preferring newer events
1020
+ * @param {Object} filter - Nostr filter object
1021
+ * @param {Object} options - Query options
1022
+ * @param {number} options.timeout - Query timeout in ms (default: 30000)
1023
+ * @returns {Promise&lt;Array>} Array of events (merged from local + relay)
1024
+ */
1025
+ async queryHybrid(filter, options = {}) {
1026
+ // Ensure initialization is complete
1027
+ await this._initReady;
1028
+
1029
+ const timeout = options.timeout !== undefined ? options.timeout : 30000;
1030
+
1031
+ // Step 1: Get events from local cache
1032
+ const localEvents = this._getMatchingCachedEvents(filter);
1033
+
1034
+ // If no relays configured, return local only
1035
+ if (this.relays.length === 0) {
1036
+ return localEvents;
1037
+ }
1038
+
1039
+ // Step 2: Query relays
1040
+ let relayEvents = [];
1041
+ try {
1042
+ relayEvents = await this.pool.querySync(this.relays, filter, { timeout });
1043
+
1044
+ // Filter out events from other authors
1045
+ if (filter.authors &amp;&amp; filter.authors.length > 0) {
1046
+ relayEvents = relayEvents.filter(event => filter.authors.includes(event.pubkey));
1047
+ }
1048
+ } catch (error) {
1049
+ console.warn('Relay query failed, using local cache only:', error.message);
1050
+ }
1051
+
1052
+ // Step 3: Merge results (prefer newer events for replaceable events)
1053
+ const merged = new Map();
1054
+
1055
+ // Add local events first
1056
+ for (const event of localEvents) {
1057
+ const key = this._getEventKey(event);
1058
+ merged.set(key, event);
1059
+ }
1060
+
1061
+ // Add or update with relay events (relay events are authoritative)
1062
+ for (const event of relayEvents) {
1063
+ const key = this._getEventKey(event);
1064
+ const existing = merged.get(key);
1065
+
1066
+ // For replaceable events, prefer newer
1067
+ if (event.kind >= 30000 &amp;&amp; event.kind &lt; 40000 &amp;&amp; existing) {
1068
+ if (event.created_at >= existing.created_at) {
1069
+ merged.set(key, event);
1070
+ }
1071
+ } else {
1072
+ // For non-replaceable or if no existing, just add
1073
+ merged.set(key, event);
1074
+ }
1075
+ }
1076
+
1077
+ const result = Array.from(merged.values());
1078
+
1079
+ // Cache the merged results
1080
+ const cacheKey = JSON.stringify(filter);
1081
+ this._eventCache.set(cacheKey, {
1082
+ events: result,
1083
+ timestamp: Date.now(),
1084
+ });
1085
+
1086
+ return result;
1087
+ }
1088
+
1089
+ /**
1090
+ * Get unique key for event (for merging)
1091
+ * @private
1092
+ */
1093
+ _getEventKey(event) {
1094
+ // For replaceable events with d-tag, use kind:dtag
1095
+ if (event.kind >= 30000 &amp;&amp; event.kind &lt; 40000) {
1096
+ const dTag = event.tags.find(t => t[0] === 'd');
1097
+ if (dTag &amp;&amp; dTag[1]) {
1098
+ return `${event.kind}:${dTag[1]}`;
1099
+ }
1100
+ }
1101
+ // Otherwise use event id
1102
+ return event.id;
1103
+ }
1104
+
1105
+ /**
1106
+ * Subscribe to events.
1107
+ *
1108
+ * Uses subscription deduplication to avoid creating multiple identical subscriptions.
1109
+ *
1110
+ * @param {Object} filter - Nostr filter object
1111
+ * @param {Function} onEvent - Callback for each event
1112
+ * @param {Object} [options={}] - Subscription options
1113
+ * @param {Function} [options.onEOSE] - Callback when End Of Stored Events is received
1114
+ * @returns {Promise&lt;Object>} Subscription object with unsubscribe method and id
1115
+ * @example
1116
+ * const sub = await client.subscribe(
1117
+ * { kinds: [30000], authors: [client.publicKey] },
1118
+ * (event) => console.log('Event:', event)
1119
+ * );
1120
+ * // Later: sub.unsubscribe();
1121
+ */
1122
+ async subscribe(filter, onEvent, options = {}) {
1123
+ // Ensure initialization is complete
1124
+ await this._initReady;
1125
+
1126
+ const subId = `sub-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
1127
+ const filterKey = JSON.stringify(filter);
1128
+
1129
+ // If no relays, check cache for matching events and trigger callbacks
1130
+ if (this.relays.length === 0) {
1131
+ // Trigger with cached events that match the filter
1132
+ const matchingEvents = this._getMatchingCachedEvents(filter);
1133
+
1134
+ // Create a mock subscription that watches for new cache entries
1135
+ const mockSub = {
1136
+ filter,
1137
+ onEvent,
1138
+ active: true
1139
+ };
1140
+
1141
+ this._subscriptions.set(subId, mockSub);
1142
+
1143
+ // Trigger callbacks asynchronously to mimic real subscription behavior
1144
+ // Check active flag to avoid triggering if unsubscribed immediately
1145
+ setTimeout(() => {
1146
+ if (mockSub.active) {
1147
+ matchingEvents.forEach(event => onEvent(event));
1148
+ if (options.onEOSE) options.onEOSE();
1149
+ }
1150
+ }, 10);
1151
+
1152
+ return {
1153
+ id: subId,
1154
+ unsubscribe: () => {
1155
+ mockSub.active = false;
1156
+ this._subscriptions.delete(subId);
1157
+ // Clear the subscription's onEvent to prevent any further calls
1158
+ mockSub.onEvent = () => {};
1159
+ // Note: active flag is checked before triggering callbacks
1160
+ },
1161
+ };
1162
+ }
1163
+
1164
+ // Check if we already have an active subscription for this filter
1165
+ const existing = activeSubscriptions.get(filterKey);
1166
+ if (existing) {
1167
+ // Add callback to existing subscription
1168
+ existing.callbacks.add(onEvent);
1169
+ existing.refCount++;
1170
+
1171
+ // Return wrapper that removes this specific callback on unsubscribe
1172
+ return {
1173
+ id: subId,
1174
+ unsubscribe: () => {
1175
+ existing.callbacks.delete(onEvent);
1176
+ existing.refCount--;
1177
+
1178
+ // Only close actual subscription when no more callbacks
1179
+ if (existing.refCount === 0) {
1180
+ if (existing.subscription &amp;&amp; existing.subscription.close) {
1181
+ existing.subscription.close();
1182
+ }
1183
+ activeSubscriptions.delete(filterKey);
1184
+ }
1185
+ this._subscriptions.delete(subId);
1186
+ },
1187
+ };
1188
+ }
1189
+
1190
+ // Create new subscription with shared callback dispatcher
1191
+ const callbacks = new Set([onEvent]);
1192
+ const subscriptionInfo = {
1193
+ callbacks,
1194
+ refCount: 1,
1195
+ subscription: null,
1196
+ };
1197
+
1198
+ // Store before creating subscription to handle race conditions
1199
+ activeSubscriptions.set(filterKey, subscriptionInfo);
1200
+
1201
+ const sub = this.pool.subscribeMany(
1202
+ this.relays,
1203
+ [filter],
1204
+ {
1205
+ onevent: (event) => {
1206
+ // CRITICAL: Filter out events from other authors immediately
1207
+ // Many relays don't respect the authors filter, so we must do it client-side
1208
+ if (filter.authors &amp;&amp; filter.authors.length > 0) {
1209
+ if (!filter.authors.includes(event.pubkey)) {
1210
+ // Silently reject events from other authors
1211
+ return;
1212
+ }
1213
+ }
1214
+
1215
+ this._cacheEvent(event);
1216
+
1217
+ // Dispatch to ALL registered callbacks for this subscription
1218
+ const subInfo = activeSubscriptions.get(filterKey);
1219
+ if (subInfo) {
1220
+ for (const cb of subInfo.callbacks) {
1221
+ try {
1222
+ cb(event);
1223
+ } catch (err) {
1224
+ console.warn('[nostr] Subscription callback error:', err.message);
1225
+ }
1226
+ }
1227
+ }
1228
+ },
1229
+ oneose: () => {
1230
+ if (options.onEOSE) options.onEOSE();
1231
+ },
1232
+ }
1233
+ );
1234
+
1235
+ // Store the actual subscription object
1236
+ subscriptionInfo.subscription = sub;
1237
+ this._subscriptions.set(subId, sub);
1238
+
1239
+ return {
1240
+ id: subId,
1241
+ unsubscribe: () => {
1242
+ callbacks.delete(onEvent);
1243
+ subscriptionInfo.refCount--;
1244
+
1245
+ // Only close actual subscription when no more callbacks
1246
+ if (subscriptionInfo.refCount === 0) {
1247
+ if (sub.close) sub.close();
1248
+ activeSubscriptions.delete(filterKey);
1249
+ }
1250
+ this._subscriptions.delete(subId);
1251
+ },
1252
+ };
1253
+ }
1254
+
1255
+ /**
1256
+ * Cache event in memory (synchronous version for loading)
1257
+ * @private
1258
+ */
1259
+ _cacheEventSync(event) {
1260
+ const key = event.id;
1261
+ this._eventCache.set(key, {
1262
+ events: [event],
1263
+ timestamp: Date.now(),
1264
+ });
1265
+
1266
+ // For replaceable events (kind 30000-39999), also cache by d-tag
1267
+ if (event.kind >= 30000 &amp;&amp; event.kind &lt; 40000) {
1268
+ const dTag = event.tags.find(t => t[0] === 'd');
1269
+ if (dTag &amp;&amp; dTag[1]) {
1270
+ const dTagKey = `d:${event.kind}:${dTag[1]}`;
1271
+ const existing = this._eventCache.get(dTagKey);
1272
+
1273
+ // Replace if newer, or if same timestamp but different event ID (for events in same second)
1274
+ const shouldReplace = !existing || !existing.events[0] ||
1275
+ event.created_at > existing.events[0].created_at ||
1276
+ (event.created_at === existing.events[0].created_at &amp;&amp; event.id !== existing.events[0].id);
1277
+
1278
+ if (shouldReplace) {
1279
+ this._eventCache.set(dTagKey, {
1280
+ events: [event],
1281
+ timestamp: Date.now(),
1282
+ });
1283
+
1284
+ // Remove old event from cache if it exists
1285
+ if (existing &amp;&amp; existing.events[0]) {
1286
+ this._eventCache.delete(existing.events[0].id);
1287
+ }
1288
+
1289
+ // Invalidate any query caches that might contain stale data for this d-tag
1290
+ // Query caches are JSON-stringified filters, so we look for any that match this event
1291
+ this._invalidateQueryCachesForEvent(event);
1292
+ }
1293
+ }
1294
+ }
1295
+ }
1296
+
1297
+ /**
1298
+ * Invalidate query caches that might be affected by a new event
1299
+ * Uses reverse index for O(1) lookup instead of O(n) scan
1300
+ * @private
1301
+ */
1302
+ _invalidateQueryCachesForEvent(event) {
1303
+ // Use reverse index for fast lookup - only check caches that could match this event's kind
1304
+ const indexedKeys = this._cacheIndex.get(event.kind);
1305
+ if (!indexedKeys || indexedKeys.size === 0) {
1306
+ return; // No cached queries for this kind
1307
+ }
1308
+
1309
+ const keysToDelete = [];
1310
+
1311
+ // Only iterate over cache entries that match the event's kind
1312
+ for (const cacheKey of indexedKeys) {
1313
+ const cached = this._eventCache.get(cacheKey);
1314
+ if (!cached) {
1315
+ // Cache entry was evicted, clean up index
1316
+ indexedKeys.delete(cacheKey);
1317
+ continue;
1318
+ }
1319
+
1320
+ try {
1321
+ const filter = JSON.parse(cacheKey);
1322
+ // Check if this event would match the filter
1323
+ if (this._eventMatchesFilter(event, filter)) {
1324
+ keysToDelete.push(cacheKey);
1325
+ }
1326
+ } catch {
1327
+ // Not a valid JSON key, clean up index
1328
+ indexedKeys.delete(cacheKey);
1329
+ }
1330
+ }
1331
+
1332
+ for (const key of keysToDelete) {
1333
+ this._eventCache.delete(key);
1334
+ this._unindexCacheEntry(key);
1335
+ }
1336
+ }
1337
+
1338
+ /**
1339
+ * Cache event in memory and persist (batched)
1340
+ * @private
1341
+ */
1342
+ async _cacheEvent(event) {
1343
+ // Cache in memory (synchronous - immediate for local-first reads)
1344
+ this._cacheEventSync(event);
1345
+
1346
+ // Queue for batched persistence (async - batches writes for I/O efficiency)
1347
+ if (this.persistentStorage) {
1348
+ // For replaceable events, use d-tag as key
1349
+ let storageKey = event.id;
1350
+ if (event.kind >= 30000 &amp;&amp; event.kind &lt; 40000) {
1351
+ const dTag = event.tags.find(t => t[0] === 'd');
1352
+ if (dTag &amp;&amp; dTag[1]) {
1353
+ storageKey = dTag[1]; // Use d-tag as key for replaceable events
1354
+ }
1355
+ }
1356
+
1357
+ // Queue for batched write (overwrites previous if same key)
1358
+ this._persistQueue.set(storageKey, event);
1359
+
1360
+ // Schedule batch flush if not already scheduled
1361
+ if (!this._persistTimer) {
1362
+ this._persistTimer = setTimeout(() => this._flushPersistQueue(), this._persistBatchMs);
1363
+ }
1364
+ }
1365
+
1366
+ // Trigger any active subscriptions that match this event
1367
+ if (this.relays.length === 0) {
1368
+ for (const sub of this._subscriptions.values()) {
1369
+ if (sub.active &amp;&amp; this._eventMatchesFilter(event, sub.filter)) {
1370
+ // Trigger callback asynchronously, but check active status again
1371
+ setTimeout(() => {
1372
+ if (sub.active) {
1373
+ sub.onEvent(event);
1374
+ }
1375
+ }, 10);
1376
+ }
1377
+ }
1378
+ }
1379
+ }
1380
+
1381
+ /**
1382
+ * Flush batched persistent writes
1383
+ * @private
1384
+ */
1385
+ async _flushPersistQueue() {
1386
+ this._persistTimer = null;
1387
+
1388
+ if (!this.persistentStorage || this._persistQueue.size === 0) {
1389
+ return;
1390
+ }
1391
+
1392
+ // Take snapshot of current queue and clear it
1393
+ const toWrite = Array.from(this._persistQueue.entries());
1394
+ this._persistQueue.clear();
1395
+
1396
+ // Write all queued events
1397
+ const writePromises = toWrite.map(async ([key, event]) => {
1398
+ try {
1399
+ await this.persistentStorage.put(key, event);
1400
+ } catch (error) {
1401
+ console.warn(`Failed to persist event ${key}:`, error.message);
1402
+ }
1403
+ });
1404
+
1405
+ // Wait for all writes to complete
1406
+ await Promise.all(writePromises);
1407
+ }
1408
+
1409
+ /**
1410
+ * Get cached events matching a filter
1411
+ * @private
1412
+ */
1413
+ _getMatchingCachedEvents(filter) {
1414
+ const matchingEvents = [];
1415
+ const seenDTags = new Map(); // For deduplicating replaceable events
1416
+
1417
+ for (const cached of this._eventCache.values()) {
1418
+ for (const event of (cached.events || [])) {
1419
+ if (this._eventMatchesFilter(event, filter)) {
1420
+ // For replaceable events (kind 30000-39999), keep only the newest
1421
+ if (event.kind >= 30000 &amp;&amp; event.kind &lt; 40000) {
1422
+ const dTag = event.tags.find(t => t[0] === 'd');
1423
+ if (dTag &amp;&amp; dTag[1]) {
1424
+ const dTagKey = `${event.kind}:${dTag[1]}`;
1425
+ const existing = seenDTags.get(dTagKey);
1426
+
1427
+ // Replace if newer, or if same timestamp with _deleted flag (tombstones)
1428
+ const shouldReplace = !existing ||
1429
+ event.created_at > existing.created_at ||
1430
+ (event.created_at === existing.created_at &amp;&amp;
1431
+ JSON.parse(event.content)?._deleted &amp;&amp;
1432
+ !JSON.parse(existing.content)?._deleted);
1433
+
1434
+ if (shouldReplace) {
1435
+ // Remove old event if exists
1436
+ if (existing) {
1437
+ const idx = matchingEvents.indexOf(existing);
1438
+ if (idx > -1) matchingEvents.splice(idx, 1);
1439
+ }
1440
+ seenDTags.set(dTagKey, event);
1441
+ matchingEvents.push(event);
1442
+ }
1443
+ continue;
1444
+ }
1445
+ }
1446
+
1447
+ // Regular events or replaceable events without d-tag
1448
+ matchingEvents.push(event);
1449
+ }
1450
+ }
1451
+ }
1452
+
1453
+ return matchingEvents;
1454
+ }
1455
+
1456
+ /**
1457
+ * Check if event matches filter
1458
+ * @private
1459
+ */
1460
+ _eventMatchesFilter(event, filter) {
1461
+ // Check kinds
1462
+ if (filter.kinds &amp;&amp; !filter.kinds.includes(event.kind)) {
1463
+ return false;
1464
+ }
1465
+
1466
+ // Check IDs
1467
+ if (filter.ids &amp;&amp; !filter.ids.includes(event.id)) {
1468
+ return false;
1469
+ }
1470
+
1471
+ // Check authors
1472
+ if (filter.authors &amp;&amp; !filter.authors.includes(event.pubkey)) {
1473
+ return false;
1474
+ }
1475
+
1476
+ // Check #d tag (for replaceable events)
1477
+ if (filter['#d']) {
1478
+ const dTag = event.tags.find(t => t[0] === 'd');
1479
+ if (!dTag || !filter['#d'].includes(dTag[1])) {
1480
+ return false;
1481
+ }
1482
+ }
1483
+
1484
+ // Check since/until
1485
+ if (filter.since &amp;&amp; event.created_at &lt; filter.since) {
1486
+ return false;
1487
+ }
1488
+ if (filter.until &amp;&amp; event.created_at > filter.until) {
1489
+ return false;
1490
+ }
1491
+
1492
+ return true;
1493
+ }
1494
+
1495
+ /**
1496
+ * Get single event by ID
1497
+ * @param {string} eventId - Event ID (hex)
1498
+ * @returns {Promise&lt;Object|null>} Event or null
1499
+ */
1500
+ async getEvent(eventId) {
1501
+ const events = await this.query({ ids: [eventId] });
1502
+ return events.length > 0 ? events[0] : null;
1503
+ }
1504
+
1505
+ /**
1506
+ * Clear the event cache (or specific entries matching a pattern)
1507
+ * @param {string} [pattern] - Optional d-tag pattern to match (clears all if not provided)
1508
+ */
1509
+ clearCache(pattern = null) {
1510
+ if (!pattern) {
1511
+ this._eventCache.clear();
1512
+ return;
1513
+ }
1514
+
1515
+ // Clear entries matching the pattern
1516
+ for (const key of this._eventCache.keys()) {
1517
+ if (key.includes(pattern)) {
1518
+ this._eventCache.delete(key);
1519
+ }
1520
+ }
1521
+ }
1522
+
1523
+ /**
1524
+ * Close all connections and subscriptions.
1525
+ *
1526
+ * @param {Object} [options={}] - Close options
1527
+ * @param {boolean} [options.flush=true] - Flush pending writes before closing
1528
+ * @returns {Promise&lt;void>}
1529
+ */
1530
+ async close(options = {}) {
1531
+ const shouldFlush = options.flush !== false;
1532
+
1533
+ // Flush pending persistent writes before closing
1534
+ if (shouldFlush &amp;&amp; this._persistTimer) {
1535
+ clearTimeout(this._persistTimer);
1536
+ await this._flushPersistQueue();
1537
+ }
1538
+
1539
+ // Stop background sync service
1540
+ if (this.syncService) {
1541
+ this.syncService.stop();
1542
+ }
1543
+
1544
+ // Close long-lived author subscription
1545
+ const authorSub = authorSubscriptions.get(this.publicKey);
1546
+ if (authorSub &amp;&amp; authorSub.subscription) {
1547
+ if (authorSub.subscription.close) {
1548
+ authorSub.subscription.close();
1549
+ }
1550
+ authorSubscriptions.delete(this.publicKey);
1551
+ }
1552
+
1553
+ // Close all subscriptions
1554
+ for (const sub of this._subscriptions.values()) {
1555
+ if (sub.close) {
1556
+ sub.close();
1557
+ } else if (sub.active !== undefined) {
1558
+ // Mock subscription
1559
+ sub.active = false;
1560
+ }
1561
+ }
1562
+ this._subscriptions.clear();
1563
+
1564
+ // Close pool
1565
+ this.pool.close(this.relays);
1566
+
1567
+ // Clear cache and index
1568
+ this._eventCache.clear();
1569
+ this._cacheIndex.clear();
1570
+ }
1571
+
1572
+ /**
1573
+ * Get relay status
1574
+ * @returns {Array} Relay connection statuses
1575
+ */
1576
+ getRelayStatus() {
1577
+ return this.relays.map(relay => ({
1578
+ url: relay,
1579
+ connected: true, // SimplePool manages this internally
1580
+ }));
1581
+ }
1582
+ }
1583
+
1584
+ /**
1585
+ * Create NostrClient instance.
1586
+ *
1587
+ * @param {Object} config - Configuration options
1588
+ * @returns {NostrClient} Client instance
1589
+ * @example
1590
+ * const client = createClient({
1591
+ * relays: ['wss://relay.example.com'],
1592
+ * appName: 'myapp'
1593
+ * });
1594
+ */
1595
+ export function createClient(config) {
1596
+ return new NostrClient(config);
1597
+ }
1598
+ </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>