# Applesauce

> 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. Use when this capability is needed.

- Skill: `tomevault-io/applesauce` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/applesauce`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/applesauce/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/applesauce

---


# 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

1. **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.
2. **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.
3. **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.
4. **Consult `references/patterns.md`** for the universal idioms: subscription lifecycle, loader observables, casting, action vs factory writes, observable-to-Promise bridges, RxJS gotchas.
5. **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.
6. **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

- Full docs: <https://applesauce.build>
- Typedoc reference: <https://applesauce.build/typedoc/>
- Live examples: <https://applesauce.build/examples/>
- Source: <https://github.com/hzrd149/applesauce>

---
> Source: [hzrd149/nostrudel](https://github.com/hzrd149/nostrudel) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-07-01 -->

