applesauce-actions 0.0.0-next-20250428164231 → 0.0.0-next-20250429163257
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -1
- package/dist/actions/__tests__/blossom.test.d.ts +1 -0
- package/dist/actions/__tests__/blossom.test.js +93 -0
- package/dist/actions/blocked-relays.js +2 -2
- package/dist/actions/blossom.d.ts +9 -0
- package/dist/actions/blossom.js +52 -0
- package/dist/actions/dm-relays.js +2 -2
- package/dist/actions/favorite-relays.js +1 -1
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/search-relays.js +2 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Applesauce Actions
|
|
2
2
|
|
|
3
|
-
A collection of pre-built actions nostr clients can use.
|
|
3
|
+
A collection of pre-built actions nostr clients can use. Built on top of `applesauce-core` and `applesauce-factory`.
|
|
4
4
|
|
|
5
5
|
[Documentation](https://hzrd149.github.io/applesauce/typedoc/modules/applesauce_actions.html)
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install applesauce-actions
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
Actions are common pre-built async operations that apps can perform. They use:
|
|
16
|
+
|
|
17
|
+
- `EventStore` for access to known nostr events
|
|
18
|
+
- `EventFactory` to build and sign new nostr events
|
|
19
|
+
- A `publish` method to publish or save the resulting events
|
|
20
|
+
|
|
21
|
+
The package provides an `ActionHub` class that combines these components into a single manager for easier action execution.
|
|
22
|
+
|
|
23
|
+
## Basic Usage
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { ActionHub } from "applesauce-actions";
|
|
27
|
+
import { FollowUser } from "applesauce-actions/actions";
|
|
28
|
+
|
|
29
|
+
async function publishEvent(event: NostrEvent) {
|
|
30
|
+
await relayPool.publish(event, ["wss://relay.example.com"]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Create an action hub with your event store, factory and publish method
|
|
34
|
+
const hub = new ActionHub(eventStore, eventFactory, publishEvent);
|
|
35
|
+
|
|
36
|
+
// Example: Follow a user
|
|
37
|
+
await hub
|
|
38
|
+
.exec(FollowUser, "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d")
|
|
39
|
+
.forEach((event) => publishEvent(event));
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For more detailed documentation and examples, visit the [full documentation](https://hzrd149.github.io/applesauce/overview/actions.html).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { EventStore } from "applesauce-core";
|
|
2
|
+
import { BLOSSOM_SERVER_LIST_KIND } from "applesauce-core/helpers/blossom";
|
|
3
|
+
import { EventFactory } from "applesauce-factory";
|
|
4
|
+
import { firstValueFrom, lastValueFrom } from "rxjs";
|
|
5
|
+
import { toArray } from "rxjs/operators";
|
|
6
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
7
|
+
import { FakeUser } from "../../__tests__/fake-user.js";
|
|
8
|
+
import { ActionHub } from "../../action-hub.js";
|
|
9
|
+
import { AddBlossomServer, NewBlossomServers, RemoveBlossomServer, SetDefaultBlossomServer } from "../blossom.js";
|
|
10
|
+
const user = new FakeUser();
|
|
11
|
+
let events;
|
|
12
|
+
let factory;
|
|
13
|
+
let hub;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
events = new EventStore();
|
|
16
|
+
factory = new EventFactory({ signer: user });
|
|
17
|
+
hub = new ActionHub(events, factory);
|
|
18
|
+
});
|
|
19
|
+
describe("NewBlossomServers", () => {
|
|
20
|
+
it("should publish a kind 10063 blossom server list", async () => {
|
|
21
|
+
const result = await lastValueFrom(hub.exec(NewBlossomServers, ["https://cdn.example.com/"]).pipe(toArray()));
|
|
22
|
+
expect(result[0]).toMatchObject({ kind: BLOSSOM_SERVER_LIST_KIND, tags: [["server", "https://cdn.example.com/"]] });
|
|
23
|
+
});
|
|
24
|
+
it("should throw if a blossom servers event already exists", async () => {
|
|
25
|
+
// Create the initial event
|
|
26
|
+
await hub.exec(NewBlossomServers, ["https://cdn.example.com/"]).forEach((e) => events.add(e));
|
|
27
|
+
// Attempt to create another one
|
|
28
|
+
await expect(lastValueFrom(hub.exec(NewBlossomServers, ["https://other.example.com/"]).pipe(toArray()))).rejects.toThrow("Blossom servers event already exists");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe("AddBlossomServer", () => {
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
// Create an initial empty server list
|
|
34
|
+
await hub.exec(NewBlossomServers, ["https://cdn.example.com/"]).forEach((e) => events.add(e));
|
|
35
|
+
});
|
|
36
|
+
it("should add a single server to the list", async () => {
|
|
37
|
+
const result = await firstValueFrom(hub.exec(AddBlossomServer, "https://other.example.com/"));
|
|
38
|
+
expect(result).toMatchObject({
|
|
39
|
+
kind: BLOSSOM_SERVER_LIST_KIND,
|
|
40
|
+
tags: expect.arrayContaining([["server", expect.stringContaining("other.example.com")]]),
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
it("should add multiple servers to the list", async () => {
|
|
44
|
+
const result = await firstValueFrom(hub.exec(AddBlossomServer, ["https://other.example.com/", "https://other2.example.com/"]));
|
|
45
|
+
expect(result).toMatchObject({
|
|
46
|
+
kind: BLOSSOM_SERVER_LIST_KIND,
|
|
47
|
+
tags: expect.arrayContaining([
|
|
48
|
+
["server", expect.stringContaining("other.example.com")],
|
|
49
|
+
["server", expect.stringContaining("other2.example.com")],
|
|
50
|
+
]),
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe("RemoveBlossomServer", () => {
|
|
55
|
+
beforeEach(async () => {
|
|
56
|
+
// Create an initial server list with two servers
|
|
57
|
+
await hub
|
|
58
|
+
.exec(NewBlossomServers, ["https://cdn.example.com/", "https://other.example.com/"])
|
|
59
|
+
.forEach((e) => events.add(e));
|
|
60
|
+
});
|
|
61
|
+
it("should remove a server from the list", async () => {
|
|
62
|
+
const result = await firstValueFrom(hub.exec(RemoveBlossomServer, "https://cdn.example.com/"));
|
|
63
|
+
expect(result).toMatchObject({
|
|
64
|
+
kind: BLOSSOM_SERVER_LIST_KIND,
|
|
65
|
+
tags: expect.arrayContaining([["server", expect.stringContaining("other.example.com")]]),
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
it("should remove multiple servers from the list", async () => {
|
|
69
|
+
const result = await firstValueFrom(hub.exec(RemoveBlossomServer, ["https://cdn.example.com/", "https://other.example.com/"]));
|
|
70
|
+
expect(result).toMatchObject({
|
|
71
|
+
kind: BLOSSOM_SERVER_LIST_KIND,
|
|
72
|
+
tags: [],
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe("SetDefaultBlossomServer", () => {
|
|
77
|
+
beforeEach(async () => {
|
|
78
|
+
// Create an initial server list with two servers
|
|
79
|
+
await hub
|
|
80
|
+
.exec(NewBlossomServers, ["https://cdn.example.com/", "https://other.example.com/"])
|
|
81
|
+
.forEach((e) => events.add(e));
|
|
82
|
+
});
|
|
83
|
+
it("should move the specified server to the top of the list", async () => {
|
|
84
|
+
const result = await firstValueFrom(hub.exec(SetDefaultBlossomServer, "https://other.example.com/"));
|
|
85
|
+
expect(result).toMatchObject({
|
|
86
|
+
kind: BLOSSOM_SERVER_LIST_KIND,
|
|
87
|
+
tags: expect.arrayContaining([
|
|
88
|
+
["server", expect.stringContaining("other.example.com")],
|
|
89
|
+
["server", expect.stringContaining("cdn.example.com")],
|
|
90
|
+
]),
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { modifyHiddenTags, modifyPublicTags } from "applesauce-factory/operations/event";
|
|
2
|
+
import { addRelayTag, removeRelayTag } from "applesauce-factory/operations/tag/relay";
|
|
3
3
|
import { kinds } from "nostr-tools";
|
|
4
4
|
function getBlockedRelaysEvent(events, self) {
|
|
5
5
|
const event = events.getReplaceable(kinds.BlockedRelaysList, self);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Action } from "../action-hub.js";
|
|
2
|
+
/** An action that adds a server to the Blossom servers event */
|
|
3
|
+
export declare function AddBlossomServer(server: string | URL | (string | URL)[]): Action;
|
|
4
|
+
/** An action that removes a server from the Blossom servers event */
|
|
5
|
+
export declare function RemoveBlossomServer(server: string | URL | (string | URL)[]): Action;
|
|
6
|
+
/** Makes a specific Blossom server the default server (move it to the top of the list) */
|
|
7
|
+
export declare function SetDefaultBlossomServer(server: string | URL): Action;
|
|
8
|
+
/** Creates a new Blossom servers event */
|
|
9
|
+
export declare function NewBlossomServers(servers?: (string | URL)[]): Action;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { BLOSSOM_SERVER_LIST_KIND } from "applesauce-core/helpers/blossom";
|
|
2
|
+
import { modifyPublicTags } from "applesauce-factory/operations/event";
|
|
3
|
+
import { addBlossomServerTag, removeBlossomServerTag } from "applesauce-factory/operations/tag/blossom";
|
|
4
|
+
function getBlossomServersEvent(events, self) {
|
|
5
|
+
const event = events.getReplaceable(BLOSSOM_SERVER_LIST_KIND, self);
|
|
6
|
+
if (!event)
|
|
7
|
+
throw new Error("Can't find Blossom servers event");
|
|
8
|
+
return event;
|
|
9
|
+
}
|
|
10
|
+
/** An action that adds a server to the Blossom servers event */
|
|
11
|
+
export function AddBlossomServer(server) {
|
|
12
|
+
return async function* ({ events, factory, self }) {
|
|
13
|
+
const servers = getBlossomServersEvent(events, self);
|
|
14
|
+
const operation = Array.isArray(server) ? server.map((s) => addBlossomServerTag(s)) : addBlossomServerTag(server);
|
|
15
|
+
const draft = await factory.modifyTags(servers, operation);
|
|
16
|
+
yield await factory.sign(draft);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** An action that removes a server from the Blossom servers event */
|
|
20
|
+
export function RemoveBlossomServer(server) {
|
|
21
|
+
return async function* ({ events, factory, self }) {
|
|
22
|
+
const servers = getBlossomServersEvent(events, self);
|
|
23
|
+
const operation = Array.isArray(server)
|
|
24
|
+
? server.map((s) => removeBlossomServerTag(s))
|
|
25
|
+
: removeBlossomServerTag(server);
|
|
26
|
+
const draft = await factory.modifyTags(servers, operation);
|
|
27
|
+
yield await factory.sign(draft);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Makes a specific Blossom server the default server (move it to the top of the list) */
|
|
31
|
+
export function SetDefaultBlossomServer(server) {
|
|
32
|
+
return async function* ({ events, factory, self }) {
|
|
33
|
+
const servers = getBlossomServersEvent(events, self);
|
|
34
|
+
const prependTag = (tag) => (tags) => [tag, ...tags];
|
|
35
|
+
const draft = await factory.modifyTags(servers, [
|
|
36
|
+
removeBlossomServerTag(server),
|
|
37
|
+
prependTag(["server", String(server)]),
|
|
38
|
+
]);
|
|
39
|
+
yield await factory.sign(draft);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Creates a new Blossom servers event */
|
|
43
|
+
export function NewBlossomServers(servers) {
|
|
44
|
+
return async function* ({ events, factory, self }) {
|
|
45
|
+
const existing = events.getReplaceable(BLOSSOM_SERVER_LIST_KIND, self);
|
|
46
|
+
if (existing)
|
|
47
|
+
throw new Error("Blossom servers event already exists");
|
|
48
|
+
const operations = servers ? servers.map((s) => addBlossomServerTag(s)) : [];
|
|
49
|
+
const draft = await factory.build({ kind: BLOSSOM_SERVER_LIST_KIND }, modifyPublicTags(...operations));
|
|
50
|
+
yield await factory.sign(draft);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { modifyPublicTags } from "applesauce-factory/operations/event";
|
|
2
|
+
import { addRelayTag, removeRelayTag } from "applesauce-factory/operations/tag/relay";
|
|
3
3
|
import { kinds } from "nostr-tools";
|
|
4
4
|
function getDMRelaysEvent(events, self) {
|
|
5
5
|
const event = events.getReplaceable(kinds.DirectMessageRelaysList, self);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FAVORITE_RELAYS_KIND } from "applesauce-core/helpers/lists";
|
|
2
|
+
import { modifyHiddenTags, modifyPublicTags } from "applesauce-factory/operations/event";
|
|
2
3
|
import { addCoordinateTag, addRelayTag, removeCoordinateTag, removeRelayTag } from "applesauce-factory/operations/tag";
|
|
3
|
-
import { modifyHiddenTags, modifyPublicTags } from "applesauce-factory/operations/tag/list";
|
|
4
4
|
function getFavoriteRelaysEvent(events, self) {
|
|
5
5
|
const event = events.getReplaceable(FAVORITE_RELAYS_KIND, self);
|
|
6
6
|
if (!event)
|
package/dist/actions/index.d.ts
CHANGED
package/dist/actions/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { modifyHiddenTags, modifyPublicTags } from "applesauce-factory/operations/event";
|
|
2
|
+
import { addRelayTag, removeRelayTag } from "applesauce-factory/operations/tag/relay";
|
|
3
3
|
import { kinds } from "nostr-tools";
|
|
4
4
|
function getSearchRelaysEvent(events, self) {
|
|
5
5
|
const event = events.getReplaceable(kinds.SearchRelaysList, self);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-actions",
|
|
3
|
-
"version": "0.0.0-next-
|
|
3
|
+
"version": "0.0.0-next-20250429163257",
|
|
4
4
|
"description": "A package for performing common nostr actions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,15 +32,15 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"applesauce-core": "0.0.0-next-
|
|
36
|
-
"applesauce-factory": "0.0.0-next-
|
|
35
|
+
"applesauce-core": "0.0.0-next-20250429163257",
|
|
36
|
+
"applesauce-factory": "0.0.0-next-20250429163257",
|
|
37
37
|
"nostr-tools": "^2.10.4",
|
|
38
38
|
"rxjs": "^7.8.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@hirez_io/observer-spy": "^2.2.0",
|
|
42
42
|
"@types/debug": "^4.1.12",
|
|
43
|
-
"applesauce-signers": "0.0.0-next-
|
|
43
|
+
"applesauce-signers": "0.0.0-next-20250429163257",
|
|
44
44
|
"nanoid": "^5.1.5",
|
|
45
45
|
"typescript": "^5.8.3",
|
|
46
46
|
"vitest": "^3.1.1"
|