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
orclose
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 });