Files
next.orly.dev/docs/applesauce-reference.md
mleku f16ab3077f Interim release: documentation updates and rate limiting improvements
- Add applesauce library reference documentation
- Add rate limiting test report for Badger
- Add memory monitoring for rate limiter (platform-specific implementations)
- Enhance PID-controlled adaptive rate limiting
- Update Neo4j and Badger monitors with improved load metrics
- Add docker-compose configuration
- Update README and configuration options

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 08:47:25 +01:00

12 KiB

Applesauce Library Reference

A collection of TypeScript libraries for building Nostr web clients. Powers the noStrudel client.

Repository: https://github.com/hzrd149/applesauce Documentation: https://hzrd149.github.io/applesauce/


Packages Overview

Package Description
applesauce-core Event utilities, key management, protocols, event storage
applesauce-relay Relay connection management with auto-reconnect
applesauce-signers Signing interfaces for multiple providers
applesauce-loaders High-level data loading for common Nostr patterns
applesauce-factory Event creation and manipulation utilities
applesauce-react React hooks and providers

Installation

# Core package
npm install applesauce-core

# With React support
npm install applesauce-core applesauce-react

# Full stack
npm install applesauce-core applesauce-relay applesauce-signers applesauce-loaders applesauce-factory

Core Concepts

Philosophy

  • Reactive Architecture: Built on RxJS observables for event-driven programming
  • No Vendor Lock-in: Generic interfaces compatible with other Nostr libraries
  • Modularity: Tree-shakeable packages - include only what you need

EventStore

The foundational class for managing Nostr event state.

Creation

import { EventStore } from "applesauce-core";

// Memory-only store
const eventStore = new EventStore();

// With persistent database
import { BetterSqlite3EventDatabase } from "applesauce-core/database";
const database = new BetterSqlite3EventDatabase("./events.db");
const eventStore = new EventStore(database);

Event Management Methods

// Add event (returns existing if duplicate, null if rejected)
eventStore.add(event, relay?);

// Remove events
eventStore.remove(id);
eventStore.remove(event);
eventStore.removeByFilters(filters);

// Update event (notify store of modifications)
eventStore.update(event);

Query Methods

// Check existence
eventStore.hasEvent(id);

// Get single event
eventStore.getEvent(id);

// Get by filters
eventStore.getByFilters(filters);

// Get sorted timeline (newest first)
eventStore.getTimeline(filters);

// Replaceable events
eventStore.hasReplaceable(kind, pubkey);
eventStore.getReplaceable(kind, pubkey, identifier?);
eventStore.getReplaceableHistory(kind, pubkey, identifier?); // requires keepOldVersions: true

Observable Subscriptions

// Single event updates
eventStore.event(id).subscribe(event => { ... });

// All matching events
eventStore.filters(filters, onlyNew?).subscribe(events => { ... });

// Sorted event arrays
eventStore.timeline(filters, onlyNew?).subscribe(events => { ... });

// Replaceable events
eventStore.replaceable(kind, pubkey).subscribe(event => { ... });

// Addressable events
eventStore.addressable(kind, pubkey, identifier).subscribe(event => { ... });

Helper Subscriptions

// Profile (kind 0)
eventStore.profile(pubkey).subscribe(profile => { ... });

// Contacts (kind 3)
eventStore.contacts(pubkey).subscribe(contacts => { ... });

// Mutes (kind 10000)
eventStore.mutes(pubkey).subscribe(mutes => { ... });

// Mailboxes/NIP-65 relays (kind 10002)
eventStore.mailboxes(pubkey).subscribe(mailboxes => { ... });

// Blossom servers (kind 10063)
eventStore.blossomServers(pubkey).subscribe(servers => { ... });

// Reactions (kind 7)
eventStore.reactions(event).subscribe(reactions => { ... });

// Thread replies
eventStore.thread(eventId).subscribe(thread => { ... });

// Comments
eventStore.comments(event).subscribe(comments => { ... });

NIP-91 AND Operators

// Use & prefix for tags requiring ALL values
eventStore.filters({
  kinds: [1],
  "&t": ["meme", "cat"],    // Must have BOTH tags
  "#t": ["black", "white"]  // Must have black OR white
});

Fallback Loaders

// Custom async loaders for missing events
eventStore.eventLoader = async (pointer) => {
  // Fetch from relay and return event
};

eventStore.replaceableLoader = async (pointer) => { ... };
eventStore.addressableLoader = async (pointer) => { ... };

Configuration

const eventStore = new EventStore();

// Keep all versions of replaceable events
eventStore.keepOldVersions = true;

// Keep expired events (default: removes them)
eventStore.keepExpired = true;

// Custom verification
eventStore.verifyEvent = (event) => verifySignature(event);

// Model memory duration (default: 60000ms)
eventStore.modelKeepWarm = 60000;

Memory Management

// Mark event as in-use
eventStore.claim(event, claimId);

// Check if claimed
eventStore.isClaimed(event);

// Remove claims
eventStore.removeClaim(event, claimId);
eventStore.clearClaim(event);

// Prune unclaimed events
eventStore.prune(count?);

// Iterate unclaimed (LRU ordered)
for (const event of eventStore.unclaimed()) { ... }

Observable Streams

// New events added
eventStore.insert$.subscribe(event => { ... });

// Events modified
eventStore.update$.subscribe(event => { ... });

// Events deleted
eventStore.remove$.subscribe(event => { ... });

EventFactory

Primary interface for creating, building, and modifying Nostr events.

Initialization

import { EventFactory } from "applesauce-factory";

// Basic
const factory = new EventFactory();

// With signer
const factory = new EventFactory({ signer: mySigner });

// Full configuration
const factory = new EventFactory({
  signer: { getPublicKey, signEvent, nip04?, nip44? },
  client: { name: "MyApp", address: "31990:..." },
  getEventRelayHint: (eventId) => "wss://relay.example.com",
  getPubkeyRelayHint: (pubkey) => "wss://relay.example.com",
  emojis: emojiArray
});

Blueprint-Based Creation

import { NoteBlueprint, ReactionBlueprint } from "applesauce-factory/blueprints";

// Pattern 1: Constructor + arguments
const note = await factory.create(NoteBlueprint, "Hello Nostr!");
const reaction = await factory.create(ReactionBlueprint, event, "+");

// Pattern 2: Direct blueprint call
const note = await factory.create(NoteBlueprint("Hello Nostr!"));

Custom Event Building

import { setContent, includeNameValueTag, includeSingletonTag } from "applesauce-factory/operations";

const event = await factory.build(
  { kind: 30023 },
  setContent("Article content..."),
  includeNameValueTag(["title", "My Title"]),
  includeSingletonTag(["d", "article-id"])
);

Event Modification

import { addPubkeyTag } from "applesauce-factory/operations";

// Full modification
const modified = await factory.modify(existingEvent, operations);

// Tags only
const updated = await factory.modifyTags(existingEvent, addPubkeyTag("pubkey"));

Helper Methods

// Short text note (kind 1)
await factory.note("Hello world!", options?);

// Reply to note
await factory.noteReply(parentEvent, "My reply");

// Reaction (kind 7)
await factory.reaction(event, "🔥");

// Event deletion
await factory.delete(events, reason?);

// Repost/share
await factory.share(event);

// NIP-22 comment
await factory.comment(article, "Great article!");

Available Blueprints

Blueprint Description
NoteBlueprint(content, options?) Standard text notes (kind 1)
CommentBlueprint(parent, content, options?) Comments on events
NoteReplyBlueprint(parent, content, options?) Replies to notes
ReactionBlueprint(event, emoji?) Emoji reactions (kind 7)
ShareBlueprint(event, options?) Event shares/reposts
PicturePostBlueprint(pictures, content, options?) Image posts
FileMetadataBlueprint(file, options?) File metadata
DeleteBlueprint(events) Event deletion
LiveStreamBlueprint(title, options?) Live streams

Models

Pre-built reactive models for common data patterns.

Built-in Models

import { ProfileModel, TimelineModel, RepliesModel } from "applesauce-core/models";

// Profile subscription (kind 0)
const profile$ = eventStore.model(ProfileModel, pubkey);

// Timeline subscription
const timeline$ = eventStore.model(TimelineModel, { kinds: [1] });

// Replies subscription (NIP-10 and NIP-22)
const replies$ = eventStore.model(RepliesModel, event);

Custom Models

import { Model } from "applesauce-core";

const AppSettingsModel: Model<AppSettings, [string]> = (appId) => {
  return (store) => {
    return store.addressable(30078, store.pubkey, appId).pipe(
      map(event => event ? JSON.parse(event.content) : null)
    );
  };
};

// Usage
const settings$ = eventStore.model(AppSettingsModel, "my-app");

Helper Functions

Event Utilities

import {
  isEvent,
  markFromCache,
  isFromCache,
  getTagValue,
  getIndexableTags
} from "applesauce-core/helpers";

Profile Management

import { getProfileContent, isValidProfile } from "applesauce-core/helpers";

const profile = getProfileContent(kind0Event);
const valid = isValidProfile(profile);

Relay Configuration

import { getInboxes, getOutboxes } from "applesauce-core/helpers";

const inboxRelays = getInboxes(kind10002Event);
const outboxRelays = getOutboxes(kind10002Event);

Zap Processing

import {
  isValidZap,
  getZapSender,
  getZapRecipient,
  getZapPayment
} from "applesauce-core/helpers";

if (isValidZap(zapEvent)) {
  const sender = getZapSender(zapEvent);
  const recipient = getZapRecipient(zapEvent);
  const payment = getZapPayment(zapEvent);
}

Lightning Parsing

import { parseBolt11, parseLNURLOrAddress } from "applesauce-core/helpers";

const invoice = parseBolt11(bolt11String);
const lnurl = parseLNURLOrAddress(addressOrUrl);

Pointer Creation

import {
  getEventPointerFromETag,
  getAddressPointerFromATag,
  getProfilePointerFromPTag,
  getAddressPointerForEvent
} from "applesauce-core/helpers";

Tag Validation

import { isETag, isATag, isPTag, isDTag, isRTag, isTTag } from "applesauce-core/helpers";

Media Detection

import { isAudioURL, isVideoURL, isImageURL, isStreamURL } from "applesauce-core/helpers";

if (isImageURL(url)) {
  // Handle image
}

Hidden Tags (NIP-51/60)

import {
  canHaveHiddenTags,
  hasHiddenTags,
  getHiddenTags,
  unlockHiddenTags,
  modifyEventTags
} from "applesauce-core/helpers";

Comment Operations

import { getCommentRootPointer, getCommentReplyPointer } from "applesauce-core/helpers";

Deletion Handling

import { getDeleteIds, getDeleteCoordinates } from "applesauce-core/helpers";

Common Patterns

Basic Nostr Client Setup

import { EventStore } from "applesauce-core";
import { EventFactory } from "applesauce-factory";
import { NoteBlueprint } from "applesauce-factory/blueprints";

// Initialize stores
const eventStore = new EventStore();
const factory = new EventFactory({ signer: mySigner });

// Subscribe to timeline
eventStore.timeline({ kinds: [1], limit: 50 }).subscribe(notes => {
  renderNotes(notes);
});

// Create a new note
const note = await factory.create(NoteBlueprint, "Hello Nostr!");

// Add to store
eventStore.add(note);

Profile Display

// Subscribe to profile updates
eventStore.profile(pubkey).subscribe(event => {
  if (event) {
    const profile = getProfileContent(event);
    displayProfile(profile);
  }
});

Reactive Reactions

// Subscribe to reactions on an event
eventStore.reactions(targetEvent).subscribe(reactions => {
  const likeCount = reactions.filter(r => r.content === "+").length;
  updateLikeButton(likeCount);
});

// Add a reaction
const reaction = await factory.reaction(targetEvent, "🔥");
eventStore.add(reaction);

Thread Loading

eventStore.thread(rootEventId).subscribe(thread => {
  renderThread(thread);
});

Nostr Event Kinds Reference

Kind Description
0 Profile metadata
1 Short text note
3 Contact list
7 Reaction
10000 Mute list
10002 Relay list (NIP-65)
10063 Blossom servers
30023 Long-form content
30078 App-specific data (NIP-78)

Resources