applesauce-actions 0.0.0-next-20250428164231 → 0.0.0-next-20250428223834

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 CHANGED
@@ -1,5 +1,42 @@
1
1
  # Applesauce Actions
2
2
 
3
- A collection of pre-built actions nostr clients can use. built on top of `applesauce-core` and `applesauce-factory`
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 { addRelayTag, removeRelayTag } from "applesauce-factory/operations/tag";
2
- import { modifyHiddenTags, modifyPublicTags } from "applesauce-factory/operations/tag/list";
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 { addRelayTag, removeRelayTag } from "applesauce-factory/operations/tag";
2
- import { modifyPublicTags } from "applesauce-factory/operations/tag/list";
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)
@@ -1,4 +1,5 @@
1
1
  export * from "./blocked-relays.js";
2
+ export * from "./blossom.js";
2
3
  export * from "./bookmarks.js";
3
4
  export * from "./contacts.js";
4
5
  export * from "./dm-relays.js";
@@ -1,4 +1,5 @@
1
1
  export * from "./blocked-relays.js";
2
+ export * from "./blossom.js";
2
3
  export * from "./bookmarks.js";
3
4
  export * from "./contacts.js";
4
5
  export * from "./dm-relays.js";
@@ -1,5 +1,5 @@
1
- import { addRelayTag, removeRelayTag } from "applesauce-factory/operations/tag";
2
- import { modifyHiddenTags, modifyPublicTags } from "applesauce-factory/operations/tag/list";
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-20250428164231",
3
+ "version": "0.0.0-next-20250428223834",
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-20250428164231",
36
- "applesauce-factory": "0.0.0-next-20250428164231",
35
+ "applesauce-core": "0.0.0-next-20250428223834",
36
+ "applesauce-factory": "0.0.0-next-20250428223834",
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-20250428164231",
43
+ "applesauce-signers": "0.0.0-next-20250428223834",
44
44
  "nanoid": "^5.1.5",
45
45
  "typescript": "^5.8.3",
46
46
  "vitest": "^3.1.1"