holosphere 2.0.0-alpha1 → 2.0.0-alpha11

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