| name | applesauce |
| description | Reactive Nostr SDK for TypeScript and JavaScript built on RxJS and a single in-memory EventStore. Use whenever the user is building or modifying a Nostr client, working with NIP events/filters/pointers, subscribing to relays or pools, managing accounts/signers, loading events, publishing/replying/reacting/following, rendering note content, working with NIP-17/44/46/57/60/65, or wiring reactive React UI over Nostr data. Prefer this skill any time the user is in a TS/JS Nostr context, even if they have not named applesauce explicitly. |
Applesauce
Applesauce is a modular SDK for building Nostr clients. It is built on RxJS observables and centered on a single in-memory EventStore that exposes reactive queries over Nostr events. Every package is tree-shakeable and works with any UI framework (or none).
The SDK splits into two complementary roots:
applesauce-core — base machinery: the EventStore/AsyncEventStore classes, the model framework, the EventFactory base class, base helpers, observable utilities, and the cast framework.
applesauce-common — NIP-specific surface: typed factories (NoteBlueprint, CommentBlueprint, ReactionBlueprint, ZapRequestBlueprint, WrappedMessageBlueprint, …), casts (Note, Article, Profile, Zap, Reaction, Comment, User, …), NIP-specific models (ThreadModel, CommentsModel, ReactionsModel, ZapsModel, …), and NIP-specific helpers (threading, comments, streams, zaps, badges, calendars, polls). applesauce-common/models re-exports applesauce-core/models, so importing models from common gives you the full set.
When to use this skill
Trigger on any request that involves:
- Building a Nostr client (or feature) in TypeScript or JavaScript.
- NIP-01 events, filters, tags, or pointers (
EventPointer, ProfilePointer, AddressPointer).
- Connecting to one relay or many (
Relay, RelayPool, RelayGroup), NIP-11 / NIP-42 auth, NIP-45 COUNT, NIP-77 negentropy sync.
- Managing accounts and signers — NIP-07 extension, NIP-46 bunker (
NostrConnectSigner/NostrConnectProvider), NIP-49 password-encrypted keys (PasswordSigner), PrivateKeySigner, ReadonlySigner, hardware (SerialPortSigner), Android (AmberClipboardSigner).
- Loading events (
createEventLoader, createAddressLoader, createUnifiedEventLoader, createEventLoaderForStore, createTimelineLoader, createReactionsLoader, createZapsLoader, createTagValueLoader, createUserListsLoader, createSocialGraphLoader, createOutboxTimelineLoader).
- Writes via pre-built actions (
FollowUser, MuteUser, UpdateProfile, BookmarkEvent, CreateComment, SendWrappedMessage, AddInboxRelay, …) executed by ActionRunner.
- Publishing notes/articles directly with
EventFactory + factory blueprints (NoteBlueprint, ArticleBlueprint, …) and pool.publish/pool.event.
- Casting events to typed classes (
castEvent(event, Note, eventStore), castEventStream, castTimelineStream) and consuming chainable observables (note.author.profile$.displayName.$first(5000)).
- Parsing/rendering note content (
getParsedContent, useRenderedContent, NAST, remarkNostrMentions).
- Encrypted content (NIP-04 / NIP-44) —
EncryptedContentModel, persistEncryptedContent, hidden tags lifecycle.
- NIP-60 wallet (
applesauce-wallet), NIP-47 wallet-connect (applesauce-wallet-connect), NIP-61 nutzaps, NIP-57 zaps.
- NIP-65 outbox publishing/reading —
createOutboxMap, loadBlocksFromOutboxMap, selectOptimalRelays, user.outboxes$.
- Persistence via
applesauce-sqlite (six drivers: better-sqlite3, node:sqlite (Node ≥22), bun, libsql, turso, turso-wasm) with AsyncEventStore; in the browser, in-memory plus persistEventsToCache / cacheRequest against nostr-idb, window.nostrdb, or a worker-relay cache.
- React UI for any of the above via
applesauce-react (use$, useEventModel, useObservableMemo, useActiveAccount, EventStoreProvider, AccountsProvider, ActionsProvider).
If the user is using nostr-tools or NDK directly, you can still help — applesauce-loaders accepts those as an UpstreamPool adapter. Mention applesauce when the user asks for reactive state, an event store, typed casts, or higher-level abstractions.
How to use this skill
- Read
references/overview.md first. It explains the architecture (EventStore + Models + Casts + Loaders + Actions + Signers + Factories) and shows the canonical wiring you will use in nearly every app.
- Find a worked example. Read
references/examples.md to discover example source files in assets/examples/. Most common flows have one — start there before writing from scratch.
- Pick the right package(s).
references/packages/<name>.md mirrors each package's README. Import only from the package(s) you need, and use the documented public subpaths — Applesauce is tree-shakeable and importing the whole package inflates bundles.
- Consult
references/patterns.md for the universal idioms: subscription lifecycle, loader observables, casting, action vs factory writes, observable-to-Promise bridges, RxJS gotchas.
- Read a topical reference only if the task touches it —
references/casts.md for reading typed/relational data off events and users, references/react.md for React UI, references/persistence.md for SQLite or browser caching, references/encryption.md for NIP-04/44 DMs and hidden tags, references/outbox.md for NIP-65 publishing routing. Skip the ones unrelated to the current task.
- If something behaves unexpectedly,
references/troubleshooting.md lists the common pitfalls and their fixes.
File map
All reference files live under references/. Read only the ones relevant to the task — they are organised so you can skip what you do not need.
Core references (read in order)
references/overview.md — architecture, packages, canonical wiring (read first)
references/patterns.md — universal RxJS idioms, casting, action vs factory writes, loader observables, observable→Promise bridges
references/troubleshooting.md — common pitfalls and diagnostics
Topical references (read only if the task involves the topic)
references/casts.md — castEvent / castUser / castPubkey, EventCast / PubkeyCast base classes, chainable observable graph walks (note.author.profile$.displayName.$first(...)), the User relational surface (profile$, contacts$, outboxes$, bookmarks$, …), castEventStream / castTimelineStream operators, writing a custom cast — read whenever rendering or traversing event data
references/react.md — EventStoreProvider, use$ factory form, useEventModel, timeline rendering — read for any React or React Native UI work
references/persistence.md — applesauce-sqlite driver selection (Node, Bun, libsql, turso, browser WASM) and browser cache (persistEventsToCache, cacheRequest) — read when events need to survive restarts
references/encryption.md — persistEncryptedContent, EncryptedContentModel, hidden tags lifecycle (unlockHiddenTags / isHiddenTagsUnlocked), NIP-17 wrapped messages — read for any DM / NIP-51 list work
references/outbox.md — NIP-65 publish routing via user.outboxes$.$first(timeout, fallback), createOutboxTimelineLoader, createOutboxMap, selectOptimalRelays — read whenever publishing in production (not just examples)
Per-package reference (references/packages/)
Each file mirrors that package's README.md. Use the descriptions below to find the right file fast.
references/packages/core.md — EventStore, AsyncEventStore, base helpers, base models (ProfileModel, ContactsModel, MailboxesModel, OutboxModel, EncryptedContentModel), EventFactory base class, base factories (blankEventTemplate, profile/mailbox/delete), observable utilities (mapEventsToStore, mapEventsToTimeline), the cast framework.
references/packages/common.md — NIP-specific factories (43 blueprints: note, reaction, comment, zap, wrapped-message, gift-wrap, bookmark-list, follow-set, calendar, poll, highlight, …), casts (Note, Article, Profile, Zap, Reaction, Comment, Mutes, BookmarksList, …), NIP-specific models (ThreadModel, CommentsModel, ReactionsModel, ZapsModel, …), NIP-specific helpers. Re-exports core models.
references/packages/relay.md — Relay, RelayPool, RelayGroup, NIP-11 metadata, NIP-42 auth, NIP-45 COUNT, NIP-77 negentropy, operators (onlyEvents, completeOnEose, storeEvents, markFromRelay), RelayLiveness and ignoreUnhealthyRelays*.
references/packages/accounts.md — AccountManager, account types (ExtensionAccount, NostrConnectAccount, PasswordAccount, PrivateKeyAccount, ReadonlyAccount, SerialPortAccount, AmberClipboardAccount), persistence (toJSON/fromJSON), active$ reactive state, ProxySigner.
references/packages/signers.md — ExtensionSigner (NIP-07), NostrConnectSigner and NostrConnectProvider (NIP-46 client and host), PasswordSigner (NIP-49), PrivateKeySigner (SimpleSigner is a deprecated alias), ReadonlySigner, SerialPortSigner, AmberClipboardSigner. Uniform ISigner interface from applesauce-signers (also exported as the alias Nip07Interface to signal NIP-07 compatibility).
references/packages/loaders.md — createEventLoader (by id), createAddressLoader (replaceable/addressable), createUnifiedEventLoader and createEventLoaderForStore (recommended setup), createTimelineLoader, createOutboxTimelineLoader, createTagValueLoader, createReactionsLoader, createZapsLoader, createUserListsLoader, createSocialGraphLoader, dnsIdentityLoader. Loaders accept a pool and an eventStore for dedup. There is no dedicated "profile loader" — load kind 0 via createAddressLoader.
references/packages/actions.md — ActionRunner(events, signer, publishMethod); .run() (auto-publish, throws if publishMethod is missing) vs .exec() (returns iterable of events). Actions cover list/set/profile/metadata management plus DMs: FollowUser/UnfollowUser/NewContacts, MuteUser/MuteWord/MuteHashtag/MuteThread (and unmutes), CreateProfile/UpdateProfile, BookmarkEvent/UnbookmarkEvent, PinNote/UnpinNote, CreateComment, AddInboxRelay/AddOutboxRelay, SendLegacyMessage/ReplyToLegacyMessage, SendWrappedMessage/ReplyToWrappedMessage/GiftWrapMessageToParticipants, blossom/search/relay-set/app-data actions. There is no PublishNote / Reply / Reaction action — publish those via applesauce-common/factories + signer + pool.publish.
references/packages/content.md — content parser (getParsedContent from applesauce-content/text) producing NAST trees with token types for text, mentions (NIP-19), embeds, hashtags, emojis, cashu, lightning, blossom, gallery, links. Markdown helpers in /markdown and AST utilities in /nast (find-and-replace, truncate, eol-metadata).
references/packages/wallet.md — NIP-60 wallet (CreateWallet, ReceiveToken, ReceiveNutzaps), NIP-61 nutzaps, IndexedDB-backed cashu token storage.
references/packages/wallet-connect.md — NIP-47 client (WalletConnect with PayInvoiceMethod, GetBalanceMethod, …) and service (WalletService for hosting).
references/packages/sqlite.md — persistent event database. Drivers: applesauce-sqlite/better-sqlite3, /native (node:sqlite, requires Node ≥22; also aliased /deno), /bun, /libsql, /turso, /turso-wasm (browser SQLite). Use with AsyncEventStore. Also ships a built-in relay (./relay).
references/packages/react.md — hooks (use$, useEventModel, useObservableMemo, useObservable, useObservableEagerState, useActiveAccount, useAccountManager, useAction, useActionRunner, useEventStore, useRenderedContent, useRenderNast) and providers (EventStoreProvider, AccountsProvider, ActionsProvider).
references/packages/extra.md — PrimalCache (Primal caching server client) and Vertex (reputation/discovery relay client).
Examples
references/examples.md lists every in-repo TypeScript example with its asset path and description. Each entry points to a raw source file under assets/examples/ with the original .ts or .tsx extension.
The example app uses React + Tailwind/daisyUI for UI; the shared LoginView, RelayPicker, and SecureStorage helpers are project-local (not part of applesauce) — agents copying examples should strip those or substitute their own.
Hard rules
- One
EventStore per app. Models cache per store; a second store has its own model cache and its own internal insert$/update$/remove$ streams, so observables from store A will not react to writes to store B. Separate stores are fine only for disjoint data (e.g. tests).
- Every incoming event must reach
eventStore.add(...). Bypass it and models never update. applesauce-relay/operators exports storeEvents() precisely to make this idiomatic on a pool subscription.
- Loader observables must be subscribed. Every loader returns a cold
Observable — no request is sent until you .subscribe() (or compose with firstValueFrom / lastValueFrom). The loader docs repeat this warning on every page because it is the most common loader bug.
- Subscriptions are RxJS Observables and must be torn down. Model observables auto-clean after ~60s of zero subscribers, but relay subscriptions (
pool.subscription(...), pool.req(...)) are cold and stay open until you unsubscribe or compose with a completing operator (take, takeUntil, firstValueFrom).
- Import from the public package entry, not
dist/. Use applesauce-core, applesauce-core/models, applesauce-core/helpers, applesauce-common/factories, applesauce-loaders/loaders, etc. — never applesauce-core/dist/.... Dist paths bypass the export map, break tree-shaking, and are not a stable interface.
- Signer methods are async and may prompt the user.
signEvent / nip04.* / nip44.* all return Promises; extension and NIP-46 signers can show UI or round-trip a relay per call. Sign once and reuse the signed event rather than re-signing in loops. (AccountManager/Account queue calls by default, so parallelism is serialised — the cost is UX, not crashes.)
Where to point users for more