-
@ hzrd149
2025-04-29 17:47:57I'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 theQueryStore
.The
Query
interface has been converted to a method instead of an object withkey
andrun
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
```typescript 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
```typescript 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 theEventFactory
to create new events to publish.Documentation: applesauce-actions
Example Usage:
```typescript 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 asyncrun
method to work const hub = new ActionHub(eventStore, eventFactory, publish); ```Once an
ActionsHub
is created, you can use therun
orexec
methods to execute actions:```typescript 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 justrx-nostr
.Before:
```typescript 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:
```typescript
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:
```typescript 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
{ // 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:
```typescript 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 }); ```