Thread

Article header

Applesauce v5

A new event casting system for easily building interfaces and some breaking changes to core interfaces

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:

⚠️ 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:

  • ActionHub renamed to ActionRunner - The class has been renamed for clarity
  • Action interface changed - Action is now an async method that calls a publish method, 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-tools to version 2.19 or removed the dependency entirely if not needed

🚀 Getting Started

To upgrade to v5:

  1. Update all applesauce-* packages to v5
  2. Remove applesauce-factory and update EventFactory imports to applesauce-core/event-factory
  3. Review and update imports to use applesauce-common where needed
  4. Update action implementations to use the new ActionRunner interface
  5. Migrate to the unified event loader for EventStore
  6. Replace useObservableState and useObservableMemo with the new use$ hook

📚 Documentation

Full documentation is available at https://hzrd149.github.io/applesauce

Replies (0)

No replies yet. Be the first to leave a comment!