holosphere 2.0.0-alpha1 → 2.0.0-alpha10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +473 -0
- package/FEATURES.md +431 -0
- package/LICENSE +29 -166
- package/LICENSE-AGPL.md +180 -0
- package/README.md +97 -16
- package/dist/2019-D2OG2idw.js +6680 -0
- package/dist/2019-D2OG2idw.js.map +1 -0
- package/dist/2019-EION3wKo.cjs +8 -0
- package/dist/2019-EION3wKo.cjs.map +1 -0
- package/dist/_commonjsHelpers-C37NGDzP.cjs +2 -0
- package/dist/_commonjsHelpers-C37NGDzP.cjs.map +1 -0
- package/dist/_commonjsHelpers-CUmg6egw.js +7 -0
- package/dist/_commonjsHelpers-CUmg6egw.js.map +1 -0
- package/dist/browser-BSniCNqO.js +3058 -0
- package/dist/browser-BSniCNqO.js.map +1 -0
- package/dist/browser-Cq59Ij19.cjs +2 -0
- package/dist/browser-Cq59Ij19.cjs.map +1 -0
- package/dist/cdn/holosphere.min.js +55 -0
- package/dist/cdn/holosphere.min.js.map +1 -0
- package/dist/cjs/holosphere.cjs +2 -0
- package/dist/cjs/holosphere.cjs.map +1 -0
- package/dist/esm/holosphere.js +53 -0
- package/dist/esm/holosphere.js.map +1 -0
- package/dist/index-DDGt_V9o.cjs +12 -0
- package/dist/index-DDGt_V9o.cjs.map +1 -0
- package/dist/index-DJXftyvB.js +39841 -0
- package/dist/index-DJXftyvB.js.map +1 -0
- package/dist/index-DMbdcMtK.cjs +18 -0
- package/dist/index-DMbdcMtK.cjs.map +1 -0
- package/dist/index-DeZ1xz_s.js +15104 -0
- package/dist/index-DeZ1xz_s.js.map +1 -0
- package/dist/indexeddb-storage-BFt6hMeF.js +176 -0
- package/dist/indexeddb-storage-BFt6hMeF.js.map +1 -0
- package/dist/indexeddb-storage-BK5tv4Sh.cjs +2 -0
- package/dist/indexeddb-storage-BK5tv4Sh.cjs.map +1 -0
- package/dist/memory-storage-C9HuoL2E.js +91 -0
- package/dist/memory-storage-C9HuoL2E.js.map +1 -0
- package/dist/memory-storage-Dao7jfYG.cjs +2 -0
- package/dist/memory-storage-Dao7jfYG.cjs.map +1 -0
- package/dist/secp256k1-BbKzbLtD.cjs +12 -0
- package/dist/secp256k1-BbKzbLtD.cjs.map +1 -0
- package/dist/secp256k1-CreY7Pcl.js +1890 -0
- package/dist/secp256k1-CreY7Pcl.js.map +1 -0
- package/docs/CONTRACTS.md +797 -0
- package/docs/FOSDEM_PROPOSAL.md +388 -0
- package/docs/LOCALFIRST.md +266 -0
- package/docs/api/ai_aggregation.js.html +333 -0
- package/docs/api/ai_breakdown.js.html +524 -0
- package/docs/api/ai_classifier.js.html +231 -0
- package/docs/api/ai_council.js.html +246 -0
- package/docs/api/ai_embeddings.js.html +304 -0
- package/docs/api/ai_federation-ai.js.html +338 -0
- package/docs/api/ai_h3-ai.js.html +970 -0
- package/docs/api/ai_index.js.html +124 -0
- package/docs/api/ai_json-ops.js.html +241 -0
- package/docs/api/ai_llm-service.js.html +239 -0
- package/docs/api/ai_nl-query.js.html +236 -0
- package/docs/api/ai_relationships.js.html +367 -0
- package/docs/api/ai_schema-extractor.js.html +235 -0
- package/docs/api/ai_spatial.js.html +307 -0
- package/docs/api/ai_tts.js.html +214 -0
- package/docs/api/content_social-protocols.js.html +180 -0
- package/docs/api/core_holosphere.js.html +757 -0
- package/docs/api/crypto_nostr-utils.js.html +306 -0
- package/docs/api/crypto_secp256k1.js.html +267 -0
- package/docs/api/data/search.json +1 -0
- package/docs/api/federation_discovery.js.html +337 -0
- package/docs/api/federation_handshake.js.html +478 -0
- package/docs/api/federation_hologram.js.html +1053 -0
- package/docs/api/federation_registry.js.html +389 -0
- package/docs/api/fonts/Inconsolata-Regular.ttf +0 -0
- package/docs/api/fonts/OpenSans-Regular.ttf +0 -0
- package/docs/api/fonts/WorkSans-Bold.ttf +0 -0
- package/docs/api/global.html +3 -0
- package/docs/api/hierarchical_upcast.js.html +128 -0
- package/docs/api/index.html +265 -0
- package/docs/api/index.js.html +1868 -0
- package/docs/api/lib_ai-methods.js.html +660 -0
- package/docs/api/lib_contract-methods.js.html +445 -0
- package/docs/api/lib_errors.js.html +56 -0
- package/docs/api/lib_federation-methods.js.html +348 -0
- package/docs/api/lib_index.js.html +33 -0
- package/docs/api/module-ai.html +5 -0
- package/docs/api/module-ai_aggregation-SmartAggregation.html +6 -0
- package/docs/api/module-ai_aggregation.SmartAggregation.html +3 -0
- package/docs/api/module-ai_aggregation.html +3 -0
- package/docs/api/module-ai_breakdown-TaskBreakdown.html +5 -0
- package/docs/api/module-ai_breakdown.TaskBreakdown.html +3 -0
- package/docs/api/module-ai_breakdown.html +3 -0
- package/docs/api/module-ai_classifier-Classifier.html +6 -0
- package/docs/api/module-ai_classifier.Classifier.html +3 -0
- package/docs/api/module-ai_classifier.html +3 -0
- package/docs/api/module-ai_council-Council.html +6 -0
- package/docs/api/module-ai_council.Council.html +3 -0
- package/docs/api/module-ai_council.html +3 -0
- package/docs/api/module-ai_embeddings-Embeddings.html +5 -0
- package/docs/api/module-ai_embeddings.Embeddings.html +3 -0
- package/docs/api/module-ai_embeddings.html +3 -0
- package/docs/api/module-ai_federation-ai-FederationAdvisor.html +6 -0
- package/docs/api/module-ai_federation-ai.FederationAdvisor.html +3 -0
- package/docs/api/module-ai_federation-ai.html +3 -0
- package/docs/api/module-ai_h3-ai-H3AI.html +6 -0
- package/docs/api/module-ai_h3-ai.H3AI.html +3 -0
- package/docs/api/module-ai_h3-ai.html +3 -0
- package/docs/api/module-ai_json-ops-JSONOps.html +5 -0
- package/docs/api/module-ai_json-ops.JSONOps.html +3 -0
- package/docs/api/module-ai_json-ops.html +3 -0
- package/docs/api/module-ai_llm-service-LLMService.html +5 -0
- package/docs/api/module-ai_llm-service.LLMService.html +3 -0
- package/docs/api/module-ai_llm-service.html +3 -0
- package/docs/api/module-ai_nl-query-NLQuery.html +5 -0
- package/docs/api/module-ai_nl-query.NLQuery.html +3 -0
- package/docs/api/module-ai_nl-query.html +3 -0
- package/docs/api/module-ai_relationships-RelationshipDiscovery.html +6 -0
- package/docs/api/module-ai_relationships.RelationshipDiscovery.html +3 -0
- package/docs/api/module-ai_relationships.html +3 -0
- package/docs/api/module-ai_schema-extractor-SchemaExtractor.html +5 -0
- package/docs/api/module-ai_schema-extractor.SchemaExtractor.html +3 -0
- package/docs/api/module-ai_schema-extractor.html +3 -0
- package/docs/api/module-ai_spatial-SpatialAnalysis.html +6 -0
- package/docs/api/module-ai_spatial.SpatialAnalysis.html +3 -0
- package/docs/api/module-ai_spatial.html +3 -0
- package/docs/api/module-ai_tts-TTS.html +5 -0
- package/docs/api/module-ai_tts.TTS.html +3 -0
- package/docs/api/module-ai_tts.html +3 -0
- package/docs/api/module-content_social-protocols.html +3 -0
- package/docs/api/module-core_holosphere-HoloSphere.html +6 -0
- package/docs/api/module-core_holosphere.HoloSphere.html +3 -0
- package/docs/api/module-core_holosphere.html +3 -0
- package/docs/api/module-crypto_nostr-utils.html +3 -0
- package/docs/api/module-crypto_secp256k1.html +3 -0
- package/docs/api/module-federation_hologram.html +3 -0
- package/docs/api/module-hierarchical_upcast.html +3 -0
- package/docs/api/module-holosphere-HoloSphereBase.html +3 -0
- package/docs/api/module-holosphere.html +3 -0
- package/docs/api/module-lib_ai-methods.html +3 -0
- package/docs/api/module-lib_contract-methods.html +3 -0
- package/docs/api/module-lib_errors-AuthorizationError.html +3 -0
- package/docs/api/module-lib_errors-ValidationError.html +3 -0
- package/docs/api/module-lib_errors.AuthorizationError.html +3 -0
- package/docs/api/module-lib_errors.ValidationError.html +3 -0
- package/docs/api/module-lib_errors.html +3 -0
- package/docs/api/module-lib_federation-methods.html +3 -0
- package/docs/api/module-lib_index.html +3 -0
- package/docs/api/module-schema_validator-ValidationError.html +3 -0
- package/docs/api/module-schema_validator.ValidationError.html +3 -0
- package/docs/api/module-schema_validator.html +3 -0
- package/docs/api/module-spatial_h3-operations.html +4 -0
- package/docs/api/module-storage_backend-factory.BackendFactory.html +3 -0
- package/docs/api/module-storage_backend-factory.html +3 -0
- package/docs/api/module-storage_backend-interface-StorageBackend.html +3 -0
- package/docs/api/module-storage_backend-interface.StorageBackend.html +3 -0
- package/docs/api/module-storage_backend-interface.html +3 -0
- package/docs/api/module-storage_backends_activitypub-backend-ActivityPubBackend.html +7 -0
- package/docs/api/module-storage_backends_activitypub-backend.ActivityPubBackend.html +3 -0
- package/docs/api/module-storage_backends_activitypub-backend.html +3 -0
- package/docs/api/module-storage_backends_activitypub_server-ActivityPubServer.html +8 -0
- package/docs/api/module-storage_backends_activitypub_server.ActivityPubServer.html +3 -0
- package/docs/api/module-storage_backends_activitypub_server.html +3 -0
- package/docs/api/module-storage_backends_gundb-backend-GunDBBackend.html +7 -0
- package/docs/api/module-storage_backends_gundb-backend.GunDBBackend.html +3 -0
- package/docs/api/module-storage_backends_gundb-backend.html +3 -0
- package/docs/api/module-storage_backends_nostr-backend-NostrBackend.html +8 -0
- package/docs/api/module-storage_backends_nostr-backend.NostrBackend.html +3 -0
- package/docs/api/module-storage_backends_nostr-backend.html +3 -0
- package/docs/api/module-storage_filesystem-storage-FileSystemStorage.html +5 -0
- package/docs/api/module-storage_filesystem-storage-browser-FileSystemStorage.html +3 -0
- package/docs/api/module-storage_filesystem-storage-browser.FileSystemStorage.html +3 -0
- package/docs/api/module-storage_filesystem-storage-browser.html +3 -0
- package/docs/api/module-storage_filesystem-storage.FileSystemStorage.html +3 -0
- package/docs/api/module-storage_filesystem-storage.html +3 -0
- package/docs/api/module-storage_global-tables.html +3 -0
- package/docs/api/module-storage_gun-async.html +3 -0
- package/docs/api/module-storage_gun-auth-GunAuth.html +5 -0
- package/docs/api/module-storage_gun-auth.GunAuth.html +3 -0
- package/docs/api/module-storage_gun-auth.html +3 -0
- package/docs/api/module-storage_gun-federation.html +3 -0
- package/docs/api/module-storage_gun-references-GunReferenceHandler.html +5 -0
- package/docs/api/module-storage_gun-references.GunReferenceHandler.html +3 -0
- package/docs/api/module-storage_gun-references.html +3 -0
- package/docs/api/module-storage_gun-schema-GunSchemaValidator.html +5 -0
- package/docs/api/module-storage_gun-schema.GunSchemaValidator.html +3 -0
- package/docs/api/module-storage_gun-schema.html +3 -0
- package/docs/api/module-storage_gun-wrapper.html +11 -0
- package/docs/api/module-storage_indexeddb-storage-IndexedDBStorage.html +5 -0
- package/docs/api/module-storage_indexeddb-storage.IndexedDBStorage.html +3 -0
- package/docs/api/module-storage_indexeddb-storage.html +3 -0
- package/docs/api/module-storage_key-storage-simple.html +3 -0
- package/docs/api/module-storage_key-storage.html +4 -0
- package/docs/api/module-storage_memory-storage-MemoryStorage.html +5 -0
- package/docs/api/module-storage_memory-storage.MemoryStorage.html +3 -0
- package/docs/api/module-storage_memory-storage.html +3 -0
- package/docs/api/module-storage_migration-MigrationTool.html +6 -0
- package/docs/api/module-storage_migration.MigrationTool.html +3 -0
- package/docs/api/module-storage_migration.html +3 -0
- package/docs/api/module-storage_nostr-async.html +18 -0
- package/docs/api/module-storage_nostr-client-LRUCache.html +3 -0
- package/docs/api/module-storage_nostr-client-NostrClient.html +7 -0
- package/docs/api/module-storage_nostr-client.NostrClient.html +15 -0
- package/docs/api/module-storage_nostr-client.html +6 -0
- package/docs/api/module-storage_nostr-wrapper.html +3 -0
- package/docs/api/module-storage_outbox-queue-OutboxQueue.html +4 -0
- package/docs/api/module-storage_outbox-queue.OutboxQueue.html +3 -0
- package/docs/api/module-storage_outbox-queue.html +3 -0
- package/docs/api/module-storage_persistent-storage-PersistentStorage.html +3 -0
- package/docs/api/module-storage_persistent-storage.html +4 -0
- package/docs/api/module-storage_sync-service-SyncService.html +5 -0
- package/docs/api/module-storage_sync-service.SyncService.html +3 -0
- package/docs/api/module-storage_sync-service.html +3 -0
- package/docs/api/module-storage_unified-storage.html +3 -0
- package/docs/api/module-subscriptions_manager.SubscriptionRegistry.html +3 -0
- package/docs/api/module-subscriptions_manager.html +3 -0
- package/docs/api/schema_validator.js.html +113 -0
- package/docs/api/scripts/core.js +726 -0
- package/docs/api/scripts/core.min.js +23 -0
- package/docs/api/scripts/resize.js +90 -0
- package/docs/api/scripts/search.js +265 -0
- package/docs/api/scripts/search.min.js +6 -0
- package/docs/api/scripts/third-party/Apache-License-2.0.txt +202 -0
- package/docs/api/scripts/third-party/fuse.js +9 -0
- package/docs/api/scripts/third-party/hljs-line-num-original.js +369 -0
- package/docs/api/scripts/third-party/hljs-line-num.js +1 -0
- package/docs/api/scripts/third-party/hljs-original.js +5171 -0
- package/docs/api/scripts/third-party/hljs.js +1 -0
- package/docs/api/scripts/third-party/popper.js +5 -0
- package/docs/api/scripts/third-party/tippy.js +1 -0
- package/docs/api/scripts/third-party/tocbot.js +672 -0
- package/docs/api/scripts/third-party/tocbot.min.js +1 -0
- package/docs/api/spatial_h3-operations.js.html +129 -0
- package/docs/api/storage_backend-factory.js.html +133 -0
- package/docs/api/storage_backend-interface.js.html +164 -0
- package/docs/api/storage_backends_activitypub-backend.js.html +298 -0
- package/docs/api/storage_backends_activitypub_server.js.html +678 -0
- package/docs/api/storage_backends_gundb-backend.js.html +878 -0
- package/docs/api/storage_backends_nostr-backend.js.html +254 -0
- package/docs/api/storage_filesystem-storage-browser.js.html +83 -0
- package/docs/api/storage_filesystem-storage.js.html +207 -0
- package/docs/api/storage_global-tables.js.html +116 -0
- package/docs/api/storage_gun-async.js.html +344 -0
- package/docs/api/storage_gun-auth.js.html +376 -0
- package/docs/api/storage_gun-federation.js.html +788 -0
- package/docs/api/storage_gun-references.js.html +212 -0
- package/docs/api/storage_gun-schema.js.html +309 -0
- package/docs/api/storage_gun-wrapper.js.html +645 -0
- package/docs/api/storage_indexeddb-storage.js.html +224 -0
- package/docs/api/storage_key-storage-simple.js.html +102 -0
- package/docs/api/storage_key-storage.js.html +171 -0
- package/docs/api/storage_memory-storage.js.html +128 -0
- package/docs/api/storage_migration.js.html +354 -0
- package/docs/api/storage_nostr-async.js.html +1076 -0
- package/docs/api/storage_nostr-client.js.html +1598 -0
- package/docs/api/storage_nostr-wrapper.js.html +218 -0
- package/docs/api/storage_outbox-queue.js.html +248 -0
- package/docs/api/storage_persistent-storage.js.html +160 -0
- package/docs/api/storage_sync-service.js.html +201 -0
- package/docs/api/storage_unified-storage.js.html +157 -0
- package/docs/api/styles/clean-jsdoc-theme-base.css +1159 -0
- package/docs/api/styles/clean-jsdoc-theme-dark.css +412 -0
- package/docs/api/styles/clean-jsdoc-theme-light.css +482 -0
- package/docs/api/styles/clean-jsdoc-theme-scrollbar.css +30 -0
- package/docs/api/styles/clean-jsdoc-theme-without-scrollbar.min.css +1 -0
- package/docs/api/styles/clean-jsdoc-theme.min.css +1 -0
- package/docs/api/subscriptions_manager.js.html +162 -0
- package/docs/contracts/api-interface.md +793 -0
- package/docs/data-model.md +476 -0
- package/docs/gun-async-usage.md +338 -0
- package/docs/plan.md +349 -0
- package/docs/quickstart.md +674 -0
- package/docs/research.md +362 -0
- package/docs/spec.md +244 -0
- package/docs/storage-backends.md +326 -0
- package/docs/tasks.md +947 -0
- package/examples/demo.html +47 -0
- package/examples/holosphere-widget.js +1242 -0
- package/examples/widget-demo.html +274 -0
- package/examples/widget.html +703 -0
- package/jsdoc.json +26 -0
- package/package.json +25 -7
- package/src/ai/aggregation.js +13 -2
- package/src/ai/breakdown.js +12 -2
- package/src/ai/classifier.js +14 -3
- package/src/ai/council.js +22 -7
- package/src/ai/embeddings.js +37 -15
- package/src/ai/federation-ai.js +13 -2
- package/src/ai/h3-ai.js +14 -2
- package/src/ai/index.js +16 -7
- package/src/ai/json-ops.js +18 -5
- package/src/ai/llm-service.js +62 -31
- package/src/ai/nl-query.js +12 -2
- package/src/ai/relationships.js +13 -2
- package/src/ai/schema-extractor.js +24 -10
- package/src/ai/spatial.js +13 -2
- package/src/ai/tts.js +25 -8
- package/src/cdn-entry.js +22 -0
- package/src/content/social-protocols.js +34 -25
- package/src/contracts/abis/Appreciative.json +1280 -0
- package/src/contracts/abis/AppreciativeFactory.json +101 -0
- package/src/contracts/abis/Bundle.json +1438 -0
- package/src/contracts/abis/BundleFactory.json +106 -0
- package/src/contracts/abis/Holon.json +881 -0
- package/src/contracts/abis/Holons.json +330 -0
- package/src/contracts/abis/Managed.json +1262 -0
- package/src/contracts/abis/ManagedFactory.json +149 -0
- package/src/contracts/abis/Membrane.json +261 -0
- package/src/contracts/abis/Splitter.json +1624 -0
- package/src/contracts/abis/SplitterFactory.json +220 -0
- package/src/contracts/abis/TestToken.json +321 -0
- package/src/contracts/abis/Zoned.json +1461 -0
- package/src/contracts/abis/ZonedFactory.json +154 -0
- package/src/contracts/chain-manager.js +403 -0
- package/src/contracts/deployer.js +500 -0
- package/src/contracts/event-listener.js +539 -0
- package/src/contracts/holon-contracts.js +359 -0
- package/src/contracts/index.js +82 -0
- package/src/contracts/networks.js +229 -0
- package/src/contracts/operations.js +687 -0
- package/src/contracts/queries.js +638 -0
- package/src/core/holosphere.js +487 -6
- package/src/crypto/nostr-utils.js +303 -0
- package/src/crypto/secp256k1.js +7 -2
- package/src/federation/handshake.js +475 -0
- package/src/federation/hologram.js +117 -3
- package/src/hierarchical/upcast.js +40 -25
- package/src/index.js +1501 -1909
- package/src/lib/ai-methods.js +657 -0
- package/src/lib/contract-methods.js +442 -0
- package/src/lib/errors.js +53 -0
- package/src/lib/federation-methods.js +345 -0
- package/src/lib/index.js +30 -0
- package/src/schema/validator.js +22 -3
- package/src/spatial/h3-operations.js +19 -3
- package/src/storage/backend-factory.js +7 -2
- package/src/storage/backend-interface.js +21 -2
- package/src/storage/backends/activitypub/server.js +25 -3
- package/src/storage/backends/activitypub-backend.js +25 -2
- package/src/storage/backends/gundb-backend.js +692 -50
- package/src/storage/backends/nostr-backend.js +116 -1
- package/src/storage/filesystem-storage-browser.js +42 -2
- package/src/storage/filesystem-storage.js +72 -5
- package/src/storage/global-tables.js +35 -3
- package/src/storage/gun-async.js +75 -15
- package/src/storage/gun-auth.js +373 -0
- package/src/storage/gun-federation.js +785 -0
- package/src/storage/gun-references.js +209 -0
- package/src/storage/gun-schema.js +306 -0
- package/src/storage/gun-wrapper.js +475 -54
- package/src/storage/indexeddb-storage.js +112 -13
- package/src/storage/key-storage-simple.js +32 -9
- package/src/storage/key-storage.js +45 -13
- package/src/storage/memory-storage.js +68 -2
- package/src/storage/migration.js +20 -7
- package/src/storage/nostr-async.js +412 -122
- package/src/storage/nostr-client.js +749 -76
- package/src/storage/nostr-wrapper.js +6 -2
- package/src/storage/outbox-queue.js +55 -18
- package/src/storage/persistent-storage.js +62 -14
- package/src/storage/sync-service.js +51 -17
- package/src/storage/unified-storage.js +154 -0
- package/src/subscriptions/manager.js +34 -17
- package/types/index.d.ts +133 -0
- package/vite.config.cdn.js +60 -0
- package/tests/unit/ai/aggregation.test.js +0 -295
- package/tests/unit/ai/breakdown.test.js +0 -446
- package/tests/unit/ai/classifier.test.js +0 -294
- package/tests/unit/ai/council.test.js +0 -262
- package/tests/unit/ai/embeddings.test.js +0 -384
- package/tests/unit/ai/federation-ai.test.js +0 -344
- package/tests/unit/ai/h3-ai.test.js +0 -458
- package/tests/unit/ai/index.test.js +0 -304
- package/tests/unit/ai/json-ops.test.js +0 -307
- package/tests/unit/ai/llm-service.test.js +0 -390
- package/tests/unit/ai/nl-query.test.js +0 -383
- package/tests/unit/ai/relationships.test.js +0 -311
- package/tests/unit/ai/schema-extractor.test.js +0 -384
- package/tests/unit/ai/spatial.test.js +0 -279
- package/tests/unit/ai/tts.test.js +0 -279
- package/tests/unit/content.test.js +0 -332
- package/tests/unit/contract/core.test.js +0 -88
- package/tests/unit/contract/crypto.test.js +0 -198
- package/tests/unit/contract/data.test.js +0 -223
- package/tests/unit/contract/federation.test.js +0 -181
- package/tests/unit/contract/hierarchical.test.js +0 -113
- package/tests/unit/contract/schema.test.js +0 -114
- package/tests/unit/contract/social.test.js +0 -217
- package/tests/unit/contract/spatial.test.js +0 -110
- package/tests/unit/contract/subscriptions.test.js +0 -128
- package/tests/unit/contract/utils.test.js +0 -159
- package/tests/unit/core.test.js +0 -152
- package/tests/unit/crypto.test.js +0 -328
- package/tests/unit/federation.test.js +0 -234
- package/tests/unit/gun-async.test.js +0 -252
- package/tests/unit/hierarchical.test.js +0 -399
- package/tests/unit/integration/scenario-01-geographic-storage.test.js +0 -74
- package/tests/unit/integration/scenario-02-federation.test.js +0 -76
- package/tests/unit/integration/scenario-03-subscriptions.test.js +0 -102
- package/tests/unit/integration/scenario-04-validation.test.js +0 -129
- package/tests/unit/integration/scenario-05-hierarchy.test.js +0 -125
- package/tests/unit/integration/scenario-06-social.test.js +0 -135
- package/tests/unit/integration/scenario-07-persistence.test.js +0 -130
- package/tests/unit/integration/scenario-08-authorization.test.js +0 -161
- package/tests/unit/integration/scenario-09-cross-dimensional.test.js +0 -139
- package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +0 -357
- package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +0 -410
- package/tests/unit/integration/scenario-12-capability-federated-read.test.js +0 -719
- package/tests/unit/performance/benchmark.test.js +0 -85
- package/tests/unit/schema.test.js +0 -213
- package/tests/unit/spatial.test.js +0 -158
- package/tests/unit/storage.test.js +0 -195
- package/tests/unit/subscriptions.test.js +0 -328
- package/tests/unit/test-data-permanence-debug.js +0 -197
- package/tests/unit/test-data-permanence.js +0 -340
- package/tests/unit/test-key-persistence-fixed.js +0 -148
- package/tests/unit/test-key-persistence.js +0 -172
- package/tests/unit/test-relay-permanence.js +0 -376
- package/tests/unit/test-second-node.js +0 -95
- package/tests/unit/test-simple-write.js +0 -89
- /package/{cleanup-test-data.js → scripts/cleanup-test-data.js} +0 -0
- /package/{test-ai-real-api.js → scripts/test-ai-real-api.js} +0 -0
|
@@ -1,21 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Nostr Async Utilities
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Nostr Async Utilities.
|
|
3
|
+
*
|
|
4
|
+
* Provides Promise-based wrappers and async patterns for Nostr operations.
|
|
5
|
+
* Includes local-first data access, query deduplication, subscription management,
|
|
6
|
+
* and background refresh capabilities for optimal performance.
|
|
7
|
+
*
|
|
8
|
+
* @module storage/nostr-async
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
|
-
* Global subscription manager to prevent duplicate subscriptions
|
|
8
|
-
* Maps:
|
|
12
|
+
* Global subscription manager to prevent duplicate subscriptions.
|
|
13
|
+
* Maps: subscriptionKey -> subscription object
|
|
14
|
+
* @private
|
|
9
15
|
*/
|
|
10
16
|
const globalSubscriptions = new Map();
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
|
-
*
|
|
19
|
+
* Single-path subscription manager (for nostrSubscribe).
|
|
20
|
+
* Maps: subscriptionKey -> { subscription, callbacks: [] }
|
|
21
|
+
* @private
|
|
22
|
+
*/
|
|
23
|
+
const singlePathSubscriptions = new Map();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Query deduplication for nostrGet - prevents duplicate relay queries.
|
|
27
|
+
* Maps: queryKey -> { promise, timestamp, callbacks: [] }
|
|
28
|
+
* @private
|
|
29
|
+
*/
|
|
30
|
+
const pendingQueries = new Map();
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Time window for query deduplication (2 seconds).
|
|
34
|
+
* @private
|
|
35
|
+
* @constant {number}
|
|
36
|
+
*/
|
|
37
|
+
const QUERY_DEDUP_WINDOW = 2000;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Write data as Nostr event (parameterized replaceable event).
|
|
41
|
+
*
|
|
14
42
|
* @param {Object} client - NostrClient instance
|
|
15
43
|
* @param {string} path - Path identifier (encoded in d-tag)
|
|
16
44
|
* @param {Object} data - Data to store
|
|
17
|
-
* @param {number} kind - Event kind (default: 30000 for parameterized replaceable)
|
|
18
|
-
* @returns {Promise<Object>} Published event result
|
|
45
|
+
* @param {number} [kind=30000] - Event kind (default: 30000 for parameterized replaceable)
|
|
46
|
+
* @returns {Promise<Object>} Published event result with relay responses
|
|
47
|
+
* @example
|
|
48
|
+
* const result = await nostrPut(client, 'myapp/holon123/items/item1', { name: 'Test' });
|
|
49
|
+
* console.log(result.event.id); // Event ID
|
|
19
50
|
*/
|
|
20
51
|
export async function nostrPut(client, path, data, kind = 30000) {
|
|
21
52
|
const dataEvent = {
|
|
@@ -32,52 +63,137 @@ export async function nostrPut(client, path, data, kind = 30000) {
|
|
|
32
63
|
}
|
|
33
64
|
|
|
34
65
|
/**
|
|
35
|
-
* Read data from Nostr (query by d-tag)
|
|
36
|
-
*
|
|
66
|
+
* Read data from Nostr (query by d-tag).
|
|
67
|
+
*
|
|
68
|
+
* LOCAL-FIRST: Checks persistent storage first, never blocks on network.
|
|
69
|
+
* Uses query deduplication to prevent duplicate relay queries within a time window.
|
|
70
|
+
*
|
|
37
71
|
* @param {Object} client - NostrClient instance
|
|
38
72
|
* @param {string} path - Path identifier
|
|
39
|
-
* @param {number} kind - Event kind (default: 30000)
|
|
40
|
-
* @param {Object} options - Query options
|
|
41
|
-
* @param {string[]} options.authors - Array of public keys to query (default: [client.publicKey])
|
|
42
|
-
* @param {boolean} options.includeAuthor - If true, adds _author field to returned data
|
|
43
|
-
* @param {boolean} options.skipPersistent - If true, skip persistent storage check
|
|
73
|
+
* @param {number} [kind=30000] - Event kind (default: 30000)
|
|
74
|
+
* @param {Object} [options={}] - Query options
|
|
75
|
+
* @param {string[]} [options.authors] - Array of public keys to query (default: [client.publicKey])
|
|
76
|
+
* @param {boolean} [options.includeAuthor=false] - If true, adds _author field to returned data
|
|
77
|
+
* @param {boolean} [options.skipPersistent=false] - If true, skip persistent storage check
|
|
78
|
+
* @param {number} [options.timeout=30000] - Query timeout in milliseconds
|
|
44
79
|
* @returns {Promise<Object|null>} Data or null if not found
|
|
80
|
+
* @example
|
|
81
|
+
* const data = await nostrGet(client, 'myapp/holon1/items/item1');
|
|
82
|
+
* if (data) {
|
|
83
|
+
* console.log(data.name);
|
|
84
|
+
* }
|
|
45
85
|
*/
|
|
46
86
|
export async function nostrGet(client, path, kind = 30000, options = {}) {
|
|
47
87
|
const timeout = options.timeout !== undefined ? options.timeout : 30000;
|
|
48
88
|
const authors = options.authors || [client.publicKey];
|
|
49
89
|
|
|
50
|
-
//
|
|
90
|
+
// CHECK LRU CACHE FIRST (always up-to-date, even before batched persist)
|
|
91
|
+
// This ensures immediate reads after writes return fresh data
|
|
92
|
+
if (!options.skipCache && client.getCachedByPath) {
|
|
93
|
+
const cachedEvent = client.getCachedByPath(path, kind);
|
|
94
|
+
if (cachedEvent && cachedEvent.content) {
|
|
95
|
+
// Verify author is in requested authors list
|
|
96
|
+
if (authors.includes(cachedEvent.pubkey)) {
|
|
97
|
+
try {
|
|
98
|
+
const data = JSON.parse(cachedEvent.content);
|
|
99
|
+
|
|
100
|
+
// Skip null/undefined or deleted items
|
|
101
|
+
if (!data || data._deleted) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Optionally include author information
|
|
106
|
+
if (options.includeAuthor) {
|
|
107
|
+
data._author = cachedEvent.pubkey;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return data;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
// Fall through if parsing fails
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// LOCAL-FIRST: Check persistent storage (may be slightly stale due to batched writes)
|
|
51
119
|
if (!options.skipPersistent && client.persistentGet) {
|
|
52
120
|
const persistedEvent = await client.persistentGet(path);
|
|
53
121
|
if (persistedEvent && persistedEvent.content) {
|
|
54
|
-
|
|
55
|
-
|
|
122
|
+
// Verify author is in requested authors list (persistent storage may have cached events from other authors)
|
|
123
|
+
if (!authors.includes(persistedEvent.pubkey)) {
|
|
124
|
+
// Author mismatch - fall through to relay query
|
|
125
|
+
} else {
|
|
126
|
+
try {
|
|
127
|
+
const data = JSON.parse(persistedEvent.content);
|
|
56
128
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
129
|
+
// Skip null/undefined or deleted items
|
|
130
|
+
if (!data || data._deleted) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
61
133
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
134
|
+
// Optionally include author information
|
|
135
|
+
if (options.includeAuthor) {
|
|
136
|
+
data._author = persistedEvent.pubkey;
|
|
137
|
+
}
|
|
66
138
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
139
|
+
// Trigger background refresh from relays (fire-and-forget)
|
|
140
|
+
if (client.refreshPathInBackground) {
|
|
141
|
+
client.refreshPathInBackground(path, kind, { authors, timeout });
|
|
142
|
+
}
|
|
71
143
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
144
|
+
return data;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
// Fall through to relay query if parsing fails
|
|
147
|
+
console.warn('[nostrGet] Failed to parse persisted event:', error);
|
|
148
|
+
}
|
|
76
149
|
}
|
|
77
150
|
}
|
|
78
151
|
}
|
|
79
152
|
|
|
80
|
-
//
|
|
153
|
+
// Query deduplication: Check if same query is already pending
|
|
154
|
+
const queryKey = `get:${client.publicKey}:${kind}:${path}:${authors.join(',')}`;
|
|
155
|
+
const pending = pendingQueries.get(queryKey);
|
|
156
|
+
|
|
157
|
+
if (pending && Date.now() - pending.timestamp < QUERY_DEDUP_WINDOW) {
|
|
158
|
+
// Reuse pending query result
|
|
159
|
+
return pending.promise;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Create new query with deduplication
|
|
163
|
+
const queryPromise = _executeNostrGet(client, path, kind, authors, timeout, options);
|
|
164
|
+
|
|
165
|
+
pendingQueries.set(queryKey, {
|
|
166
|
+
promise: queryPromise,
|
|
167
|
+
timestamp: Date.now(),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Clean up after promise resolves
|
|
171
|
+
queryPromise.finally(() => {
|
|
172
|
+
// Remove from pending after a short delay to allow coalescing
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
const current = pendingQueries.get(queryKey);
|
|
175
|
+
if (current && current.promise === queryPromise) {
|
|
176
|
+
pendingQueries.delete(queryKey);
|
|
177
|
+
}
|
|
178
|
+
}, QUERY_DEDUP_WINDOW);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return queryPromise;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Internal function to execute nostrGet query.
|
|
186
|
+
*
|
|
187
|
+
* @private
|
|
188
|
+
* @param {Object} client - NostrClient instance
|
|
189
|
+
* @param {string} path - Path identifier
|
|
190
|
+
* @param {number} kind - Event kind
|
|
191
|
+
* @param {string[]} authors - Array of author public keys
|
|
192
|
+
* @param {number} timeout - Query timeout
|
|
193
|
+
* @param {Object} options - Query options
|
|
194
|
+
* @returns {Promise<Object|null>} Data or null
|
|
195
|
+
*/
|
|
196
|
+
async function _executeNostrGet(client, path, kind, authors, timeout, options) {
|
|
81
197
|
const filter = {
|
|
82
198
|
kinds: [kind],
|
|
83
199
|
authors: authors, // Support multiple authors for cross-holosphere queries
|
|
@@ -87,37 +203,52 @@ export async function nostrGet(client, path, kind = 30000, options = {}) {
|
|
|
87
203
|
|
|
88
204
|
const events = await client.query(filter, { timeout });
|
|
89
205
|
|
|
90
|
-
|
|
206
|
+
// Filter by author (relays may not respect authors filter)
|
|
207
|
+
const authoredEvents = events.filter(event => authors.includes(event.pubkey));
|
|
208
|
+
|
|
209
|
+
if (authoredEvents.length === 0) {
|
|
91
210
|
return null;
|
|
92
211
|
}
|
|
93
212
|
|
|
94
|
-
// Get most recent event (across
|
|
95
|
-
const event =
|
|
213
|
+
// Get most recent event (across allowed authors)
|
|
214
|
+
const event = authoredEvents.sort((a, b) => b.created_at - a.created_at)[0];
|
|
96
215
|
|
|
97
216
|
try {
|
|
98
217
|
const data = JSON.parse(event.content);
|
|
218
|
+
// Skip null/undefined or deleted items
|
|
219
|
+
if (!data || data._deleted) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
99
222
|
// Optionally include author information
|
|
100
223
|
if (options.includeAuthor) {
|
|
101
224
|
data._author = event.pubkey;
|
|
102
225
|
}
|
|
103
226
|
return data;
|
|
104
227
|
} catch (error) {
|
|
105
|
-
|
|
228
|
+
// Silent - return null for unparseable events
|
|
106
229
|
return null;
|
|
107
230
|
}
|
|
108
231
|
}
|
|
109
232
|
|
|
110
233
|
/**
|
|
111
|
-
* Query all events under a path prefix
|
|
112
|
-
*
|
|
234
|
+
* Query all events under a path prefix.
|
|
235
|
+
*
|
|
236
|
+
* LOCAL-FIRST: Checks persistent storage first, never blocks on network.
|
|
237
|
+
* Uses query deduplication to prevent duplicate relay queries within a time window.
|
|
238
|
+
*
|
|
113
239
|
* @param {Object} client - NostrClient instance
|
|
114
240
|
* @param {string} pathPrefix - Path prefix to match
|
|
115
|
-
* @param {number} kind - Event kind (default: 30000)
|
|
116
|
-
* @param {Object} options - Query options
|
|
117
|
-
* @param {string[]} options.authors - Array of public keys to query (default: [client.publicKey])
|
|
118
|
-
* @param {boolean} options.includeAuthor - If true, adds _author field to returned data
|
|
119
|
-
* @param {boolean} options.skipPersistent - If true, skip persistent storage check
|
|
241
|
+
* @param {number} [kind=30000] - Event kind (default: 30000)
|
|
242
|
+
* @param {Object} [options={}] - Query options
|
|
243
|
+
* @param {string[]} [options.authors] - Array of public keys to query (default: [client.publicKey])
|
|
244
|
+
* @param {boolean} [options.includeAuthor=false] - If true, adds _author field to returned data
|
|
245
|
+
* @param {boolean} [options.skipPersistent=false] - If true, skip persistent storage check
|
|
246
|
+
* @param {number} [options.timeout=30000] - Query timeout in milliseconds
|
|
247
|
+
* @param {number} [options.limit=1000] - Maximum number of events to retrieve
|
|
120
248
|
* @returns {Promise<Array>} Array of data objects
|
|
249
|
+
* @example
|
|
250
|
+
* const items = await nostrGetAll(client, 'myapp/holon1/items/');
|
|
251
|
+
* console.log(`Found ${items.length} items`);
|
|
121
252
|
*/
|
|
122
253
|
export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}) {
|
|
123
254
|
const timeout = options.timeout !== undefined ? options.timeout : 30000;
|
|
@@ -133,6 +264,9 @@ export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}
|
|
|
133
264
|
for (const event of persistedEvents) {
|
|
134
265
|
if (!event || !event.tags) continue;
|
|
135
266
|
|
|
267
|
+
// Verify author is in requested authors list (persistent storage may have cached events from other authors)
|
|
268
|
+
if (!authors.includes(event.pubkey)) continue;
|
|
269
|
+
|
|
136
270
|
const dTag = event.tags.find(t => t[0] === 'd');
|
|
137
271
|
if (!dTag || !dTag[1] || !dTag[1].startsWith(pathPrefix)) continue;
|
|
138
272
|
|
|
@@ -143,8 +277,11 @@ export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}
|
|
|
143
277
|
try {
|
|
144
278
|
const data = JSON.parse(event.content);
|
|
145
279
|
|
|
146
|
-
//
|
|
147
|
-
if (data._deleted)
|
|
280
|
+
// Handle null/undefined or deleted items - remove from map if this is newer
|
|
281
|
+
if (!data || data._deleted) {
|
|
282
|
+
byPath.delete(path);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
148
285
|
|
|
149
286
|
// Optionally include author information
|
|
150
287
|
if (options.includeAuthor) {
|
|
@@ -167,7 +304,50 @@ export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}
|
|
|
167
304
|
}
|
|
168
305
|
}
|
|
169
306
|
|
|
170
|
-
//
|
|
307
|
+
// Query deduplication: Check if same query is already pending
|
|
308
|
+
const queryKey = `getAll:${client.publicKey}:${kind}:${pathPrefix}:${authors.join(',')}`;
|
|
309
|
+
const pending = pendingQueries.get(queryKey);
|
|
310
|
+
|
|
311
|
+
if (pending && Date.now() - pending.timestamp < QUERY_DEDUP_WINDOW) {
|
|
312
|
+
// Reuse pending query result
|
|
313
|
+
return pending.promise;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Create new query with deduplication
|
|
317
|
+
const queryPromise = _executeNostrGetAll(client, pathPrefix, kind, authors, timeout, limit, options);
|
|
318
|
+
|
|
319
|
+
pendingQueries.set(queryKey, {
|
|
320
|
+
promise: queryPromise,
|
|
321
|
+
timestamp: Date.now(),
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Clean up after promise resolves
|
|
325
|
+
queryPromise.finally(() => {
|
|
326
|
+
setTimeout(() => {
|
|
327
|
+
const current = pendingQueries.get(queryKey);
|
|
328
|
+
if (current && current.promise === queryPromise) {
|
|
329
|
+
pendingQueries.delete(queryKey);
|
|
330
|
+
}
|
|
331
|
+
}, QUERY_DEDUP_WINDOW);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
return queryPromise;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Internal function to execute nostrGetAll query.
|
|
339
|
+
*
|
|
340
|
+
* @private
|
|
341
|
+
* @param {Object} client - NostrClient instance
|
|
342
|
+
* @param {string} pathPrefix - Path prefix to match
|
|
343
|
+
* @param {number} kind - Event kind
|
|
344
|
+
* @param {string[]} authors - Array of author public keys
|
|
345
|
+
* @param {number} timeout - Query timeout
|
|
346
|
+
* @param {number} limit - Maximum results
|
|
347
|
+
* @param {Object} options - Query options
|
|
348
|
+
* @returns {Promise<Array>} Array of data objects
|
|
349
|
+
*/
|
|
350
|
+
async function _executeNostrGetAll(client, pathPrefix, kind, authors, timeout, limit, options) {
|
|
171
351
|
const filter = {
|
|
172
352
|
kinds: [kind],
|
|
173
353
|
authors: authors,
|
|
@@ -176,10 +356,12 @@ export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}
|
|
|
176
356
|
|
|
177
357
|
const events = await client.query(filter, { timeout });
|
|
178
358
|
|
|
179
|
-
// Filter by path prefix
|
|
359
|
+
// Filter by path prefix AND verify author (relays may not respect authors filter)
|
|
180
360
|
const matching = events.filter(event => {
|
|
181
361
|
const dTag = event.tags.find(t => t[0] === 'd');
|
|
182
|
-
|
|
362
|
+
const pathMatches = dTag && dTag[1] && dTag[1].startsWith(pathPrefix);
|
|
363
|
+
const authorAllowed = authors.includes(event.pubkey);
|
|
364
|
+
return pathMatches && authorAllowed;
|
|
183
365
|
});
|
|
184
366
|
|
|
185
367
|
// Parse content and group by d-tag (keep latest only, across all authors)
|
|
@@ -192,8 +374,11 @@ export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}
|
|
|
192
374
|
try {
|
|
193
375
|
const data = JSON.parse(event.content);
|
|
194
376
|
|
|
195
|
-
//
|
|
196
|
-
if (data._deleted)
|
|
377
|
+
// Handle null/undefined or deleted items - remove from map if this is newer
|
|
378
|
+
if (!data || data._deleted) {
|
|
379
|
+
byPath.delete(dTag);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
197
382
|
|
|
198
383
|
// Optionally include author information
|
|
199
384
|
if (options.includeAuthor) {
|
|
@@ -201,7 +386,7 @@ export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}
|
|
|
201
386
|
}
|
|
202
387
|
byPath.set(dTag, { data, created_at: event.created_at });
|
|
203
388
|
} catch (error) {
|
|
204
|
-
|
|
389
|
+
// Silent - skip unparseable events
|
|
205
390
|
}
|
|
206
391
|
}
|
|
207
392
|
}
|
|
@@ -211,14 +396,18 @@ export async function nostrGetAll(client, pathPrefix, kind = 30000, options = {}
|
|
|
211
396
|
}
|
|
212
397
|
|
|
213
398
|
/**
|
|
214
|
-
* Query all events under a path prefix (HYBRID MODE - local + relay)
|
|
215
|
-
*
|
|
399
|
+
* Query all events under a path prefix (HYBRID MODE - local + relay).
|
|
400
|
+
*
|
|
401
|
+
* Checks local cache first, then merges with relay data.
|
|
402
|
+
*
|
|
216
403
|
* @param {Object} client - NostrClient instance
|
|
217
404
|
* @param {string} pathPrefix - Path prefix to match
|
|
218
|
-
* @param {number} kind - Event kind (default: 30000)
|
|
219
|
-
* @param {Object} options - Query options
|
|
220
|
-
* @param {string[]} options.authors - Array of public keys to query (default: [client.publicKey])
|
|
221
|
-
* @param {boolean} options.includeAuthor - If true, adds _author field to returned data
|
|
405
|
+
* @param {number} [kind=30000] - Event kind (default: 30000)
|
|
406
|
+
* @param {Object} [options={}] - Query options
|
|
407
|
+
* @param {string[]} [options.authors] - Array of public keys to query (default: [client.publicKey])
|
|
408
|
+
* @param {boolean} [options.includeAuthor=false] - If true, adds _author field to returned data
|
|
409
|
+
* @param {number} [options.timeout=30000] - Query timeout in milliseconds
|
|
410
|
+
* @param {number} [options.limit=1000] - Maximum number of events to retrieve
|
|
222
411
|
* @returns {Promise<Array>} Array of data objects (merged from local + relay)
|
|
223
412
|
*/
|
|
224
413
|
export async function nostrGetAllHybrid(client, pathPrefix, kind = 30000, options = {}) {
|
|
@@ -237,10 +426,12 @@ export async function nostrGetAllHybrid(client, pathPrefix, kind = 30000, option
|
|
|
237
426
|
|
|
238
427
|
const events = await queryMethod.call(client, filter, { timeout });
|
|
239
428
|
|
|
240
|
-
// Filter by path prefix
|
|
429
|
+
// Filter by path prefix AND verify author (relays may not respect authors filter)
|
|
241
430
|
const matching = events.filter(event => {
|
|
242
431
|
const dTag = event.tags.find(t => t[0] === 'd');
|
|
243
|
-
|
|
432
|
+
const pathMatches = dTag && dTag[1] && dTag[1].startsWith(pathPrefix);
|
|
433
|
+
const authorAllowed = authors.includes(event.pubkey);
|
|
434
|
+
return pathMatches && authorAllowed;
|
|
244
435
|
});
|
|
245
436
|
|
|
246
437
|
// Parse content and group by d-tag (keep latest only)
|
|
@@ -252,13 +443,22 @@ export async function nostrGetAllHybrid(client, pathPrefix, kind = 30000, option
|
|
|
252
443
|
if (!existing || event.created_at > existing.created_at) {
|
|
253
444
|
try {
|
|
254
445
|
const data = JSON.parse(event.content);
|
|
446
|
+
// Skip null or non-object content
|
|
447
|
+
if (data === null || typeof data !== 'object') {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
// Handle deleted items - remove from map if this is newer
|
|
451
|
+
if (data._deleted) {
|
|
452
|
+
byPath.delete(dTag);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
255
455
|
// Optionally include author information
|
|
256
456
|
if (options.includeAuthor) {
|
|
257
457
|
data._author = event.pubkey;
|
|
258
458
|
}
|
|
259
459
|
byPath.set(dTag, { data, created_at: event.created_at });
|
|
260
460
|
} catch (error) {
|
|
261
|
-
|
|
461
|
+
// Silent - skip unparseable events
|
|
262
462
|
}
|
|
263
463
|
}
|
|
264
464
|
}
|
|
@@ -268,11 +468,19 @@ export async function nostrGetAllHybrid(client, pathPrefix, kind = 30000, option
|
|
|
268
468
|
}
|
|
269
469
|
|
|
270
470
|
/**
|
|
271
|
-
* Delete data (publish deletion event - NIP-09)
|
|
471
|
+
* Delete data (publish deletion event - NIP-09).
|
|
472
|
+
*
|
|
473
|
+
* Publishes a tombstone event and a NIP-09 deletion event to mark data as deleted.
|
|
474
|
+
*
|
|
272
475
|
* @param {Object} client - NostrClient instance
|
|
273
476
|
* @param {string} path - Path identifier
|
|
274
|
-
* @param {number} kind - Original event kind (default: 30000)
|
|
275
|
-
* @returns {Promise<Object>} Deletion event result
|
|
477
|
+
* @param {number} [kind=30000] - Original event kind (default: 30000)
|
|
478
|
+
* @returns {Promise<Object>} Deletion event result with status
|
|
479
|
+
* @example
|
|
480
|
+
* const result = await nostrDelete(client, 'myapp/holon1/items/item1');
|
|
481
|
+
* if (result.reason !== 'not_found') {
|
|
482
|
+
* console.log('Item deleted successfully');
|
|
483
|
+
* }
|
|
276
484
|
*/
|
|
277
485
|
export async function nostrDelete(client, path, kind = 30000) {
|
|
278
486
|
// Read existing data first
|
|
@@ -289,7 +497,7 @@ export async function nostrDelete(client, path, kind = 30000) {
|
|
|
289
497
|
};
|
|
290
498
|
|
|
291
499
|
// Publish tombstone as replacement event
|
|
292
|
-
// This ensures reads will see _deleted flag
|
|
500
|
+
// This ensures reads will see _deleted flag (also updates LRU cache via _cacheEvent)
|
|
293
501
|
const result = await nostrPut(client, path, tombstone, kind);
|
|
294
502
|
|
|
295
503
|
// Also delete from persistent storage to prevent resurrection on restart
|
|
@@ -301,11 +509,6 @@ export async function nostrDelete(client, path, kind = 30000) {
|
|
|
301
509
|
}
|
|
302
510
|
}
|
|
303
511
|
|
|
304
|
-
// Clear from memory cache
|
|
305
|
-
if (client.clearCache) {
|
|
306
|
-
client.clearCache(path);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
512
|
// Also publish NIP-09 deletion event for relay compliance
|
|
310
513
|
const coordinate = `${kind}:${client.publicKey}:${path}`;
|
|
311
514
|
const deletionEvent = {
|
|
@@ -319,25 +522,29 @@ export async function nostrDelete(client, path, kind = 30000) {
|
|
|
319
522
|
|
|
320
523
|
// Publish deletion event (best effort, don't block on failure)
|
|
321
524
|
try {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
coordinate,
|
|
325
|
-
relays: publishResult?.relays?.length || 0,
|
|
326
|
-
successful: publishResult?.successful || []
|
|
327
|
-
});
|
|
525
|
+
await client.publish(deletionEvent);
|
|
526
|
+
// Silent success - deletion queued
|
|
328
527
|
} catch (error) {
|
|
329
|
-
|
|
528
|
+
// Silent - deletion will be retried
|
|
330
529
|
}
|
|
331
530
|
|
|
332
531
|
return result;
|
|
333
532
|
}
|
|
334
533
|
|
|
534
|
+
// Rate limit delay between bulk delete operations (ms)
|
|
535
|
+
const DELETE_RATE_LIMIT_MS = 100;
|
|
536
|
+
|
|
335
537
|
/**
|
|
336
|
-
* Delete all data with path prefix (publish deletion events - NIP-09)
|
|
538
|
+
* Delete all data with path prefix (publish deletion events - NIP-09).
|
|
539
|
+
* Includes built-in rate limiting to avoid overwhelming relays.
|
|
540
|
+
*
|
|
337
541
|
* @param {Object} client - NostrClient instance
|
|
338
542
|
* @param {string} pathPrefix - Path prefix to delete all items under
|
|
339
|
-
* @param {number} kind - Original event kind (default: 30000)
|
|
340
|
-
* @returns {Promise<Object>} Deletion results
|
|
543
|
+
* @param {number} [kind=30000] - Original event kind (default: 30000)
|
|
544
|
+
* @returns {Promise<Object>} Deletion results with success count
|
|
545
|
+
* @example
|
|
546
|
+
* const result = await nostrDeleteAll(client, 'myapp/holon1/items/');
|
|
547
|
+
* console.log(`Deleted ${result.count} items`);
|
|
341
548
|
*/
|
|
342
549
|
export async function nostrDeleteAll(client, pathPrefix, kind = 30000) {
|
|
343
550
|
// Query events from relay
|
|
@@ -388,9 +595,10 @@ export async function nostrDeleteAll(client, pathPrefix, kind = 30000) {
|
|
|
388
595
|
return { success: true, count: 0, results: [] };
|
|
389
596
|
}
|
|
390
597
|
|
|
391
|
-
// Publish tombstone events for each matching item
|
|
598
|
+
// Publish tombstone events for each matching item (with rate limiting)
|
|
392
599
|
const tombstoneResults = [];
|
|
393
|
-
for (
|
|
600
|
+
for (let i = 0; i < matching.length; i++) {
|
|
601
|
+
const event = matching[i];
|
|
394
602
|
const dTag = event.tags.find(t => t[0] === 'd')[1];
|
|
395
603
|
|
|
396
604
|
try {
|
|
@@ -400,7 +608,7 @@ export async function nostrDeleteAll(client, pathPrefix, kind = 30000) {
|
|
|
400
608
|
_deletedAt: Date.now()
|
|
401
609
|
};
|
|
402
610
|
|
|
403
|
-
// Publish tombstone replacement
|
|
611
|
+
// Publish tombstone replacement (this also updates LRU cache via _cacheEvent)
|
|
404
612
|
const result = await nostrPut(client, dTag, tombstone, kind);
|
|
405
613
|
tombstoneResults.push(result);
|
|
406
614
|
|
|
@@ -413,9 +621,9 @@ export async function nostrDeleteAll(client, pathPrefix, kind = 30000) {
|
|
|
413
621
|
}
|
|
414
622
|
}
|
|
415
623
|
|
|
416
|
-
//
|
|
417
|
-
if (
|
|
418
|
-
|
|
624
|
+
// Rate limit: add delay between deletes to avoid overwhelming relays
|
|
625
|
+
if (i < matching.length - 1 && DELETE_RATE_LIMIT_MS > 0) {
|
|
626
|
+
await new Promise(resolve => setTimeout(resolve, DELETE_RATE_LIMIT_MS));
|
|
419
627
|
}
|
|
420
628
|
} catch (error) {
|
|
421
629
|
console.error(`Failed to delete item ${dTag}:`, error);
|
|
@@ -438,15 +646,10 @@ export async function nostrDeleteAll(client, pathPrefix, kind = 30000) {
|
|
|
438
646
|
content: '', // Optional deletion reason/note
|
|
439
647
|
};
|
|
440
648
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
itemsDeleted: matching.length,
|
|
444
|
-
coordinates: coordinates.length,
|
|
445
|
-
relays: publishResult?.relays?.length || 0,
|
|
446
|
-
successful: publishResult?.successful || []
|
|
447
|
-
});
|
|
649
|
+
await client.publish(deletionEvent);
|
|
650
|
+
// Silent success - bulk deletion queued
|
|
448
651
|
} catch (error) {
|
|
449
|
-
|
|
652
|
+
// Silent - bulk deletion will be retried
|
|
450
653
|
}
|
|
451
654
|
|
|
452
655
|
return {
|
|
@@ -457,16 +660,60 @@ export async function nostrDeleteAll(client, pathPrefix, kind = 30000) {
|
|
|
457
660
|
}
|
|
458
661
|
|
|
459
662
|
/**
|
|
460
|
-
* Subscribe to path changes
|
|
663
|
+
* Subscribe to path changes.
|
|
664
|
+
*
|
|
665
|
+
* Uses subscription deduplication - multiple subscribers to same path share one relay subscription.
|
|
666
|
+
*
|
|
461
667
|
* @param {Object} client - NostrClient instance
|
|
462
668
|
* @param {string} path - Path to subscribe to
|
|
463
669
|
* @param {Function} callback - Callback function (data, event) => void
|
|
464
|
-
* @param {Object} options - Subscription options
|
|
670
|
+
* @param {Object} [options={}] - Subscription options
|
|
671
|
+
* @param {number} [options.kind=30000] - Event kind to subscribe to
|
|
465
672
|
* @returns {Object} Subscription object with unsubscribe method
|
|
673
|
+
* @example
|
|
674
|
+
* const sub = nostrSubscribe(client, 'myapp/holon1/items/item1', (data, event) => {
|
|
675
|
+
* console.log('Item updated:', data);
|
|
676
|
+
* });
|
|
677
|
+
* // Later: sub.unsubscribe();
|
|
466
678
|
*/
|
|
467
679
|
export function nostrSubscribe(client, path, callback, options = {}) {
|
|
468
680
|
const kind = options.kind || 30000;
|
|
469
|
-
|
|
681
|
+
|
|
682
|
+
// Create unique key for this subscription
|
|
683
|
+
const subscriptionKey = `single:${client.publicKey}:${kind}:${path}`;
|
|
684
|
+
|
|
685
|
+
// Check if we already have an active subscription for this path
|
|
686
|
+
const existing = singlePathSubscriptions.get(subscriptionKey);
|
|
687
|
+
if (existing) {
|
|
688
|
+
// Add callback to existing subscription
|
|
689
|
+
existing.callbacks.push(callback);
|
|
690
|
+
|
|
691
|
+
// Return wrapper that removes only this callback on unsubscribe
|
|
692
|
+
return {
|
|
693
|
+
unsubscribe: () => {
|
|
694
|
+
const idx = existing.callbacks.indexOf(callback);
|
|
695
|
+
if (idx > -1) {
|
|
696
|
+
existing.callbacks.splice(idx, 1);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// If no more callbacks, unsubscribe the whole thing
|
|
700
|
+
if (existing.callbacks.length === 0) {
|
|
701
|
+
existing.subscription.unsubscribe();
|
|
702
|
+
singlePathSubscriptions.delete(subscriptionKey);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Create new subscription
|
|
709
|
+
const callbacks = [callback];
|
|
710
|
+
const subscriptionInfo = {
|
|
711
|
+
callbacks,
|
|
712
|
+
subscription: null,
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
// Store before creating subscription to handle race conditions
|
|
716
|
+
singlePathSubscriptions.set(subscriptionKey, subscriptionInfo);
|
|
470
717
|
|
|
471
718
|
const filter = {
|
|
472
719
|
kinds: [kind],
|
|
@@ -475,40 +722,61 @@ export function nostrSubscribe(client, path, callback, options = {}) {
|
|
|
475
722
|
limit: 10, // Limit results for single item subscription
|
|
476
723
|
};
|
|
477
724
|
|
|
478
|
-
let initialLoadComplete = false;
|
|
479
|
-
|
|
480
725
|
const subscription = client.subscribe(
|
|
481
726
|
filter,
|
|
482
727
|
(event) => {
|
|
483
728
|
// Verify event is from our public key (relay may not respect author filter)
|
|
484
729
|
if (event.pubkey !== client.publicKey) {
|
|
485
|
-
console.warn('[nostrSubscribe] Rejecting event from different author:', {
|
|
486
|
-
expected: client.publicKey,
|
|
487
|
-
received: event.pubkey,
|
|
488
|
-
eventId: event.id
|
|
489
|
-
});
|
|
490
730
|
return;
|
|
491
731
|
}
|
|
492
732
|
|
|
493
733
|
try {
|
|
494
734
|
const data = JSON.parse(event.content);
|
|
495
|
-
|
|
735
|
+
|
|
736
|
+
// Skip null/undefined or deleted items - don't send tombstones to subscribers
|
|
737
|
+
if (!data || data._deleted) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Dispatch to all registered callbacks
|
|
742
|
+
for (const cb of callbacks) {
|
|
743
|
+
try {
|
|
744
|
+
cb(data, event);
|
|
745
|
+
} catch (err) {
|
|
746
|
+
console.error('Subscription callback error:', err);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
496
749
|
} catch (error) {
|
|
497
750
|
console.error('Failed to parse event in subscription:', error);
|
|
498
751
|
}
|
|
499
752
|
},
|
|
500
753
|
{
|
|
501
754
|
onEOSE: () => {
|
|
502
|
-
|
|
755
|
+
// EOSE received
|
|
503
756
|
},
|
|
504
757
|
}
|
|
505
758
|
);
|
|
506
759
|
|
|
507
|
-
|
|
760
|
+
subscriptionInfo.subscription = subscription;
|
|
761
|
+
|
|
762
|
+
return {
|
|
763
|
+
unsubscribe: () => {
|
|
764
|
+
const idx = callbacks.indexOf(callback);
|
|
765
|
+
if (idx > -1) {
|
|
766
|
+
callbacks.splice(idx, 1);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// If no more callbacks, unsubscribe the whole thing
|
|
770
|
+
if (callbacks.length === 0) {
|
|
771
|
+
subscription.unsubscribe();
|
|
772
|
+
singlePathSubscriptions.delete(subscriptionKey);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
};
|
|
508
776
|
}
|
|
509
777
|
|
|
510
778
|
/**
|
|
511
|
-
* Subscribe to path prefix (multiple paths)
|
|
779
|
+
* Subscribe to path prefix (multiple paths).
|
|
512
780
|
*
|
|
513
781
|
* Subscribes to data events and uses Page Visibility API to refresh when tab becomes visible.
|
|
514
782
|
* Note: Nostr relays do not broadcast replaceable event updates to active subscriptions.
|
|
@@ -516,8 +784,14 @@ export function nostrSubscribe(client, path, callback, options = {}) {
|
|
|
516
784
|
* @param {Object} client - NostrClient instance
|
|
517
785
|
* @param {string} pathPrefix - Path prefix to match
|
|
518
786
|
* @param {Function} callback - Callback function (data, path, event) => void
|
|
519
|
-
* @param {Object} options - Subscription options
|
|
787
|
+
* @param {Object} [options={}] - Subscription options
|
|
788
|
+
* @param {number} [options.kind=30000] - Event kind to subscribe to
|
|
520
789
|
* @returns {Promise<Object>} Subscription object with unsubscribe method
|
|
790
|
+
* @example
|
|
791
|
+
* const sub = await nostrSubscribeMany(client, 'myapp/holon1/items/', (data, path, event) => {
|
|
792
|
+
* console.log('Item event:', data);
|
|
793
|
+
* });
|
|
794
|
+
* // Later: sub.unsubscribe();
|
|
521
795
|
*/
|
|
522
796
|
export async function nostrSubscribeMany(client, pathPrefix, callback, options = {}) {
|
|
523
797
|
const kind = options.kind || 30000;
|
|
@@ -589,9 +863,15 @@ export async function nostrSubscribeMany(client, pathPrefix, callback, options =
|
|
|
589
863
|
|
|
590
864
|
// Client-side prefix filter
|
|
591
865
|
if (dTag && dTag.startsWith(pathPrefix)) {
|
|
592
|
-
acceptedCount++;
|
|
593
866
|
try {
|
|
594
867
|
const data = JSON.parse(event.content);
|
|
868
|
+
|
|
869
|
+
// Skip null/undefined data or deleted items - don't send tombstones to subscribers
|
|
870
|
+
if (!data || data._deleted) {
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
acceptedCount++;
|
|
595
875
|
// Notify all registered callbacks
|
|
596
876
|
for (const cb of callbacks) {
|
|
597
877
|
cb(data, dTag, event);
|
|
@@ -672,12 +952,16 @@ export async function nostrSubscribeMany(client, pathPrefix, callback, options =
|
|
|
672
952
|
}
|
|
673
953
|
|
|
674
954
|
/**
|
|
675
|
-
* Update data (merge with existing)
|
|
955
|
+
* Update data (merge with existing).
|
|
956
|
+
*
|
|
676
957
|
* @param {Object} client - NostrClient instance
|
|
677
958
|
* @param {string} path - Path identifier
|
|
678
959
|
* @param {Object} updates - Fields to update
|
|
679
|
-
* @param {number} kind - Event kind (default: 30000)
|
|
960
|
+
* @param {number} [kind=30000] - Event kind (default: 30000)
|
|
680
961
|
* @returns {Promise<Object>} Updated event result
|
|
962
|
+
* @throws {Error} If no data found at path
|
|
963
|
+
* @example
|
|
964
|
+
* await nostrUpdate(client, 'myapp/holon1/items/item1', { status: 'completed' });
|
|
681
965
|
*/
|
|
682
966
|
export async function nostrUpdate(client, path, updates, kind = 30000) {
|
|
683
967
|
// Read existing data
|
|
@@ -695,10 +979,11 @@ export async function nostrUpdate(client, path, updates, kind = 30000) {
|
|
|
695
979
|
}
|
|
696
980
|
|
|
697
981
|
/**
|
|
698
|
-
* Batch read multiple paths
|
|
982
|
+
* Batch read multiple paths.
|
|
983
|
+
*
|
|
699
984
|
* @param {Object} client - NostrClient instance
|
|
700
985
|
* @param {string[]} paths - Array of paths
|
|
701
|
-
* @param {number} kind - Event kind (default: 30000)
|
|
986
|
+
* @param {number} [kind=30000] - Event kind (default: 30000)
|
|
702
987
|
* @returns {Promise<Object>} Object mapping paths to data
|
|
703
988
|
*/
|
|
704
989
|
export async function nostrBatchGet(client, paths, kind = 30000) {
|
|
@@ -739,10 +1024,11 @@ export async function nostrBatchGet(client, paths, kind = 30000) {
|
|
|
739
1024
|
}
|
|
740
1025
|
|
|
741
1026
|
/**
|
|
742
|
-
* Batch write multiple paths
|
|
1027
|
+
* Batch write multiple paths.
|
|
1028
|
+
*
|
|
743
1029
|
* @param {Object} client - NostrClient instance
|
|
744
1030
|
* @param {Object} pathDataMap - Object mapping paths to data
|
|
745
|
-
* @param {number} kind - Event kind (default: 30000)
|
|
1031
|
+
* @param {number} [kind=30000] - Event kind (default: 30000)
|
|
746
1032
|
* @returns {Promise<Array>} Array of publish results
|
|
747
1033
|
*/
|
|
748
1034
|
export async function nostrBatchPut(client, pathDataMap, kind = 30000) {
|
|
@@ -754,11 +1040,13 @@ export async function nostrBatchPut(client, pathDataMap, kind = 30000) {
|
|
|
754
1040
|
}
|
|
755
1041
|
|
|
756
1042
|
/**
|
|
757
|
-
* Retry operation with exponential backoff
|
|
1043
|
+
* Retry operation with exponential backoff.
|
|
1044
|
+
*
|
|
758
1045
|
* @param {Function} operation - Async function to retry
|
|
759
|
-
* @param {number} maxRetries - Max retry attempts
|
|
760
|
-
* @param {number} baseDelay - Base delay in ms
|
|
1046
|
+
* @param {number} [maxRetries=3] - Max retry attempts
|
|
1047
|
+
* @param {number} [baseDelay=100] - Base delay in ms
|
|
761
1048
|
* @returns {Promise<any>} Promise resolving to operation result
|
|
1049
|
+
* @throws {Error} Last error if all retries fail
|
|
762
1050
|
*/
|
|
763
1051
|
export async function nostrRetry(operation, maxRetries = 3, baseDelay = 100) {
|
|
764
1052
|
let lastError;
|
|
@@ -779,12 +1067,14 @@ export async function nostrRetry(operation, maxRetries = 3, baseDelay = 100) {
|
|
|
779
1067
|
}
|
|
780
1068
|
|
|
781
1069
|
/**
|
|
782
|
-
* Wait for specific condition on data
|
|
1070
|
+
* Wait for specific condition on data.
|
|
1071
|
+
*
|
|
783
1072
|
* @param {Object} client - NostrClient instance
|
|
784
1073
|
* @param {string} path - Path to watch
|
|
785
1074
|
* @param {Function} predicate - Condition function (data) => boolean
|
|
786
|
-
* @param {number} timeout - Timeout in ms
|
|
1075
|
+
* @param {number} [timeout=5000] - Timeout in ms
|
|
787
1076
|
* @returns {Promise<any>} Promise resolving when condition is met
|
|
1077
|
+
* @throws {Error} If timeout occurs before condition is met
|
|
788
1078
|
*/
|
|
789
1079
|
export async function nostrWaitFor(client, path, predicate, timeout = 5000) {
|
|
790
1080
|
return new Promise((resolve, reject) => {
|