├── .changeset
├── README.md
└── config.json
├── .clinerules
├── .cursorrules
├── .devcontainer
├── Dockerfile
└── devcontainer.json
├── .github
└── workflows
│ └── deploy.yml
├── .gitignore
├── .gitmodules
├── .prettierignore
├── .prettierrc
├── .roo
├── mcp.json
└── rules
│ ├── INDEX.md
│ ├── NOSTR.md
│ └── TESTS.md
├── .roomodes
├── .specs
└── relay-transport.md
├── .tenex.json
├── BUILD.md
├── LICENSE
├── README.md
├── REFERENCES.md
├── biome.json
├── bun.lock
├── context
└── SPEC.md
├── docs
├── .vitepress
│ ├── config.mts
│ └── theme
│ │ ├── index.ts
│ │ └── style.css
├── api-examples.md
├── index.md
└── package.json
├── ndk-blossom
├── CHANGELOG.md
├── README.md
├── SPEC.md
├── context
│ └── SPEC.md
├── docs
│ ├── error-handling.md
│ ├── getting-started.md
│ ├── mirroring.md
│ └── optimization.md
├── example
│ └── blossom-upload
│ │ ├── README.md
│ │ ├── bun.lock
│ │ ├── index.html
│ │ ├── index.tsx
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ └── vite.config.ts
├── package.json
├── src
│ ├── blossom.ts
│ ├── healing
│ │ └── url-healing.ts
│ ├── index.ts
│ ├── types
│ │ └── index.ts
│ ├── upload
│ │ └── uploader.ts
│ └── utils
│ │ ├── auth.ts
│ │ ├── constants.ts
│ │ ├── errors.ts
│ │ ├── http.ts
│ │ ├── logger.ts
│ │ └── sha256.ts
└── tsconfig.json
├── ndk-cache-dexie
├── .gitignore
├── .prettierignore
├── CHANGELOG.md
├── README.md
├── package.json
├── previous-head-with-ndk-hooks
├── src
│ ├── caches
│ │ ├── event-tags.ts
│ │ ├── events.ts
│ │ ├── nip05.ts
│ │ ├── profiles.ts
│ │ ├── relay-info.ts
│ │ ├── unpublished-events.ts
│ │ └── zapper.ts
│ ├── db.ts
│ ├── index.test.ts
│ ├── index.ts
│ └── lru-cache.ts
├── test
│ ├── performance.test.ts
│ └── setup.ts
├── tsconfig.json
├── typedoc.json
└── vitest.config.ts
├── ndk-cache-nostr
├── .gitignore
├── .prettierignore
├── CHANGELOG.md
├── README.md
├── docs
│ └── cache
│ │ └── nostr.md
├── package.json
├── src
│ ├── index.ts
│ └── queue.ts
└── tsconfig.json
├── ndk-cache-redis
├── .gitignore
├── .prettierignore
├── CHANGELOG.md
├── README.md
├── package.json
├── src
│ ├── index.test.ts
│ └── index.ts
└── tsconfig.json
├── ndk-cache-sqlite-wasm
├── CHANGELOG.md
├── docs
│ ├── INDEX.md
│ ├── bundling.md
│ └── web-worker-setup.md
├── example
│ ├── index.html
│ ├── sql-wasm.wasm
│ └── vite
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── bun.lock
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── public
│ │ └── vite.svg
│ │ ├── src
│ │ ├── counter.ts
│ │ ├── main.ts
│ │ ├── style.css
│ │ └── typescript.svg
│ │ └── tsconfig.json
├── package.json
├── src
│ ├── db
│ │ ├── indexeddb-utils.ts
│ │ ├── migrations.ts
│ │ ├── schema.ts
│ │ └── wasm-loader.ts
│ ├── functions
│ │ ├── addDecryptedEvent.ts
│ │ ├── addUnpublishedEvent.ts
│ │ ├── discardUnpublishedEvent.ts
│ │ ├── fetchProfile.ts
│ │ ├── fetchProfileSync.ts
│ │ ├── getAllProfilesSync.ts
│ │ ├── getDecryptedEvent.ts
│ │ ├── getEvent.ts
│ │ ├── getProfiles.ts
│ │ ├── getRelayStatus.ts
│ │ ├── getUnpublishedEvents.ts
│ │ ├── query.ts
│ │ ├── saveProfile.ts
│ │ ├── setEvent.ts
│ │ └── updateRelayStatus.ts
│ ├── index.ts
│ ├── types.ts
│ └── worker.ts
└── tsconfig.json
├── ndk-cache-sqlite
├── README.md
├── bun.lock
├── example
│ ├── README.md
│ ├── bun.lock
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
├── package.json
├── src
│ ├── db
│ │ ├── database.ts
│ │ ├── migrations.ts
│ │ └── schema.ts
│ ├── functions
│ │ ├── fetchProfile.ts
│ │ ├── getEvent.ts
│ │ ├── getProfiles.ts
│ │ ├── getRelayStatus.ts
│ │ ├── query.ts
│ │ ├── saveProfile.ts
│ │ ├── setEvent.ts
│ │ └── updateRelayStatus.ts
│ ├── index.test.ts
│ ├── index.ts
│ └── types.ts
├── test
│ └── setup
│ │ └── vitest.setup.ts
├── tsconfig.json
└── vitest.config.ts
├── ndk-core
├── .prettierignore
├── CHANGELOG.md
├── LICENSE
├── OUTBOX-REPORT.md
├── OUTBOX.md
├── README.md
├── RELEASE-NOTES.md
├── SIG-SAMPLING.md
├── bun.lock
├── docs-styles.css
├── docs
│ ├── api-examples.md
│ ├── getting-started
│ │ ├── introduction.md
│ │ ├── signers.md
│ │ └── usage.md
│ ├── index.md
│ ├── internals
│ │ └── subscriptions.md
│ ├── migration
│ │ └── 2.12-to-2.13.md
│ └── tutorial
│ │ ├── auth.md
│ │ ├── local-first.md
│ │ ├── publishing.md
│ │ ├── signer-persistence.md
│ │ ├── speed.md
│ │ ├── subscription-management.md
│ │ └── zaps
│ │ └── index.md
├── package.json
├── snippets
│ ├── event
│ │ ├── basic.md
│ │ ├── publish-tracking.md
│ │ ├── signing-with-different-signers.md
│ │ └── tagging-users-and-events.md
│ ├── index.md
│ ├── testing
│ │ ├── event-generation.md
│ │ ├── mock-relays.md
│ │ ├── nutzap-testing.md
│ │ └── relay-pool-testing.md
│ └── user
│ │ ├── generate-keys.md
│ │ └── get-profile.md
├── src
│ ├── app-settings
│ │ └── index.ts
│ ├── cache
│ │ └── index.ts
│ ├── dvm
│ │ └── schedule.ts
│ ├── events
│ │ ├── content-tagger.test.ts
│ │ ├── content-tagger.ts
│ │ ├── dedup.ts
│ │ ├── encode.test.ts
│ │ ├── encryption.test.ts
│ │ ├── encryption.ts
│ │ ├── fetch-tagged-event.ts
│ │ ├── gift-wrapping.ts
│ │ ├── index.test.ts
│ │ ├── index.ts
│ │ ├── kind.ts
│ │ ├── kinds
│ │ │ ├── NDKRelayList.ts
│ │ │ ├── article.ts
│ │ │ ├── blossom-list.ts
│ │ │ ├── cashu
│ │ │ │ ├── token.ts
│ │ │ │ ├── tx.test.ts
│ │ │ │ └── tx.ts
│ │ │ ├── classified.ts
│ │ │ ├── drafts.ts
│ │ │ ├── dvm
│ │ │ │ ├── NDKTranscriptionDVM.ts
│ │ │ │ ├── feedback.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── request.ts
│ │ │ │ └── result.ts
│ │ │ ├── follow-pack.test.ts
│ │ │ ├── follow-pack.ts
│ │ │ ├── highlight.ts
│ │ │ ├── image.ts
│ │ │ ├── index.ts
│ │ │ ├── lists
│ │ │ │ ├── index.test.ts
│ │ │ │ └── index.ts
│ │ │ ├── nip89
│ │ │ │ ├── app-handler.test.ts
│ │ │ │ └── app-handler.ts
│ │ │ ├── nutzap
│ │ │ │ ├── index.test.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── mint-list.ts
│ │ │ │ └── proof.ts
│ │ │ ├── repost.ts
│ │ │ ├── simple-group
│ │ │ │ ├── index.ts
│ │ │ │ ├── member-list.ts
│ │ │ │ └── metadata.ts
│ │ │ ├── story.test.ts
│ │ │ ├── story.ts
│ │ │ ├── subscriptions
│ │ │ │ ├── amount.ts
│ │ │ │ ├── receipt.ts
│ │ │ │ ├── subscription-start.ts
│ │ │ │ ├── tier.test.ts
│ │ │ │ └── tier.ts
│ │ │ ├── video.ts
│ │ │ └── wiki.ts
│ │ ├── nip19.test.ts
│ │ ├── nip19.ts
│ │ ├── nip73.ts
│ │ ├── publish-tracking.test.ts
│ │ ├── repost.test.ts
│ │ ├── repost.ts
│ │ ├── serializer.ts
│ │ ├── signature.ts
│ │ ├── validation.ts
│ │ └── wrap.ts
│ ├── index.ts
│ ├── light-bolt11-decoder.d.ts
│ ├── ndk
│ │ ├── active-user.ts
│ │ ├── entity.ts
│ │ ├── fetch-event-from-tag.ts
│ │ ├── index.ts
│ │ └── queue
│ │ │ └── index.ts
│ ├── outbox
│ │ ├── index.ts
│ │ ├── read
│ │ │ └── with-authors.ts
│ │ ├── relay-ranking.ts
│ │ ├── tracker.test.ts
│ │ ├── tracker.ts
│ │ └── write.ts
│ ├── relay
│ │ ├── auth-policies.test.ts
│ │ ├── auth-policies.ts
│ │ ├── connectivity.test.ts
│ │ ├── connectivity.ts
│ │ ├── index.test.ts
│ │ ├── index.ts
│ │ ├── pool
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── publisher.ts
│ │ ├── score.ts
│ │ ├── sets
│ │ │ ├── calculate.test.ts
│ │ │ ├── calculate.ts
│ │ │ ├── index.ts
│ │ │ ├── publish.test.ts
│ │ │ └── utils.ts
│ │ ├── signature-verification-stats.ts
│ │ ├── sub-manager.ts
│ │ ├── subscription.test.ts
│ │ └── subscription.ts
│ ├── signers
│ │ ├── deserialization.ts
│ │ ├── index.ts
│ │ ├── nip07
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── nip46
│ │ │ ├── backend
│ │ │ │ ├── connect.ts
│ │ │ │ ├── get-public-key.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── nip04-decrypt.ts
│ │ │ │ ├── nip04-encrypt.ts
│ │ │ │ ├── nip44-decrypt.ts
│ │ │ │ ├── nip44-encrypt.ts
│ │ │ │ ├── ping.ts
│ │ │ │ └── sign-event.ts
│ │ │ ├── index.test.ts
│ │ │ ├── index.ts
│ │ │ ├── nostrconnect.ts
│ │ │ └── rpc.ts
│ │ ├── private-key
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ │ ├── registry.ts
│ │ ├── serialization.test.ts
│ │ └── types.ts
│ ├── subscription.test.ts
│ ├── subscription
│ │ ├── grouping.test.ts
│ │ ├── grouping.ts
│ │ ├── index.test.ts
│ │ ├── index.ts
│ │ ├── manager.test.ts
│ │ ├── manager.ts
│ │ ├── utils.test.ts
│ │ └── utils.ts
│ ├── thread
│ │ ├── index.test.ts
│ │ └── index.ts
│ ├── types.ts
│ ├── user
│ │ ├── follows.test.ts
│ │ ├── follows.ts
│ │ ├── index.test.ts
│ │ ├── index.ts
│ │ ├── nip05.test.ts
│ │ ├── nip05.ts
│ │ ├── pin.ts
│ │ └── profile.ts
│ ├── utils
│ │ ├── filter.ts
│ │ ├── get-users-relay-list.ts
│ │ ├── imeta.test.ts
│ │ ├── imeta.ts
│ │ ├── normalize-url.ts
│ │ └── timeout.ts
│ ├── workers
│ │ └── sig-verification.ts
│ ├── zap
│ │ └── invoice.ts
│ └── zapper
│ │ ├── index.test.ts
│ │ ├── index.ts
│ │ ├── ln.ts
│ │ ├── nip57.test.ts
│ │ ├── nip57.ts
│ │ └── nip61.ts
├── test
│ ├── helpers
│ │ ├── test-fixtures.ts
│ │ └── time.ts
│ ├── index.ts
│ ├── mocks
│ │ ├── event-generator.ts
│ │ ├── nutzaps.ts
│ │ ├── relay-mock.ts
│ │ └── relay-pool-mock.ts
│ └── setup
│ │ └── vitest.setup.ts
├── tsconfig.json
├── tsconfig.typedoc.json
├── typedoc.json
└── vitest.config.ts
├── ndk-expert-best-practices.md
├── ndk-hooks
├── .gitignore
├── .roomodes
├── CHANGELOG.md
├── README.md
├── docs
│ ├── index.md
│ ├── muting.md
│ └── session-management.md
├── example
│ └── session
│ │ ├── README.md
│ │ ├── bun.lock
│ │ ├── index.html
│ │ ├── index.tsx
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
├── package.json
├── rules
│ └── NOSTR.md
├── snippets
│ ├── session-monitoring.md
│ ├── update-user-profile.md
│ └── use-mute-item.md
├── src
│ ├── index.ts
│ ├── mutes
│ │ ├── hooks
│ │ │ ├── __tests__
│ │ │ │ └── mute-hooks.test.ts
│ │ │ ├── index.ts
│ │ │ ├── use-is-item-muted.ts
│ │ │ ├── use-mute-criteria.ts
│ │ │ └── use-mute-filter.ts
│ │ ├── store
│ │ │ ├── __tests__
│ │ │ │ ├── fixtures.ts
│ │ │ │ └── mute-store.test.ts
│ │ │ ├── add-extra-mute-items.ts
│ │ │ ├── index.ts
│ │ │ ├── init.ts
│ │ │ ├── is-item-muted.ts
│ │ │ ├── load.ts
│ │ │ ├── mute.ts
│ │ │ ├── set-active-pubkey.ts
│ │ │ ├── types.ts
│ │ │ └── unmute.ts
│ │ └── utils
│ │ │ ├── compute-mute-criteria.ts
│ │ │ └── identify-mute-item.ts
│ ├── ndk
│ │ ├── headless
│ │ │ └── index.tsx
│ │ ├── hooks
│ │ │ └── index.ts
│ │ └── store
│ │ │ └── index.ts
│ ├── observer
│ │ └── hooks
│ │ │ └── index.ts
│ ├── profiles
│ │ ├── hooks
│ │ │ └── index.ts
│ │ ├── store
│ │ │ ├── fetch-profile.ts
│ │ │ ├── index.ts
│ │ │ ├── initialize.ts
│ │ │ └── set-profile.ts
│ │ └── types.ts
│ ├── session
│ │ ├── hooks
│ │ │ ├── __tests__
│ │ │ │ └── use-ndk-session-monitor.test.ts
│ │ │ ├── control.ts
│ │ │ ├── index.ts
│ │ │ ├── sessions.ts
│ │ │ ├── signers.ts
│ │ │ ├── use-available-sessions.ts
│ │ │ └── use-ndk-session-monitor.ts
│ │ ├── index.ts
│ │ ├── storage
│ │ │ ├── __tests__
│ │ │ │ ├── index.test.ts
│ │ │ │ └── mock-storage-adapter.ts
│ │ │ └── index.ts
│ │ ├── store
│ │ │ ├── add-session.ts
│ │ │ ├── index.ts
│ │ │ ├── init.ts
│ │ │ ├── remove-session.ts
│ │ │ ├── start-session.ts
│ │ │ ├── stop-session.ts
│ │ │ ├── switch-to-user.ts
│ │ │ ├── types.ts
│ │ │ └── update-session.ts
│ │ └── utils.ts
│ ├── subscribe
│ │ ├── hooks
│ │ │ ├── event.ts
│ │ │ ├── index.ts
│ │ │ ├── subscribe.test.ts
│ │ │ └── subscribe.ts
│ │ └── store
│ │ │ ├── index.test.ts
│ │ │ └── index.ts
│ ├── utils
│ │ ├── __tests__
│ │ │ └── mute.test.ts
│ │ ├── mute.ts
│ │ └── time.ts
│ └── wallet
│ │ └── hooks
│ │ └── index.ts
├── tsconfig.json
└── vitest.config.ts
├── ndk-mobile-expert-best-practices.md
├── ndk-mobile-sig-check-setup.md
├── ndk-mobile
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
│ ├── index.md
│ ├── migration-nutzap-hooks.md
│ ├── mint.md
│ ├── nutzaps.md
│ ├── session.md
│ ├── subscriptions.md
│ └── wallet.md
├── knowledge.md
├── package.json
├── rules
│ └── NOSTR.md
├── snippets
│ └── mobile
│ │ ├── cashu
│ │ ├── advanced-usage.md
│ │ ├── basic-usage.md
│ │ └── database.md
│ │ ├── events
│ │ └── rendering-event-content.md
│ │ ├── ndk
│ │ └── initializing-ndk.md
│ │ ├── profile-integration-examples.md
│ │ ├── session
│ │ └── login.md
│ │ └── user
│ │ └── loading-user-profiles.md
├── src
│ ├── cache-adapter
│ │ └── sqlite
│ │ │ ├── get-all-profiles.ts
│ │ │ ├── index.ts
│ │ │ ├── migrations.ts
│ │ │ ├── nutzap-state-get.ts
│ │ │ ├── nutzap-state-set.ts
│ │ │ └── search-profiles.ts
│ ├── components
│ │ ├── event
│ │ │ ├── content.tsx
│ │ │ └── index.tsx
│ │ ├── index.tsx
│ │ └── relays
│ │ │ ├── index.tsx
│ │ │ └── indicator.tsx
│ ├── hooks
│ │ ├── index.ts
│ │ └── nip55.tsx
│ ├── index.ts
│ ├── mint
│ │ ├── index.ts
│ │ └── mint-methods.ts
│ ├── session-monitor.ts
│ ├── session-storage-adapter.ts
│ ├── signers
│ │ ├── index.ts
│ │ └── nip55.ts
│ ├── stores
│ │ └── wallet.ts
│ ├── types.ts
│ ├── types
│ │ └── cashu.ts
│ └── utils
│ │ └── time.ts
├── tsconfig.build.json
├── tsconfig.json
└── vitest.config.ts
├── ndk-svelte-components
├── .eslintrc.cjs
├── .gitignore
├── .npmrc
├── .prettierignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── images
│ └── relay-list.png
├── package.json
├── postcss.config.cjs
├── src
│ ├── app.html
│ ├── lib
│ │ ├── event
│ │ │ ├── ElementConnector.svelte
│ │ │ ├── EventCard.svelte
│ │ │ ├── EventCardDropdownMenu.svelte
│ │ │ ├── EventThread.svelte
│ │ │ └── content
│ │ │ │ ├── EventContent.svelte
│ │ │ │ ├── Kind1.svelte
│ │ │ │ ├── Kind1063.svelte
│ │ │ │ ├── Kind30000.svelte
│ │ │ │ ├── Kind30001.svelte
│ │ │ │ ├── Kind30023.svelte
│ │ │ │ ├── Kind9802.svelte
│ │ │ │ ├── NoteContentLink.svelte
│ │ │ │ ├── NoteContentNewline.svelte
│ │ │ │ ├── NoteContentPerson.svelte
│ │ │ │ ├── NoteContentTopic.svelte
│ │ │ │ ├── RenderHtml.svelte
│ │ │ │ └── renderer
│ │ │ │ ├── hashtag.svelte
│ │ │ │ ├── index.ts
│ │ │ │ ├── link.svelte
│ │ │ │ ├── mention.svelte
│ │ │ │ └── nostr-event.svelte
│ │ ├── index.ts
│ │ ├── relay
│ │ │ ├── RelayList.svelte
│ │ │ ├── RelayListItem.svelte
│ │ │ └── RelayName.svelte
│ │ ├── stores
│ │ │ └── ndk.ts
│ │ ├── user
│ │ │ ├── Avatar.svelte
│ │ │ ├── Name.svelte
│ │ │ ├── Nip05.svelte
│ │ │ ├── Npub.svelte
│ │ │ └── UserCard.svelte
│ │ └── utils
│ │ │ ├── event
│ │ │ └── index.ts
│ │ │ ├── extensions
│ │ │ ├── event.svelte
│ │ │ ├── hashtag.svelte
│ │ │ ├── image.svelte
│ │ │ └── mention.svelte
│ │ │ ├── index.ts
│ │ │ ├── markdown.ts
│ │ │ ├── notes.ts
│ │ │ ├── relay
│ │ │ └── index.ts
│ │ │ └── user
│ │ │ └── index.ts
│ ├── routes
│ │ └── +page.svelte
│ └── styles
│ │ └── global.css
├── static
│ └── favicon.png
├── svelte.config.js
├── tailwind.config.js
├── tsconfig.json
└── vite.config.ts
├── ndk-svelte
├── .gitignore
├── .prettierignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
│ └── wrappers
│ │ └── svelte.md
├── package.json
├── src
│ ├── index.svelte.ts
│ └── index.ts
└── tsconfig.json
├── ndk-wallet
├── CHANGELOG.md
├── README.md
├── docs
│ ├── index.md
│ ├── nutsack.md
│ ├── nutzap-monitor-state-store.md
│ ├── nutzap-monitor.md
│ └── nutzaps.md
├── package.json
├── snippets
│ └── wallet
│ │ ├── connect-nwc.md
│ │ └── using-cashu-wallet.md
├── src
│ ├── index.ts
│ ├── light-bolt11-decoder.d.ts
│ ├── nutzap-monitor
│ │ ├── fetch-page.ts
│ │ ├── group-nutzaps.test.ts
│ │ ├── group-nutzaps.ts
│ │ ├── index.test.ts
│ │ ├── index.ts
│ │ ├── spend-status.test.ts
│ │ └── spend-status.ts
│ ├── utils
│ │ ├── cashu.ts
│ │ └── ln.ts
│ └── wallets
│ │ ├── cashu
│ │ ├── deposit-monitor.ts
│ │ ├── deposit.ts
│ │ ├── event-handlers
│ │ │ ├── deletion.ts
│ │ │ ├── index.ts
│ │ │ ├── quote.ts
│ │ │ └── token.ts
│ │ ├── mint.ts
│ │ ├── mint
│ │ │ └── utils.ts
│ │ ├── pay
│ │ │ ├── ln.ts
│ │ │ ├── nut.test.ts
│ │ │ └── nut.ts
│ │ ├── quote.ts
│ │ ├── validate.ts
│ │ └── wallet
│ │ │ ├── effect.ts
│ │ │ ├── fee.ts
│ │ │ ├── index.test.ts
│ │ │ ├── index.ts
│ │ │ ├── migrate.ts
│ │ │ ├── payment.ts
│ │ │ ├── state
│ │ │ ├── balance.ts
│ │ │ ├── index.ts
│ │ │ ├── proofs.ts
│ │ │ ├── token.ts
│ │ │ ├── update.test.ts
│ │ │ └── update.ts
│ │ │ └── txs.ts
│ │ ├── index.ts
│ │ ├── mint.ts
│ │ ├── nwc
│ │ ├── index.ts
│ │ ├── nutzap.ts
│ │ ├── req.ts
│ │ ├── res.ts
│ │ └── types.ts
│ │ └── webln
│ │ ├── index.ts
│ │ └── pay.ts
├── tsconfig.json
├── vitest.config.ts
└── vitest.setup.ts
├── ndk-web-expert-best-practices.md
├── package.json
├── prepare-docs.sh
├── turbo.json
├── vitest.config.ts
└── vitest.workspace.ts
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "public",
8 | "baseBranch": "master",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
12 |
--------------------------------------------------------------------------------
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use the specific Bun version that matches our package manager
2 | FROM oven/bun:1.0.0
3 |
4 | # Set working directory
5 | WORKDIR /app
6 |
7 | # Copy package files
8 | COPY package.json bun.lockb ./
9 |
10 | # Install dependencies
11 | RUN bun install
12 |
13 | # Copy the rest of the code
14 | COPY . .
15 |
16 | # Build the project
17 | RUN bun run build
18 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "NDK devcontainer",
3 | "build": {
4 | "dockerfile": "Dockerfile",
5 | "context": "../"
6 | },
7 | "customizations": {
8 | "vscode": {
9 | "settings": {
10 | "terminal.integrated.shell.linux": "/bin/bash"
11 | }
12 | }
13 | },
14 | "postStartCommand": "echo 'export PATH=$(bun bin):$PATH' >> ~/.bashrc && . ~/.bashrc"
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | temp
3 | **/build
4 | **/dist
5 | **/lib
6 | !ndk-svelte-components/src/lib/
7 | **/.vscode
8 | justfile
9 | package-lock.json
10 | **/*.js
11 | !.eslintrc.js
12 | !svelte.config.js
13 | !tailwind.config.js
14 | !postcss.config.js
15 | **/*.d.ts
16 | **/*.d.ts.map
17 | !light-bolt11-decoder.d.ts
18 | *.tgz
19 | .DS_Store
20 | .turbo
21 | _local_
22 | .svelte-kit/
23 | .pnpm-store
24 | docs/.vitepress/cache
25 | docs/.vitepress/dist
26 | .ngit
27 | **/.repomix-output.txt
28 | cursor-tools.config.json
29 | coverage
30 |
31 | # Generated docs directories
32 | /docs/getting-started/
33 | /docs/internals/
34 | /docs/migration/
35 | /docs/tutorial/
36 | /docs/api-examples.md
37 | /docs/index.md
38 | /docs/snippets/
39 | /docs/cache/
40 | /docs/mobile/
41 | /docs/wallet/
42 | /docs/wrappers/
43 | /docs/hooks/
44 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nostr-dev-kit/ndk/7617d9356f14774a2e646f831f023ba09ce5ef34/.gitmodules
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 | docs
3 | coverage
4 | **/.changeset
5 | **/.svelte-kit
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": false,
3 | "tabWidth": 4,
4 | "singleQuote": false,
5 | "semi": true,
6 | "trailingComma": "es5",
7 | "printWidth": 100,
8 | "overrides": [
9 | {
10 | "files": "*.svelte",
11 | "options": {
12 | "parser": "svelte"
13 | }
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/.roo/mcp.json:
--------------------------------------------------------------------------------
1 | {
2 | "mcpServers": {
3 | "tenex": {
4 | "command": "/Users/pablofernandez/test123/TENEX-pfkmc9/mcp/tenex-mcp",
5 | "args": [],
6 | "alwaysAllow": ["publish", "publish_task_status_update"],
7 | "env": {
8 | "NSEC": "nsec13dvn7mzaqxj5chjg4s784lhen4ugthngpcawl0zw43na3ge720ys8m9kjv"
9 | },
10 | "disabled": false
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.roo/rules/INDEX.md:
--------------------------------------------------------------------------------
1 | Package manager = bun
2 | Testing = vitest
3 | Monorepo = yes
4 |
5 | # Clean Code
6 | Don't try to fit solutions with existent patterns, unless explicitly told to, you can modify implementations and interfaces; prefer clean code over patching existing code just to prevent modifying an interface.
7 |
8 | When you are about to modify an interface, check if the interface you are modifying has been committed or if it's uncommited code. If it's uncommitted code just change it, if it's committed, ask the user for feedback.
9 |
10 | # Don't guess
11 | Prefer asking the user questions rather than guessing. When you are confused or have tried an approach and it doesn't work ALWAYS wait and ask the user for feedback.
12 |
13 | # Don't reimplement code
14 | Whenever you have trouble with an import, don't reimplement existing code, find a solution to fixing the imports or ask for help, but do not copy code or functionality that is implemented somewhere else in the codebase
15 |
--------------------------------------------------------------------------------
/.roo/rules/NOSTR.md:
--------------------------------------------------------------------------------
1 | Publish status updates using publish_task_status_update VERY frequently; before doing something, as you make progress and when you finish. Specially when you are changing direction, or trying different approaches. This is very important. You will be fired if you forget to update your boss about it.
--------------------------------------------------------------------------------
/.tenex.json:
--------------------------------------------------------------------------------
1 | {
2 | "pubkey": "047a38f942f13d44d221ffb0da0fa1ee6ce268ea94fe98ab2cd9100c9a648165",
3 | "title": "NDK",
4 | "nsec": "nsec13dvn7mzaqxj5chjg4s784lhen4ugthngpcawl0zw43na3ge720ys8m9kjv",
5 | "hashtags": [],
6 | "repoUrl": "git@github.com:nostr-dev-kit/ndk.git",
7 | "eventId": "31933:fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52:NDK-s6ot2k"
8 | }
9 |
--------------------------------------------------------------------------------
/BUILD.md:
--------------------------------------------------------------------------------
1 | # Build NDK
2 |
3 | NDK is structured as a monorepo using `bun` as the package manager.
4 |
5 | ```
6 | git clone https://github.com/nostr-dev-kit/ndk
7 | cd ndk
8 | bun install
9 | bun run build
10 | ```
11 |
12 | If you only care about building ndk core and not the family of packages you can just
13 |
14 | ```
15 | git clone https://github.com/nostr-dev-kit/ndk
16 | cd ndk
17 | bun install
18 | cd ndk
19 | bun run build
20 | ```
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Pablo Fernandez
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/2.0.0-beta.1/schema.json",
3 | "formatter": {
4 | "enabled": true,
5 | "formatWithErrors": false,
6 | "indentStyle": "space",
7 | "indentWidth": 4,
8 | "lineWidth": 120
9 | },
10 | "linter": {
11 | "enabled": true,
12 | "rules": {
13 | "recommended": true
14 | }
15 | },
16 | "javascript": {
17 | "formatter": {
18 | "quoteStyle": "double",
19 | "trailingCommas": "all"
20 | }
21 | },
22 | "files": {
23 | "includes": [
24 | "**/*.{js,ts,jsx,tsx,json}",
25 | "!**/dist/**",
26 | "!**/docs/.vitepress/cache/**",
27 | "!ndk-cache-sqlite-wasm/example/**",
28 | "!**/node_modules/**"
29 | ],
30 | "ignoreUnknown": true
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/context/SPEC.md:
--------------------------------------------------------------------------------
1 | NDK
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.ts:
--------------------------------------------------------------------------------
1 | import type { Theme } from "vitepress";
2 | import DefaultTheme from "vitepress/theme";
3 | // https://vitepress.dev/guide/custom-theme
4 | import { h } from "vue";
5 | import "./style.css";
6 |
7 | export default {
8 | extends: DefaultTheme,
9 | Layout: () => {
10 | return h(DefaultTheme.Layout, null, {
11 | // https://vitepress.dev/guide/extending-default-theme#layout-slots
12 | });
13 | },
14 | enhanceApp({ app, router, siteData }) {
15 | // ...
16 | },
17 | } satisfies Theme;
18 |
--------------------------------------------------------------------------------
/docs/api-examples.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Runtime API Examples
6 |
7 | This page demonstrates usage of some of the runtime APIs provided by VitePress.
8 |
9 | The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
10 |
11 | ```md
12 |
17 |
18 | ## Results
19 |
20 | ### Theme Data
21 |
{{ theme }}
22 |
23 | ### Page Data
24 | {{ page }}
25 |
26 | ### Page Frontmatter
27 | {{ frontmatter }}
28 | ```
29 |
30 |
35 |
36 | ## Results
37 |
38 | ### Theme Data
39 | {{ theme }}
40 |
41 | ### Page Data
42 | {{ page }}
43 |
44 | ### Page Frontmatter
45 | {{ frontmatter }}
46 |
47 | ## More
48 |
49 | Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).
50 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "NDK Documentation"
7 | tagline: "Nostr Development Kit Docs"
8 | actions:
9 | - theme: brand
10 | text: Getting Started
11 | link: /getting-started/introduction.html
12 | - theme: secondary
13 | text: References
14 | link: https://github.com/nostr-dev-kit/ndk/blob/master/REFERENCES.md
15 |
16 | ---
17 |
18 | NDK is a nostr development kit that makes the experience of building Nostr-related applications, whether they are relays, clients, or anything in between, better, more reliable and overall nicer to work with than existing solutions.
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "author": "",
9 | "license": "ISC",
10 | "description": "",
11 | "dependencies": {
12 | "vitepress": "^1.6.3"
13 | },
14 | "devDependencies": {
15 | "vitepress-plugin-mermaid": "^2.0.17"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ndk-blossom/example/blossom-upload/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ndk-blossom-upload-example",
3 | "private": true,
4 | "version": "0.1.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@nostr-dev-kit/ndk": "^2.0.0",
13 | "@nostr-dev-kit/ndk-blossom": "^0.1.1",
14 | "@nostr-dev-kit/ndk-hooks": "workspace:*",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0"
17 | },
18 | "devDependencies": {
19 | "@types/react": "^18.2.15",
20 | "@types/react-dom": "^18.2.7",
21 | "@vitejs/plugin-react": "^4.0.3",
22 | "typescript": "^5.0.2",
23 | "vite": "^4.4.5"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ndk-blossom/example/blossom-upload/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/ndk-blossom/example/blossom-upload/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/ndk-blossom/example/blossom-upload/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import { resolve } from "path";
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [react()],
8 | resolve: {
9 | alias: {
10 | // This allows importing from the local ndk-blossom and ndk/ndk-hooks source
11 | "@nostr-dev-kit/ndk-blossom": resolve(__dirname, "../../src"),
12 | "@nostr-dev-kit/ndk": resolve(__dirname, "../../../ndk-core/src"),
13 | "@nostr-dev-kit/ndk-hooks": resolve(__dirname, "../../../ndk-hooks/src"),
14 | },
15 | },
16 | optimizeDeps: {
17 | include: ["react", "react-dom"],
18 | },
19 | // Explicitly set the base directory to ensure paths resolve correctly
20 | root: __dirname,
21 | // Configure the build output
22 | build: {
23 | outDir: "dist",
24 | rollupOptions: {
25 | input: {
26 | main: resolve(__dirname, "index.html"),
27 | },
28 | },
29 | },
30 | // Configure the dev server
31 | server: {
32 | port: 5173,
33 | open: true,
34 | },
35 | });
36 |
--------------------------------------------------------------------------------
/ndk-blossom/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * NDK-Blossom - Blossom protocol support for NDK
3 | *
4 | * This package extends NDK with support for the Blossom protocol,
5 | * allowing you to easily upload, manage, and fix URLs for blobs
6 | * (binary data like images, videos, etc.) stored on Blossom servers.
7 | */
8 |
9 | // Export the main class and types from the blossom module
10 | export { default as NDKBlossom } from "./blossom";
11 | export * from "./blossom";
12 |
13 | // Export utility functions for direct usage if needed
14 | export { extractHashFromUrl } from "./healing/url-healing";
15 |
16 | // Export SHA256 utilities
17 | export { SHA256Calculator } from "./types";
18 | export { DefaultSHA256Calculator, defaultSHA256Calculator } from "./utils/sha256";
19 |
20 | // Set default export
21 | import NDKBlossom from "./blossom";
22 | export default NDKBlossom;
23 |
--------------------------------------------------------------------------------
/ndk-blossom/src/utils/constants.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Kind number for Blossom authorization events (BUD-01)
3 | */
4 | export const BLOSSOM_AUTH_EVENT_KIND = 24242;
5 |
6 | /**
7 | * Default retry options
8 | */
9 | export const DEFAULT_RETRY_OPTIONS = {
10 | maxRetries: 3,
11 | retryDelay: 1000,
12 | backoffFactor: 1.5,
13 | retryableStatusCodes: [408, 429, 500, 502, 503, 504],
14 | };
15 |
16 | /**
17 | * Default headers for requests
18 | */
19 | export const DEFAULT_HEADERS = {
20 | Accept: "application/json",
21 | };
22 |
23 | /**
24 | * Debug namespace for NDK-Blossom
25 | */
26 | export const DEBUG_NAMESPACE = "ndk:blossom";
27 |
28 | /**
29 | * HTTP status codes for server errors
30 | */
31 | export const SERVER_ERROR_STATUS_CODES = [500, 501, 502, 503, 504, 505];
32 |
--------------------------------------------------------------------------------
/ndk-blossom/src/utils/sha256.ts:
--------------------------------------------------------------------------------
1 | import { SHA256Calculator } from "../types";
2 |
3 | /**
4 | * Default implementation of SHA256 calculator
5 | * Uses Web Crypto API
6 | */
7 | export class DefaultSHA256Calculator implements SHA256Calculator {
8 | /**
9 | * Calculate SHA256 hash of a file
10 | *
11 | * @param file File to hash
12 | * @returns Hash as hex string
13 | */
14 | async calculateSha256(file: File): Promise {
15 | // Convert file to ArrayBuffer
16 | const buffer = await file.arrayBuffer();
17 |
18 | // Hash the buffer
19 | const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
20 |
21 | // Convert to hex string
22 | return Array.from(new Uint8Array(hashBuffer))
23 | .map((b) => b.toString(16).padStart(2, "0"))
24 | .join("");
25 | }
26 | }
27 |
28 | /**
29 | * Singleton instance of the default SHA256 calculator
30 | */
31 | export const defaultSHA256Calculator = new DefaultSHA256Calculator();
32 |
--------------------------------------------------------------------------------
/ndk-blossom/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "ESNext",
5 | "moduleResolution": "node",
6 | "esModuleInterop": true,
7 | "strict": true,
8 | "skipLibCheck": true,
9 | "declaration": true,
10 | "declarationMap": true,
11 | "sourceMap": true,
12 | "outDir": "./dist",
13 | "rootDir": "./src",
14 | "lib": ["ES2020", "DOM"]
15 | },
16 | "include": ["src/**/*"],
17 | "exclude": ["node_modules", "dist"]
18 | }
19 |
--------------------------------------------------------------------------------
/ndk-cache-dexie/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | **/*.js
4 | dist
5 | docs
6 |
--------------------------------------------------------------------------------
/ndk-cache-dexie/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
--------------------------------------------------------------------------------
/ndk-cache-dexie/README.md:
--------------------------------------------------------------------------------
1 | # ndk-cache-dexie
2 |
3 | NDK cache adapter for [Dexie](https://dexie.org/). Dexie is a wrapper around IndexedDB, an in-browser database.
4 |
5 | ## Usage
6 |
7 | NDK will attempt to use the Dexie adapter to store users, events, and tags. The default behaviour is to always check the cache first and then hit relays, replacing older cached events as needed.
8 |
9 | ### Install
10 |
11 | ```
12 | pnpm add @nostr-dev-kit/ndk-cache-dexie
13 | ```
14 |
15 | ### Add as a cache adapter
16 |
17 | ```ts
18 | import NDKCacheAdapterDexie from "@nostr-dev-kit/ndk-cache-dexie";
19 |
20 | const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'your-db-name' });
21 | const ndk = new NDK({cacheAdapter: dexieAdapter, ...other config options});
22 | ```
23 |
24 | 🚨 Because Dexie only exists client-side, this cache adapter will not work in pure node.js environments. You'll need to make sure that you're using the right cache adapter in the right place (e.g. Redis on the backend, Dexie on the frontend).
25 |
26 | # License
27 |
28 | MIT
29 |
--------------------------------------------------------------------------------
/ndk-cache-dexie/previous-head-with-ndk-hooks:
--------------------------------------------------------------------------------
1 | b046798b5a732869908244e8252187193b281a1a
2 |
--------------------------------------------------------------------------------
/ndk-cache-dexie/src/caches/event-tags.ts:
--------------------------------------------------------------------------------
1 | import type debug from "debug";
2 | import type { Table } from "dexie";
3 | import type { LRUCache } from "typescript-lru-cache";
4 | import type { EventTag } from "../db";
5 | import type { CacheHandler } from "../lru-cache";
6 |
7 | export type EventTagCacheEntry = string;
8 |
9 | export async function eventTagsWarmUp(cacheHandler: CacheHandler, eventTags: Table) {
10 | const array = await eventTags.limit(cacheHandler.maxSize).toArray();
11 | for (const event of array) {
12 | cacheHandler.add(event.tagValue, event.eventId, false);
13 | }
14 | }
15 |
16 | export const eventTagsDump = (eventTags: Table, debug: debug.IDebugger) => {
17 | return async (dirtyKeys: Set, cache: LRUCache) => {
18 | const entries = [];
19 |
20 | for (const tagValue of dirtyKeys) {
21 | const eventIds = cache.get(tagValue);
22 | if (eventIds) {
23 | for (const eventId of eventIds) entries.push({ tagValue, eventId });
24 | }
25 | }
26 |
27 | if (entries.length > 0) {
28 | debug(`Saving ${entries.length} events cache entries to database`);
29 | await eventTags.bulkPut(entries);
30 | }
31 |
32 | dirtyKeys.clear();
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/ndk-cache-dexie/src/caches/events.ts:
--------------------------------------------------------------------------------
1 | import type debug from "debug";
2 | import type { Table } from "dexie";
3 | import type { LRUCache } from "typescript-lru-cache";
4 | import type { Event } from "../db";
5 | import type { CacheHandler } from "../lru-cache";
6 |
7 | export type EventCacheEntry = Event;
8 |
9 | export async function eventsWarmUp(cacheHandler: CacheHandler, events: Table) {
10 | const array = await events.limit(cacheHandler.maxSize).toArray();
11 | for (const event of array) {
12 | cacheHandler.set(event.id, event, false);
13 | }
14 | }
15 |
16 | export const eventsDump = (events: Table, debug: debug.IDebugger) => {
17 | return async (dirtyKeys: Set, cache: LRUCache) => {
18 | const entries: EventCacheEntry[] = [];
19 |
20 | for (const event of dirtyKeys) {
21 | const entry = cache.get(event);
22 | if (entry) entries.push(entry);
23 | }
24 |
25 | if (entries.length > 0) {
26 | debug(`Saving ${entries.length} events cache entries to database`);
27 | await events.bulkPut(entries);
28 | }
29 |
30 | dirtyKeys.clear();
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/ndk-cache-dexie/src/caches/nip05.ts:
--------------------------------------------------------------------------------
1 | import type debug from "debug";
2 | import type { Table } from "dexie";
3 | import type { LRUCache } from "typescript-lru-cache";
4 | import type { Nip05 } from "../db";
5 | import type { CacheHandler } from "../lru-cache";
6 |
7 | export type Nip05CacheEntry = {
8 | profile: string | null;
9 | fetchedAt: number;
10 | };
11 |
12 | export async function nip05WarmUp(cacheHandler: CacheHandler, nip05s: Table) {
13 | const array = await nip05s.limit(cacheHandler.maxSize).toArray();
14 | for (const nip05 of array) {
15 | cacheHandler.set(nip05.nip05, nip05, false);
16 | }
17 | }
18 |
19 | export const nip05Dump = (nip05s: Table, debug: debug.IDebugger) => {
20 | return async (dirtyKeys: Set, cache: LRUCache) => {
21 | const entries = [];
22 |
23 | for (const nip05 of dirtyKeys) {
24 | const entry = cache.get(nip05);
25 | if (entry) {
26 | entries.push({
27 | nip05,
28 | ...entry,
29 | });
30 | }
31 | }
32 |
33 | if (entries.length) {
34 | debug(`Saving ${entries.length} NIP-05 cache entries to database`);
35 | await nip05s.bulkPut(entries);
36 | }
37 |
38 | dirtyKeys.clear();
39 | };
40 | };
41 |
--------------------------------------------------------------------------------
/ndk-cache-dexie/src/caches/profiles.ts:
--------------------------------------------------------------------------------
1 | import type { Table } from "dexie";
2 | import type { LRUCache } from "typescript-lru-cache";
3 | import type { Profile } from "../db";
4 | import type { CacheHandler } from "../lru-cache";
5 | export { db } from "../db.js";
6 | import createDebug from "debug";
7 |
8 | const d = createDebug("ndk:dexie-adapter:profiles");
9 |
10 | export async function profilesWarmUp(cacheHandler: CacheHandler, profiles: Table): Promise {
11 | const array = await profiles.limit(cacheHandler.maxSize).toArray();
12 | for (const user of array) {
13 | const obj = user;
14 | cacheHandler.set(user.pubkey, obj, false);
15 | }
16 |
17 | d("Loaded %d profiles from database", cacheHandler.size());
18 | }
19 |
20 | export const profilesDump = (profiles: Table, debug: debug.IDebugger) => {
21 | return async (dirtyKeys: Set, cache: LRUCache) => {
22 | const entries = [];
23 |
24 | for (const pubkey of dirtyKeys) {
25 | const entry = cache.get(pubkey);
26 | if (entry) {
27 | entries.push(entry);
28 | }
29 | }
30 |
31 | if (entries.length) {
32 | debug(`Saving ${entries.length} users to database`);
33 |
34 | await profiles.bulkPut(entries);
35 | }
36 |
37 | dirtyKeys.clear();
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/ndk-cache-dexie/src/caches/zapper.ts:
--------------------------------------------------------------------------------
1 | import type debug from "debug";
2 | import type { Table } from "dexie";
3 | import type { LRUCache } from "typescript-lru-cache";
4 | import type { Lnurl } from "../db";
5 | import type { CacheHandler } from "../lru-cache";
6 |
7 | export type ZapperCacheEntry = {
8 | document: string | null;
9 | fetchedAt: number;
10 | };
11 |
12 | export async function zapperWarmUp(cacheHandler: CacheHandler, lnurls: Table) {
13 | const array = await lnurls.limit(cacheHandler.maxSize).toArray();
14 | for (const lnurl of array) {
15 | cacheHandler.set(lnurl.pubkey, { document: lnurl.document, fetchedAt: lnurl.fetchedAt }, false);
16 | }
17 | }
18 |
19 | export const zapperDump = (lnurls: Table, debug: debug.IDebugger) => {
20 | return async (dirtyKeys: Set, cache: LRUCache) => {
21 | const entries = [];
22 |
23 | for (const pubkey of dirtyKeys) {
24 | const entry = cache.get(pubkey);
25 | if (entry) {
26 | entries.push({
27 | pubkey,
28 | ...entry,
29 | });
30 | }
31 | }
32 |
33 | if (entries.length) {
34 | debug(`Saving ${entries.length} zapper cache entries to database`);
35 | await lnurls.bulkPut(entries);
36 | }
37 |
38 | dirtyKeys.clear();
39 | };
40 | };
41 |
--------------------------------------------------------------------------------
/ndk-cache-dexie/test/setup.ts:
--------------------------------------------------------------------------------
1 | import "fake-indexeddb/auto";
2 | import { vi } from "vitest";
3 |
4 | // Mock the debug module
5 | vi.mock("debug", () => {
6 | return {
7 | default: () => {
8 | const debugFn = (..._args: any[]) => {};
9 | debugFn.extend = () => debugFn;
10 | return debugFn;
11 | },
12 | };
13 | });
14 |
--------------------------------------------------------------------------------
/ndk-cache-dexie/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "composite": false,
6 | "declaration": true,
7 | "declarationMap": true,
8 | "esModuleInterop": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "inlineSources": false,
11 | "isolatedModules": true,
12 | "moduleResolution": "node",
13 | "noUnusedLocals": false,
14 | "noUnusedParameters": false,
15 | "preserveWatchOutput": true,
16 | "skipLibCheck": true,
17 | "strict": true,
18 | "allowJs": true,
19 | "checkJs": true
20 | },
21 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts"],
22 | "exclude": ["dist", "build", "node_modules"]
23 | }
24 |
--------------------------------------------------------------------------------
/ndk-cache-dexie/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "entryPoints": ["src/index.ts"],
3 | "out": "docs",
4 | "name": "NDK Dexie Cache Adapter",
5 | "theme": "default",
6 | "plugin": ["typedoc-plugin-markdown"],
7 | "excludeExternals": true,
8 | "excludePrivate": true,
9 | "excludeProtected": true,
10 | "categorizeByGroup": true,
11 | "hideParameterTypesInTitle": false,
12 | "navigation": {
13 | "includeGroups": true
14 | },
15 | "customCss": "../ndk/docs-styles.css"
16 | }
17 |
--------------------------------------------------------------------------------
/ndk-cache-dexie/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | test: {
5 | environment: "node",
6 | setupFiles: ["./test/setup.ts"],
7 | globals: true,
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/ndk-cache-nostr/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | **/*.js
3 | dist
4 |
--------------------------------------------------------------------------------
/ndk-cache-nostr/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/ndk-cache-nostr/README.md:
--------------------------------------------------------------------------------
1 | # ndk-cache-nostr
2 |
3 | NDK cache adapter using a nostr relay as the database.
4 |
5 | This cache adapter is meant to be run against a local relay. This adapter will generate two NDK instances:
6 |
7 | `ndk` -- This talks exclusively to the local relay, with outbox model disabled.
8 | `fallbackNdk` -- This is used to hydrate the cache and uses the outbox model -- each query the cache receives is placed in a queue in the background so that subsequent requests can be served from the cache. All events from other relays
9 |
10 | ## Usage
11 |
12 | ### Install
13 |
14 | ```
15 | npm add @nostr-dev-kit/ndk-cache-nostr
16 |
17 | ```
18 |
19 | ### Add as a cache adapter
20 |
21 | ```ts
22 | import NDKCacheAdapterNostr from "@nostr-dev-kit/ndk-cache-nostr";
23 |
24 | const cacheAdapter = new NDKCacheAdapterNostr({
25 | relayUrl: "ws://localhost:5577",
26 | });
27 | const ndk = new NDK({ cacheAdapter });
28 | ```
29 |
30 | If running server-side in a NodeJS environment, you should make sure to polyfill `WebSocket`.
31 |
32 | # License
33 |
34 | MIT
35 |
--------------------------------------------------------------------------------
/ndk-cache-nostr/docs/cache/nostr.md:
--------------------------------------------------------------------------------
1 | # Nostr Cache Adapter
2 |
3 | NDK cache adapter using a nostr relay as the database.
4 |
5 | This cache adapter is meant to be run against a local relay. This adapter will generate two NDK instances:
6 |
7 | `ndk` -- This talks exclusively to the local relay, with outbox model disabled.
8 | `fallbackNdk` -- This is used to hydrate the cache and uses the outbox model -- each query the cache receives is placed in a queue in the background so that subsequent requests can be served from the cache. All events from other relays
9 |
10 | ## Usage
11 |
12 | ### Install
13 |
14 | ```
15 | npm add @nostr-dev-kit/ndk-cache-nostr
16 |
17 | ```
18 |
19 | ### Add as a cache adapter
20 |
21 | ```ts
22 | import NDKCacheAdapterNostr from "@nostr-dev-kit/ndk-cache-nostr";
23 |
24 | const cacheAdapter = new NDKCacheAdapterNostr({
25 | relayUrl: 'ws://localhost:5577',
26 | });
27 | const ndk = new NDK({ cacheAdapter });
28 | ```
29 |
30 | If running server-side in a NodeJS environment, you should make sure to polyfill `WebSocket`.
31 |
32 | # License
33 |
34 | MIT
35 |
--------------------------------------------------------------------------------
/ndk-cache-nostr/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "composite": false,
6 | "declaration": true,
7 | "declarationMap": true,
8 | "esModuleInterop": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "inlineSources": false,
11 | "isolatedModules": true,
12 | "moduleResolution": "node",
13 | "noUnusedLocals": false,
14 | "noUnusedParameters": false,
15 | "preserveWatchOutput": true,
16 | "skipLibCheck": true,
17 | "strict": true,
18 | "allowJs": true,
19 | "checkJs": true
20 | },
21 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts"],
22 | "exclude": ["dist", "build", "node_modules"]
23 | }
24 |
--------------------------------------------------------------------------------
/ndk-cache-redis/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | **/*.js
3 | dist
4 |
--------------------------------------------------------------------------------
/ndk-cache-redis/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/ndk-cache-redis/README.md:
--------------------------------------------------------------------------------
1 | # ndk-cache-redis
2 |
3 | NDK cache adapter for redis.
4 |
5 | This cache is mostly a skeleton; the cache hit logic is very basic and only checks if
6 | a query is using precisely `kinds` and `authors` filtering.
7 |
8 | ## Usage
9 |
10 | ### Install
11 |
12 | ```
13 | npm add @nostr-dev-kit/ndk-cache-redis
14 | ```
15 |
16 | ### Add as a cache adapter
17 |
18 | ```ts
19 | import NDKRedisCacheAdapter from "@nostr-dev-kit/ndk-cache-redis";
20 |
21 | const cacheAdapter = new NDKRedisCacheAdapter();
22 | const ndk = new NDK({ cacheAdapter });
23 | ```
24 |
25 | # License
26 |
27 | MIT
28 |
--------------------------------------------------------------------------------
/ndk-cache-redis/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nostr-dev-kit/ndk-cache-redis",
3 | "version": "0.1.22",
4 | "description": "NDK Redis Cache Adapter",
5 | "type": "module",
6 | "main": "./dist/index.js",
7 | "module": "./dist/index.mjs",
8 | "types": "./dist/index.d.ts",
9 | "license": "MIT",
10 | "files": [
11 | "dist"
12 | ],
13 | "scripts": {
14 | "build": "tsup src/index.ts --format cjs,esm --dts",
15 | "clean": "rm -rf dist"
16 | },
17 | "dependencies": {
18 | "@nostr-dev-kit/ndk": "2.14.24",
19 | "ioredis": "^5.0.0"
20 | },
21 | "devDependencies": {
22 | "@types/ioredis": "^5.0.0",
23 | "@types/node": "^20.0.0",
24 | "tsup": "^8",
25 | "typescript": "^5.8.2"
26 | },
27 | "repository": {
28 | "type": "git",
29 | "url": "https://github.com/nostr-dev-kit/ndk"
30 | },
31 | "keywords": [
32 | "nostr",
33 | "redis",
34 | "cache"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/ndk-cache-redis/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "composite": false,
6 | "declaration": true,
7 | "declarationMap": true,
8 | "esModuleInterop": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "inlineSources": false,
11 | "isolatedModules": true,
12 | "moduleResolution": "node",
13 | "noUnusedLocals": false,
14 | "noUnusedParameters": false,
15 | "preserveWatchOutput": true,
16 | "skipLibCheck": true,
17 | "strict": true,
18 | "allowJs": true,
19 | "checkJs": true,
20 | "types": ["node"]
21 | },
22 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts"],
23 | "exclude": ["dist", "build", "node_modules"]
24 | }
25 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/example/sql-wasm.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nostr-dev-kit/ndk/7617d9356f14774a2e646f831f023ba09ce5ef34/ndk-cache-sqlite-wasm/example/sql-wasm.wasm
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/example/vite/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/example/vite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/example/vite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "typescript": "~5.7.2",
13 | "vite": "^6.3.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/example/vite/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/example/vite/src/counter.ts:
--------------------------------------------------------------------------------
1 | export function setupCounter(element: HTMLButtonElement) {
2 | let counter = 0;
3 | const setCounter = (count: number) => {
4 | counter = count;
5 | element.innerHTML = `count is ${counter}`;
6 | };
7 | element.addEventListener("click", () => setCounter(counter + 1));
8 | setCounter(0);
9 | }
10 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/example/vite/src/typescript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/example/vite/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "isolatedModules": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "noUncheckedSideEffectImports": true
22 | },
23 | "include": ["src"]
24 | }
25 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nostr-dev-kit/ndk-cache-sqlite-wasm",
3 | "version": "0.5.8",
4 | "description": "SQLite WASM cache adapter for NDK, compatible with browser and JS environments.",
5 | "main": "dist/index.mjs",
6 | "module": "dist/index.mjs",
7 | "types": "dist/index.d.ts",
8 | "exports": {
9 | ".": {
10 | "import": "./dist/index.mjs",
11 | "require": "./dist/index.mjs",
12 | "types": "./dist/index.d.ts"
13 | },
14 | "./sql-wasm.wasm": "./dist/sql-wasm.wasm"
15 | },
16 | "files": [
17 | "dist",
18 | "dist/sql-wasm.wasm",
19 | "docs"
20 | ],
21 | "scripts": {
22 | "build": "tsc --emitDeclarationOnly && bun build ./src/index.ts --outfile ./dist/index.mjs --format esm --target browser && bun build ./src/worker.ts --outfile ./dist/worker.js --format esm --target browser && cp ./example/sql-wasm.wasm ./dist/sql-wasm.wasm",
23 | "prepublishOnly": "bun run build"
24 | },
25 | "dependencies": {
26 | "sql.js": "^1.8.0"
27 | },
28 | "devDependencies": {
29 | "@nostr-dev-kit/ndk": "^2.14.23",
30 | "@types/sql.js": "^1.4.9",
31 | "typescript": "^5.0.0"
32 | },
33 | "license": "MIT"
34 | }
35 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/db/indexeddb-utils.ts:
--------------------------------------------------------------------------------
1 | export function openIndexedDB(dbName: string): Promise {
2 | return new Promise((resolve, reject) => {
3 | const request = indexedDB.open(dbName, 1);
4 | request.onupgradeneeded = () => {
5 | request.result.createObjectStore("db", { keyPath: "id" });
6 | };
7 | request.onsuccess = () => resolve(request.result);
8 | request.onerror = () => reject(request.error);
9 | });
10 | }
11 |
12 | export async function loadFromIndexedDB(dbName: string): Promise {
13 | const db = await openIndexedDB(dbName);
14 | return new Promise((resolve, reject) => {
15 | const tx = db.transaction("db", "readonly");
16 | const store = tx.objectStore("db");
17 | const getReq = store.get("main");
18 | getReq.onsuccess = () => resolve(getReq.result ? getReq.result.data : null);
19 | getReq.onerror = () => reject(getReq.error);
20 | });
21 | }
22 |
23 | export async function saveToIndexedDB(dbName: string, data: Uint8Array): Promise {
24 | const db = await openIndexedDB(dbName);
25 | return new Promise((resolve, reject) => {
26 | const tx = db.transaction("db", "readwrite");
27 | const store = tx.objectStore("db");
28 | const putReq = store.put({ id: "main", data });
29 | putReq.onsuccess = () => {
30 | resolve();
31 | };
32 | putReq.onerror = () => reject(putReq.error);
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/db/migrations.ts:
--------------------------------------------------------------------------------
1 | import { SCHEMA } from "./schema";
2 |
3 | /**
4 | * Runs all necessary database migrations.
5 | * Applies the schema for events, profiles, nutzap_monitor_state, decrypted_events, and unpublished_events tables.
6 | * @param db The SQLite WASM database instance
7 | */
8 | import type { SQLDatabase } from "../types";
9 | export async function runMigrations(db: SQLDatabase): Promise {
10 | db.exec?.(SCHEMA.events);
11 | db.exec?.(SCHEMA.profiles);
12 | db.exec?.(SCHEMA.nutzap_monitor_state);
13 | db.exec?.(SCHEMA.decrypted_events);
14 | db.exec?.(SCHEMA.unpublished_events);
15 | db.exec?.(SCHEMA.event_tags);
16 | }
17 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/functions/addDecryptedEvent.ts:
--------------------------------------------------------------------------------
1 | import type { NDKEvent } from "@nostr-dev-kit/ndk";
2 | import type { NDKCacheAdapterSqliteWasm } from "../index";
3 |
4 | /**
5 | * Adds a decrypted event to the SQLite WASM database.
6 | */
7 | export function addDecryptedEvent(this: NDKCacheAdapterSqliteWasm, event: NDKEvent): void {
8 | if (!this.db) throw new Error("Database not initialized");
9 |
10 | const stmt = `
11 | INSERT OR REPLACE INTO decrypted_events (
12 | id, event
13 | ) VALUES (?, ?)
14 | `;
15 | this.db.run(stmt, [event.id, JSON.stringify(event)]);
16 | }
17 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/functions/addUnpublishedEvent.ts:
--------------------------------------------------------------------------------
1 | import type { NDKEvent } from "@nostr-dev-kit/ndk";
2 | import type { NDKCacheAdapterSqliteWasm } from "../index";
3 |
4 | /**
5 | * Adds an unpublished event to the SQLite WASM database.
6 | * @param event The event to add
7 | * @param relayUrls Array of relay URLs
8 | * @param lastTryAt Timestamp of last try
9 | */
10 | export function addUnpublishedEvent(
11 | this: NDKCacheAdapterSqliteWasm,
12 | event: NDKEvent,
13 | relayUrls: string[],
14 | lastTryAt: number = Date.now(),
15 | ): void {
16 | if (!this.db) throw new Error("Database not initialized");
17 | const stmt = `
18 | INSERT OR REPLACE INTO unpublished_events (
19 | id, event, relays, lastTryAt
20 | ) VALUES (?, ?, ?, ?)
21 | `;
22 | this.db.run(stmt, [event.id, JSON.stringify(event), JSON.stringify(relayUrls), lastTryAt]);
23 | }
24 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/functions/discardUnpublishedEvent.ts:
--------------------------------------------------------------------------------
1 | import type { NDKCacheAdapterSqliteWasm } from "../index";
2 |
3 | /**
4 | * Removes an unpublished event from the SQLite WASM database by event ID.
5 | */
6 | export function discardUnpublishedEvent(this: NDKCacheAdapterSqliteWasm, eventId: string): void {
7 | if (!this.db) throw new Error("Database not initialized");
8 | const stmt = "DELETE FROM unpublished_events WHERE id = ?";
9 | this.db.run(stmt, [eventId]);
10 | }
11 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/functions/fetchProfile.ts:
--------------------------------------------------------------------------------
1 | import type { NDKCacheEntry, NDKUserProfile } from "@nostr-dev-kit/ndk";
2 | import type { NDKCacheAdapterSqliteWasm } from "../index";
3 |
4 | /**
5 | * Fetches a user profile by pubkey from the SQLite WASM database.
6 | */
7 | export async function fetchProfile(
8 | this: NDKCacheAdapterSqliteWasm,
9 | pubkey: string,
10 | ): Promise | null> {
11 | if (!this.db) throw new Error("Database not initialized");
12 |
13 | const stmt = "SELECT profile, updated_at FROM profiles WHERE pubkey = ? LIMIT 1";
14 | const results = this.db.exec(stmt, [pubkey]);
15 | if (results && results.length > 0 && results[0].values && results[0].values.length > 0) {
16 | const [profileStr, updatedAt] = results[0].values[0];
17 | try {
18 | const profile = JSON.parse(profileStr as string);
19 | return { ...profile, cachedAt: updatedAt };
20 | } catch {
21 | return null;
22 | }
23 | }
24 | return null;
25 | }
26 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/functions/fetchProfileSync.ts:
--------------------------------------------------------------------------------
1 | import type { NDKCacheEntry, NDKUserProfile } from "@nostr-dev-kit/ndk";
2 | import type { NDKCacheAdapterSqliteWasm } from "../index";
3 |
4 | /**
5 | * Synchronously fetches a user profile by pubkey from the SQLite WASM database.
6 | */
7 | /**
8 | * Synchronous profile fetch is NOT supported in Web Worker mode.
9 | * BREAKING CHANGE: If useWorker is true, this will throw.
10 | * See CHANGELOG.md for details.
11 | */
12 | export function fetchProfileSync(
13 | this: NDKCacheAdapterSqliteWasm,
14 | pubkey: string,
15 | ): NDKCacheEntry | null {
16 | if (!this.db) throw new Error("Database not initialized");
17 |
18 | const stmt = "SELECT profile, updated_at FROM profiles WHERE pubkey = ? LIMIT 1";
19 | const results = this.db.exec(stmt, [pubkey]);
20 | if (results && results.length > 0 && results[0].values && results[0].values.length > 0) {
21 | const [profileStr, updatedAt] = results[0].values[0];
22 | try {
23 | const profile = JSON.parse(profileStr as string);
24 | return { ...profile, cachedAt: updatedAt };
25 | } catch {
26 | return null;
27 | }
28 | }
29 | return null;
30 | }
31 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/functions/getAllProfilesSync.ts:
--------------------------------------------------------------------------------
1 | import type { Hexpubkey, NDKCacheEntry, NDKUserProfile } from "@nostr-dev-kit/ndk";
2 | import type { NDKCacheAdapterSqliteWasm } from "../index";
3 |
4 | /**
5 | * Synchronously fetches all user profiles from the SQLite WASM database.
6 | */
7 | /**
8 | * Synchronous getAllProfiles is NOT supported in Web Worker mode.
9 | * BREAKING CHANGE: If useWorker is true, this will throw.
10 | * See CHANGELOG.md for details.
11 | */
12 | export function getAllProfilesSync(this: NDKCacheAdapterSqliteWasm): Map> {
13 | if (!this.db) throw new Error("Database not initialized");
14 |
15 | // Initialize the Map to store profiles
16 | const profiles = new Map>();
17 |
18 | const stmt = "SELECT pubkey, profile, updated_at FROM profiles";
19 | const results = this.db.exec(stmt);
20 |
21 | if (results && results.length > 0 && results[0].values && results[0].values.length > 0) {
22 | for (const row of results[0].values) {
23 | const [pubkey, profileStr, updatedAt] = row;
24 | try {
25 | const profile = JSON.parse(profileStr as string);
26 | profiles.set(pubkey as string, { ...profile, cachedAt: updatedAt as number });
27 | } catch {
28 | // skip invalid profile
29 | }
30 | }
31 | }
32 | return profiles;
33 | }
34 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/functions/getDecryptedEvent.ts:
--------------------------------------------------------------------------------
1 | import type { NDKEvent, NDKEventId } from "@nostr-dev-kit/ndk";
2 | import type { NDKCacheAdapterSqliteWasm } from "../index";
3 |
4 | /**
5 | * Retrieves a decrypted event by ID from the SQLite WASM database.
6 | */
7 | export function getDecryptedEvent(this: NDKCacheAdapterSqliteWasm, eventId: NDKEventId): NDKEvent | null {
8 | if (!this.db) throw new Error("Database not initialized");
9 |
10 | const stmt = "SELECT event FROM decrypted_events WHERE id = ? LIMIT 1";
11 | const results = this.db.exec(stmt, [eventId]);
12 | if (results && results.length > 0 && results[0].values && results[0].values.length > 0) {
13 | const eventStr = results[0].values[0][0] as string;
14 | try {
15 | return JSON.parse(eventStr);
16 | } catch {
17 | return null;
18 | }
19 | } else {
20 | console.warn("[WASM] No decrypted event found for ID:", eventId);
21 | }
22 | return null;
23 | }
24 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/functions/getRelayStatus.ts:
--------------------------------------------------------------------------------
1 | import type { NDKCacheRelayInfo } from "@nostr-dev-kit/ndk";
2 | import type { NDKCacheAdapterSqliteWasm } from "../index";
3 |
4 | /**
5 | * Gets relay status from the SQLite WASM database.
6 | * Reads relay info as a JSON string from the relay_status table.
7 | */
8 | export function getRelayStatus(this: NDKCacheAdapterSqliteWasm, relayUrl: string): NDKCacheRelayInfo | undefined {
9 | const stmt = `
10 | CREATE TABLE IF NOT EXISTS relay_status (
11 | url TEXT PRIMARY KEY,
12 | info TEXT
13 | )
14 | `;
15 | if (!this.db) throw new Error("Database not initialized");
16 |
17 | // Create table if it doesn't exist
18 | this.db.run(stmt);
19 |
20 | const select = "SELECT info FROM relay_status WHERE url = ? LIMIT 1";
21 | const results = this.db.exec(select, [relayUrl]);
22 | if (results && results.length > 0 && results[0].values && results[0].values.length > 0) {
23 | const infoStr = results[0].values[0][0] as string;
24 | try {
25 | return JSON.parse(infoStr);
26 | } catch {
27 | return undefined;
28 | }
29 | }
30 | return undefined;
31 | }
32 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/functions/getUnpublishedEvents.ts:
--------------------------------------------------------------------------------
1 | import type { NDKEvent } from "@nostr-dev-kit/ndk";
2 | import type { NDKCacheAdapterSqliteWasm } from "../index";
3 |
4 | /**
5 | * Retrieves all unpublished events from the SQLite WASM database.
6 | * Returns an array of { event, relays, lastTryAt }
7 | */
8 | export async function getUnpublishedEvents(
9 | this: NDKCacheAdapterSqliteWasm,
10 | ): Promise<{ event: NDKEvent; relays?: string[]; lastTryAt?: number }[]> {
11 | if (!this.db) throw new Error("Database not initialized");
12 |
13 | const events: { event: NDKEvent; relays?: string[]; lastTryAt?: number }[] = [];
14 | const stmt = "SELECT id, event, relays, lastTryAt FROM unpublished_events";
15 | const results = this.db.exec(stmt);
16 |
17 | if (results && results.length > 0 && results[0].values && results[0].values.length > 0) {
18 | for (const row of results[0].values) {
19 | const [id, eventStr, relaysStr, lastTryAt] = row;
20 | try {
21 | const event = JSON.parse(eventStr as string);
22 | const relays = relaysStr ? JSON.parse(relaysStr as string) : [];
23 | events.push({ event, relays, lastTryAt: lastTryAt as number });
24 | } catch {
25 | // skip invalid
26 | }
27 | }
28 | }
29 | return events;
30 | }
31 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/functions/saveProfile.ts:
--------------------------------------------------------------------------------
1 | import type { NDKUserProfile } from "@nostr-dev-kit/ndk";
2 | import type { NDKCacheAdapterSqliteWasm } from "../index";
3 |
4 | /**
5 | * Saves a user profile by pubkey to the SQLite WASM database.
6 | */
7 | export async function saveProfile(
8 | this: NDKCacheAdapterSqliteWasm,
9 | pubkey: string,
10 | profile: NDKUserProfile,
11 | ): Promise {
12 | if (!this.db) throw new Error("Database not initialized");
13 |
14 | const stmt = `
15 | INSERT OR REPLACE INTO profiles (
16 | pubkey, profile, updated_at
17 | ) VALUES (?, ?, ?)
18 | `;
19 | const profileStr = JSON.stringify(profile);
20 | const updatedAt = Math.floor(Date.now() / 1000);
21 | this.db.run(stmt, [pubkey, profileStr, updatedAt]);
22 | }
23 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/functions/updateRelayStatus.ts:
--------------------------------------------------------------------------------
1 | import type { NDKCacheRelayInfo } from "@nostr-dev-kit/ndk";
2 | import type { NDKCacheAdapterSqliteWasm } from "../index";
3 |
4 | /**
5 | * Updates relay status in the SQLite WASM database.
6 | * Stores relay info as a JSON string in a dedicated table.
7 | */
8 | export function updateRelayStatus(this: NDKCacheAdapterSqliteWasm, relayUrl: string, info: NDKCacheRelayInfo): void {
9 | if (!this.db) throw new Error("Database not initialized");
10 |
11 | const stmt = `
12 | CREATE TABLE IF NOT EXISTS relay_status (
13 | url TEXT PRIMARY KEY,
14 | info TEXT
15 | );
16 | `;
17 | this.db.run(stmt);
18 | const upsert = `
19 | INSERT OR REPLACE INTO relay_status (url, info)
20 | VALUES (?, ?)
21 | `;
22 | this.db.run(upsert, [relayUrl, JSON.stringify(info)]);
23 | }
24 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/src/types.ts:
--------------------------------------------------------------------------------
1 | // Import the actual sql.js types
2 | import type initSqlJs from "sql.js";
3 |
4 | export interface NDKCacheAdapterSqliteWasmOptions {
5 | dbName?: string;
6 | wasmUrl?: string;
7 | useWorker?: boolean;
8 | workerUrl?: string;
9 | }
10 |
11 | export type WorkerMessage = {
12 | id: string;
13 | type: string;
14 | payload?: unknown;
15 | };
16 |
17 | export type WorkerResponse = {
18 | id: string;
19 | result?: unknown;
20 | error?: {
21 | message: string;
22 | stack?: string;
23 | };
24 | };
25 |
26 | // Re-export sql.js types for convenience
27 | export type QueryExecResult = initSqlJs.QueryExecResult;
28 | export type Database = initSqlJs.Database;
29 |
30 | // Extended Database type that includes our custom methods added in wasm-loader
31 | export type SQLDatabase = Database & {
32 | _scheduleSave: () => void;
33 | saveToIndexedDB: () => Promise;
34 | };
35 |
36 | // Legacy type alias for backward compatibility
37 | export type SQLQueryResult = QueryExecResult;
38 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite-wasm/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "declaration": true,
7 | "outDir": "dist",
8 | "rootDir": "src",
9 | "strict": true,
10 | "esModuleInterop": true,
11 | "skipLibCheck": true
12 | },
13 | "include": ["src/**/*.ts"],
14 | "exclude": ["dist", "node_modules"]
15 | }
16 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ndk-cache-sqlite-example",
3 | "version": "1.0.0",
4 | "description": "Example app demonstrating NDK SQLite cache adapter functionality",
5 | "type": "module",
6 | "scripts": {
7 | "start": "bun run src/index.ts",
8 | "start:node": "npx tsx src/index.ts",
9 | "dev": "bun --watch src/index.ts",
10 | "dev:node": "npx tsx --watch src/index.ts",
11 | "build": "bun build src/index.ts --outdir dist --target node",
12 | "clean": "rm -rf dist cache.db"
13 | },
14 | "dependencies": {
15 | "@nostr-dev-kit/ndk": "^2.14.23",
16 | "better-sqlite3": "^9.0.0"
17 | },
18 | "devDependencies": {
19 | "@types/node": "^20.0.0",
20 | "typescript": "^5.0.0",
21 | "tsx": "^4.0.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "ESNext",
5 | "moduleResolution": "bundler",
6 | "allowImportingTsExtensions": true,
7 | "allowSyntheticDefaultImports": true,
8 | "esModuleInterop": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "strict": true,
11 | "noEmit": true,
12 | "skipLibCheck": true,
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "verbatimModuleSyntax": true,
16 | "declaration": true,
17 | "declarationMap": true,
18 | "sourceMap": true,
19 | "outDir": "dist"
20 | },
21 | "include": ["src/**/*", "../src/**/*"],
22 | "exclude": ["node_modules", "dist"]
23 | }
24 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nostr-dev-kit/ndk-cache-sqlite",
3 | "version": "0.1.0",
4 | "description": "SQLite cache adapter for NDK using better-sqlite3, compatible with Node.js environments.",
5 | "main": "dist/index.js",
6 | "module": "dist/index.mjs",
7 | "types": "dist/index.d.ts",
8 | "exports": {
9 | ".": {
10 | "import": "./dist/index.mjs",
11 | "require": "./dist/index.js",
12 | "types": "./dist/index.d.ts"
13 | }
14 | },
15 | "files": [
16 | "dist",
17 | "docs"
18 | ],
19 | "scripts": {
20 | "build": "tsc --emitDeclarationOnly && bun build ./src/index.ts --outfile ./dist/index.mjs --format esm --target node && bun build ./src/index.ts --outfile ./dist/index.js --format cjs --target node",
21 | "test": "vitest",
22 | "test:run": "vitest run",
23 | "prepublishOnly": "bun run build"
24 | },
25 | "dependencies": {
26 | "better-sqlite3": "^9.0.0"
27 | },
28 | "devDependencies": {
29 | "@nostr-dev-kit/ndk": "^2.14.23",
30 | "@types/better-sqlite3": "^7.6.8",
31 | "typescript": "^5.0.0",
32 | "vitest": "^1.0.0"
33 | },
34 | "license": "MIT"
35 | }
36 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite/src/db/migrations.ts:
--------------------------------------------------------------------------------
1 | import { SCHEMA } from "./schema";
2 | import type { SQLiteDatabase } from "../types";
3 |
4 | /**
5 | * Runs all necessary database migrations.
6 | * Applies the schema for events, profiles, nutzap_monitor_state, decrypted_events, and unpublished_events tables.
7 | * @param db The better-sqlite3 database instance
8 | */
9 | export function runMigrations(db: SQLiteDatabase): void {
10 | db.exec(SCHEMA.events);
11 | db.exec(SCHEMA.profiles);
12 | db.exec(SCHEMA.nutzap_monitor_state);
13 | db.exec(SCHEMA.decrypted_events);
14 | db.exec(SCHEMA.unpublished_events);
15 | db.exec(SCHEMA.event_tags);
16 | db.exec(SCHEMA.relay_status);
17 | }
18 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite/src/functions/fetchProfile.ts:
--------------------------------------------------------------------------------
1 | import type { NDKCacheEntry, NDKUserProfile } from "@nostr-dev-kit/ndk";
2 | import type { NDKCacheAdapterSqlite } from "../index";
3 |
4 | /**
5 | * Fetches a user profile by pubkey from the SQLite database using better-sqlite3.
6 | */
7 | export async function fetchProfile(
8 | this: NDKCacheAdapterSqlite,
9 | pubkey: string,
10 | ): Promise | null> {
11 | if (!this.db) throw new Error("Database not initialized");
12 |
13 | const stmt = "SELECT profile, updated_at FROM profiles WHERE pubkey = ? LIMIT 1";
14 |
15 | try {
16 | const prepared = this.db.getDatabase().prepare(stmt);
17 | const result = prepared.get(pubkey) as { profile?: string; updated_at?: number } | undefined;
18 |
19 | if (result && result.profile) {
20 | try {
21 | const profile = JSON.parse(result.profile);
22 | return { ...profile, cachedAt: result.updated_at };
23 | } catch {
24 | return null;
25 | }
26 | }
27 | return null;
28 | } catch (e) {
29 | console.error("Error fetching profile:", e);
30 | return null;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite/src/functions/getEvent.ts:
--------------------------------------------------------------------------------
1 | import { NDKEvent } from "@nostr-dev-kit/ndk";
2 | import type { DatabaseWrapper } from "../db/database";
3 | import type NDK from "@nostr-dev-kit/ndk";
4 |
5 | /**
6 | * Retrieves an event by ID from the SQLite database using better-sqlite3.
7 | */
8 | export async function getEvent(this: { db?: DatabaseWrapper; ndk?: NDK }, id: string): Promise {
9 | const stmt = "SELECT raw FROM events WHERE id = ? AND deleted = 0 LIMIT 1";
10 |
11 | if (!this.db) throw new Error("DB not initialized");
12 |
13 | try {
14 | const prepared = this.db.getDatabase().prepare(stmt);
15 | const result = prepared.get(id) as { raw?: string } | undefined;
16 |
17 | if (result && result.raw) {
18 | try {
19 | const eventData = JSON.parse(result.raw);
20 | return new NDKEvent(this.ndk, eventData);
21 | } catch {
22 | return null;
23 | }
24 | }
25 | return null;
26 | } catch (e) {
27 | console.error("Error retrieving event:", e);
28 | return null;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ndk-cache-sqlite/src/functions/getProfiles.ts:
--------------------------------------------------------------------------------
1 | import type { NDKUserProfile, Hexpubkey } from "@nostr-dev-kit/ndk";
2 | import type { NDKCacheAdapterSqlite } from "../index";
3 |
4 | /**
5 | * Fetches profiles that match the given filter from the SQLite database.
6 | */
7 | export async function getProfiles(
8 | this: NDKCacheAdapterSqlite,
9 | filter: (pubkey: Hexpubkey, profile: NDKUserProfile) => boolean,
10 | ): Promise