Its been a few months but I think the v5 release of applesauce is finally ready. This major release introduces new features, along with some breaking changes to some core interfaces.
Its also been cool to see some newer clients like grimoire by verbiricha and some existing apps like routstr using applesauce.
🎉 Major New Features
New Casting System
The new casting system makes it incredibly easy to work with Nostr events in web UIs. Transform raw Nostr events into typed classes with both synchronous properties and reactive observable interfaces.
import { castEvent, Note } from "applesauce-common/casts";
const note = castEvent(event, Note, eventStore);
// Access synchronous properties
console.log(note.id, note.createdAt, note.isReply);
// Subscribe to reactive observables
const profile$ = note.author.profile$;
const replies$ = note.replies$;
The casting system provides classes for common Nostr event types including Note, Profile, User, Reaction, Zap, Comment, Share, Article, and more. All cast classes extend EventCast and provide a consistent interface with both direct property access and chainable observables.
Learn more: Casting System Documentation
New applesauce-common Package
I've moved a lot of the non-protocol related code out into a new applesauce-common package, this should help keep things more organized without cluttering the applesauce-core package. The package includes:
- Cast classes for kind 1 and other common event kinds
- Helper functions for common social and other stuff NIPs
- Utilities for encryption, caching, and more
Unified Event Loader
The new unified event loader simplifies loading both single events and addressable/replaceable events (like profiles) through a single interface. It automatically routes to the appropriate loader based on the pointer type.
import { createEventLoaderForStore } from "applesauce-loaders/loaders";
// Set up the unified loader (one-time setup)
createEventLoaderForStore(eventStore, pool, {
bufferTime: 1000,
followRelayHints: true,
lookupRelays: ["wss://purplepag.es"],
});
// Now works for both event IDs and addressable events
eventStore.event({ id: "event_id" }).subscribe(...);
eventStore.replaceable({ kind: 0, pubkey: "pubkey" }).subscribe(...);
The unified loader replaces the previous separate eventLoader, replaceableLoader, and addressableLoader methods with a single, more powerful interface.
Learn more: Unified Event Loader Documentation
New use$ React Hook
The new use$ hook simplifies React integration by combining useObservableState and useMemo into a single, easy-to-use hook. It automatically handles subscription management, cleanup, and state updates.
import { use$ } from "applesauce-react/hooks";
function Profile({ user }: { user: User }) {
// subscribe to any rxjs observables latest value (initially undefined)
const profile = use$(user.profile$);
// Or use it like useMemo and change the observable when `user` chnages
const contacts = use$(() => user.contacts$, [user]);
return (
<div>
<h2>{profile?.displayName || user.npub}</h2>
<p>{contacts?.length || 0} contacts</p>
</div>
);
}
The hook works seamlessly with both BehaviorSubject and regular observables, and supports factory functions for dynamic observables based on props.
Learn more: use$ Hook Documentation
Encrypted Content Caching
New helper functions make it easy to cache encrypted content, so users don't need to decrypt the same events repeatedly. The persistEncryptedContent function automatically handles persistence and restoration of encrypted content for Nostr events.
import { persistEncryptedContent } from "applesauce-common/helpers";
persistEncryptedContent(eventStore, storage);
// Decrypted content is automatically cached
await unlockHiddenBookmarks(bookmarks, signer);
Learn more: Encrypted Content Caching Documentation
Tons of new examples
I've spent way too much time working on random examples using applesauce, at this point there is probably an example for just about anything you could want to do with a nostr client.
You can see all the examples at hzrd149.github.io/applesauce/examples but here are a few I'm most proud of:
- Working NIP-60 wallet
- Primal user search
- Relay status using NIP-66 monitors
- Relay discovery using attributes
- Multiple hashtag search with AND filters
- Mute manager
- Caching events with a local relay
⚠️ Breaking Changes
Package Reorganization
All non-protocol-related code has been moved to the new applesauce-common package. This means:
- Many imports have changed from their original packages to
applesauce-common - Cast classes are now in
applesauce-common/casts - Helper functions are in
applesauce-common/helpers - Observable utilities are in
applesauce-common/observable
Migration tip: Search your codebase for imports from packages like applesauce-core, applesauce-extra, etc., and update them to use applesauce-common where appropriate.
Action Runner Changes
The ActionRunner (formerly ActionHub) has undergone significant changes:
ActionHubrenamed toActionRunner- The class has been renamed for clarity- Action interface changed -
Actionis now an async method that calls apublishmethod, rather than an async generator of events - Action context extended - The action context now supports sub-actions and casting
This is mainly to allow the actions to specify additional relays events should be published too and to wait for events to be fetched from the event store.
If you're using the action system, you'll need to update your action implementations to use the new interface.
EventFactory Package Removed
The applesauce-factory package has been removed. EventFactory is now exported from applesauce-core/event-factory:
// Before (v4)
import { EventFactory } from "applesauce-factory";
// After (v5)
import { EventFactory } from "applesauce-core/event-factory";
Blueprints and operations that were previously in applesauce-factory are now in applesauce-common:
- Blueprints:
applesauce-common/blueprints - Operations:
applesauce-common/operations
EventStore Loader Changes
The EventStore now uses a single eventLoader method instead of separate eventLoader, replaceableLoader, and addressableLoader methods. The unified loader handles all three cases automatically.
RelayPool Default Behavior
RelayPool now ignores unreachable (ready=false) relays by default in request, subscription, publish, and sync methods. This improves reliability by automatically skipping relays that aren't responding.
📦 Package Updates
- Updated
nostr-toolsto version 2.19 or removed the dependency entirely if not needed
🚀 Getting Started
To upgrade to v5:
- Update all
applesauce-*packages to v5 - Remove
applesauce-factoryand updateEventFactoryimports toapplesauce-core/event-factory - Review and update imports to use
applesauce-commonwhere needed - Update action implementations to use the new
ActionRunnerinterface - Migrate to the unified event loader for
EventStore - Replace
useObservableStateanduseObservableMemowith the newuse$hook
📚 Documentation
Full documentation is available at https://hzrd149.github.io/applesauce