Why Nostr? What is Njump?
2025-04-29 17:47:57

hzrd149 on Nostr: I'm excited to announce the release of Applesauce v1.0.0! There are a few breaking ...

I’m excited to announce the release of Applesauce v1.0.0! There are a few breaking changes and a lot of improvements and new features across all packages. Each package has been updated to 1.0.0, marking a stable API for developers to build upon.

Applesauce core changes

There was a change in the applesauce-core package in the QueryStore.

The Query interface has been converted to a method instead of an object with key and run fields.

A bunch of new helper methods and queries were added, checkout the changelog for a full list.

Applesauce Relay

There is a new applesauce-relay package that provides a simple RxJS based api for connecting to relays and publishing events.

Documentation: applesauce-relay

Features:

  • A simple API for subscribing or publishing to a single relay or a group of relays
  • No connect or close methods, connections are managed automatically by rxjs
  • NIP-11 auth_required support
  • Support for NIP-42 authentication
  • Prebuilt or custom re-connection back-off
  • Keep-alive timeout (default 30s)
  • Client-side Negentropy sync support

Example Usage: Single relay

import { Relay } from "applesauce-relay";

// Connect to a relay
const relay = new Relay("wss://relay.example.com");

// Create a REQ and subscribe to it
relay
  .req({
    kinds: [1],
    limit: 10,
  })
  .subscribe((response) => {
    if (response === "EOSE") {
      console.log("End of stored events");
    } else {
      console.log("Received event:", response);
    }
  });

Example Usage: Relay pool

import { Relay, RelayPool } from "applesauce-relay";

// Create a pool with a custom relay
const pool = new RelayPool();

// Create a REQ and subscribe to it
pool
  .req(["wss://relay.damus.io", "wss://relay.snort.social"], {
    kinds: [1],
    limit: 10,
  })
  .subscribe((response) => {
    if (response === "EOSE") {
      console.log("End of stored events on all relays");
    } else {
      console.log("Received event:", response);
    }
  });

Applesauce actions

Another new package is the applesauce-actions package. This package provides a set of async operations for common Nostr actions.

Actions are run against the events in the EventStore and use the EventFactory to create new events to publish.

Documentation: applesauce-actions

Example Usage:

import { ActionHub } from "applesauce-actions";

// An EventStore and EventFactory are required to use the ActionHub
import { eventStore } from "./stores.ts";
import { eventFactory } from "./factories.ts";

// Custom publish logic
const publish = async (event: NostrEvent) => {
  console.log("Publishing", event);
  await app.relayPool.publish(event, app.defaultRelays);
};

// The `publish` method is optional for the async `run` method to work
const hub = new ActionHub(eventStore, eventFactory, publish);

Once an ActionsHub is created, you can use the run or exec methods to execute actions:

import { FollowUser, MuteUser } from "applesauce-actions/actions";

// Follow fiatjaf
await hub.run(
  FollowUser,
  "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
);

// Or use the `exec` method with a custom publish method
await hub
  .exec(
    MuteUser,
    "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
  )
  .forEach((event) => {
    // NOTE: Don't publish this event because we never want to mute fiatjaf
    // pool.publish(['wss://pyramid.fiatjaf.com/'], event)
  });

There are a log more actions including some for working with NIP-51 lists (private and public), you can find them in the reference

Applesauce loaders

The applesauce-loaders package has been updated to support any relay connection libraries and not just rx-nostr.

Before:

import { ReplaceableLoader } from "applesauce-loaders";
import { createRxNostr } from "rx-nostr";

// Create a new rx-nostr instance
const rxNostr = createRxNostr();

// Create a new replaceable loader
const replaceableLoader = new ReplaceableLoader(rxNostr);

After:


import { Observable } from "rxjs";
import { ReplaceableLoader, NostrRequest } from "applesauce-loaders";
import { SimplePool } from "nostr-tools";

// Create a new nostr-tools pool
const pool = new SimplePool();

// Create a method that subscribes using nostr-tools and returns an observable
function nostrRequest: NostrRequest = (relays, filters, id) => {
  return new Observable((subscriber) => {
    const sub = pool.subscribe(relays, filters, {
      onevent: (event) => {
        subscriber.next(event);
      },
      onclose: () => subscriber.complete(),
      oneose: () => subscriber.complete(),
    });

    return () => sub.close();
  });
};

// Create a new replaceable loader
const replaceableLoader = new ReplaceableLoader(nostrRequest);

Of course you can still use rx-nostr if you want:

import { createRxNostr } from "rx-nostr";

// Create a new rx-nostr instance
const rxNostr = createRxNostr();

// Create a method that subscribes using rx-nostr and returns an observable
function nostrRequest(
  relays: string[],
  filters: Filter[],
  id?: string,
): Observable<NostrEvent> {
  // Create a new oneshot request so it will complete when EOSE is received
  const req = createRxOneshotReq({ filters, rxReqId: id });
  return rxNostr
    .use(req, { on: { relays } })
    .pipe(map((packet) => packet.event));
}

// Create a new replaceable loader
const replaceableLoader = new ReplaceableLoader(nostrRequest);

There where a few more changes, check out the changelog

Applesauce wallet

Its far from complete, but there is a new applesauce-wallet package that provides a actions and queries for working with NIP-60 wallets.

Documentation: applesauce-wallet

Example Usage:

import { CreateWallet, UnlockWallet } from "applesauce-wallet/actions";

// Create a new NIP-60 wallet
await hub.run(CreateWallet, ["wss://mint.example.com"], privateKey);

// Unlock wallet and associated tokens/history
await hub.run(UnlockWallet, { tokens: true, history: true });
Author Public Key
npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr