applesauce-actions 0.0.0-next-20250526151506 → 0.0.0-next-20250609183606

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.
@@ -1,5 +1,5 @@
1
1
  import { from, Subject } from "rxjs";
2
- import { describe, expect, it, vi } from "vitest";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { subscribeSpyTo } from "@hirez_io/observer-spy";
4
4
  import { EventFactory } from "applesauce-factory";
5
5
  import { EventStore } from "applesauce-core";
@@ -7,8 +7,12 @@ import { FakeUser } from "./fake-user.js";
7
7
  import { ActionHub } from "../action-hub.js";
8
8
  import { CreateProfile } from "../actions/profile.js";
9
9
  const user = new FakeUser();
10
- const events = new EventStore();
11
- const factory = new EventFactory({ signer: user });
10
+ let events = new EventStore();
11
+ let factory = new EventFactory({ signer: user });
12
+ beforeEach(() => {
13
+ events = new EventStore();
14
+ factory = new EventFactory({ signer: user });
15
+ });
12
16
  describe("runAction", () => {
13
17
  it("should handle action that return observables", async () => {
14
18
  const e = [user.note(), user.profile({ name: "testing" })];
@@ -1,7 +1,7 @@
1
1
  import { Observable } from "rxjs";
2
2
  import { NostrEvent } from "nostr-tools";
3
- import { ISyncEventStore } from "applesauce-core/event-store";
4
3
  import { EventFactory } from "applesauce-factory";
4
+ import { IEventStoreActions, IEventStoreRead } from "applesauce-core";
5
5
  /**
6
6
  * A callback used to tell the upstream app to publish an event
7
7
  * @param label a label describing what
@@ -10,7 +10,7 @@ export type PublishMethod = (event: NostrEvent) => void | Promise<void>;
10
10
  /** The context that is passed to actions for them to use to preform actions */
11
11
  export type ActionContext = {
12
12
  /** The event store to load events from */
13
- events: ISyncEventStore;
13
+ events: IEventStoreRead;
14
14
  /** The pubkey of the signer in the event factory */
15
15
  self: string;
16
16
  /** The event factory used to build and modify events */
@@ -21,10 +21,12 @@ export type Action = (ctx: ActionContext) => Observable<NostrEvent> | AsyncGener
21
21
  export type ActionConstructor<Args extends Array<any>> = (...args: Args) => Action;
22
22
  /** The main class that runs actions */
23
23
  export declare class ActionHub {
24
- events: ISyncEventStore;
24
+ events: IEventStoreRead & IEventStoreActions;
25
25
  factory: EventFactory;
26
26
  publish?: PublishMethod | undefined;
27
- constructor(events: ISyncEventStore, factory: EventFactory, publish?: PublishMethod | undefined);
27
+ /** Whether to save all events created by actions to the event store */
28
+ saveToStore: boolean;
29
+ constructor(events: IEventStoreRead & IEventStoreActions, factory: EventFactory, publish?: PublishMethod | undefined);
28
30
  protected context: ActionContext | undefined;
29
31
  protected getContext(): Promise<ActionContext>;
30
32
  /** Runs an action in a ActionContext and converts the result to an Observable */
@@ -1,9 +1,11 @@
1
- import { from, isObservable, lastValueFrom, switchMap, toArray } from "rxjs";
1
+ import { from, isObservable, lastValueFrom, switchMap, tap, toArray } from "rxjs";
2
2
  /** The main class that runs actions */
3
3
  export class ActionHub {
4
4
  events;
5
5
  factory;
6
6
  publish;
7
+ /** Whether to save all events created by actions to the event store */
8
+ saveToStore = true;
7
9
  constructor(events, factory, publish) {
8
10
  this.events = events;
9
11
  this.factory = factory;
@@ -44,6 +46,6 @@ export class ActionHub {
44
46
  return from(this.getContext()).pipe(switchMap((ctx) => {
45
47
  const action = Action(...args);
46
48
  return ActionHub.runAction(ctx, action);
47
- }));
49
+ }), tap((event) => this.saveToStore && this.events.add(event)));
48
50
  }
49
51
  }
@@ -44,6 +44,10 @@ describe("exports", () => {
44
44
  "RemoveRelayFromRelaySet",
45
45
  "RemoveSearchRelay",
46
46
  "RemoveUserFromFollowSet",
47
+ "ReplyToLegacyMessage",
48
+ "ReplyToWrappedMessage",
49
+ "SendLegacyMessage",
50
+ "SendWrappedMessage",
47
51
  "SetDefaultBlossomServer",
48
52
  "SetListMetadata",
49
53
  "UnbookmarkEvent",
@@ -1,4 +1,4 @@
1
- import { beforeEach, describe, expect, it } from "vitest";
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
  import { EventStore } from "applesauce-core";
3
3
  import { EventFactory } from "applesauce-factory";
4
4
  import { kinds } from "nostr-tools";
@@ -24,6 +24,7 @@ beforeEach(() => {
24
24
  created_at: Math.floor(Date.now() / 1000),
25
25
  });
26
26
  events.add(muteList);
27
+ vi.useFakeTimers();
27
28
  });
28
29
  describe("MuteThread", () => {
29
30
  it("should add an event to public tags in mute list", async () => {
@@ -43,9 +44,12 @@ describe("MuteThread", () => {
43
44
  });
44
45
  describe("UnmuteThread", () => {
45
46
  it("should remove an event from public tags in mute list", async () => {
47
+ vi.setSystemTime(new Date("2025-01-01T00:00:00Z"));
46
48
  // First add the thread to mute list
47
49
  const addSpy = subscribeSpyTo(hub.exec(MuteThread, testEventId), { expectErrors: false });
48
50
  await addSpy.onComplete();
51
+ // Wait a second to ensure events have newer created_at
52
+ await vi.advanceTimersByTime(1000);
49
53
  // Then unmute it
50
54
  const spy = subscribeSpyTo(hub.exec(UnmuteThread, testEventId), { expectErrors: false });
51
55
  await spy.onComplete();
@@ -54,9 +58,12 @@ describe("UnmuteThread", () => {
54
58
  expect(mutedThings.threads).not.toContain(testEventId);
55
59
  });
56
60
  it("should remove an event from hidden tags in mute list", async () => {
61
+ vi.setSystemTime(new Date("2025-01-01T00:00:00Z"));
57
62
  // First add the thread to hidden mute list
58
63
  const addSpy = subscribeSpyTo(hub.exec(MuteThread, testEventId, true), { expectErrors: false });
59
64
  await addSpy.onComplete();
65
+ // Wait a second to ensure events have newer created_at
66
+ await vi.advanceTimersByTime(2000);
60
67
  // Then unmute it
61
68
  const spy = subscribeSpyTo(hub.exec(UnmuteThread, testEventId, true), { expectErrors: false });
62
69
  await spy.onComplete();
@@ -52,7 +52,7 @@ export function CreateBookmarkSet(title, description, additional) {
52
52
  const existing = getBookmarkEvent(events, self);
53
53
  if (existing)
54
54
  throw new Error("Bookmark list already exists");
55
- const draft = await factory.process({ kind: kinds.BookmarkList }, setListTitle(title), setListDescription(description), additional.image ? setListImage(additional.image) : undefined, additional.public ? modifyPublicTags(...additional.public.map(addEventBookmarkTag)) : undefined, additional.hidden ? modifyHiddenTags(...additional.hidden.map(addEventBookmarkTag)) : undefined);
55
+ const draft = await factory.build({ kind: kinds.BookmarkList }, setListTitle(title), setListDescription(description), additional.image ? setListImage(additional.image) : undefined, additional.public ? modifyPublicTags(...additional.public.map(addEventBookmarkTag)) : undefined, additional.hidden ? modifyHiddenTags(...additional.hidden.map(addEventBookmarkTag)) : undefined);
56
56
  yield await factory.sign(draft);
57
57
  };
58
58
  }
@@ -29,7 +29,7 @@ export function NewContacts(pubkeys) {
29
29
  const contacts = events.getReplaceable(kinds.Contacts, self);
30
30
  if (contacts)
31
31
  throw new Error("Contact list already exists");
32
- const draft = await factory.process({ kind: kinds.Contacts, tags: pubkeys?.map((p) => ["p", p]) });
32
+ const draft = await factory.build({ kind: kinds.Contacts, tags: pubkeys?.map((p) => ["p", p]) });
33
33
  yield await factory.sign(draft);
34
34
  };
35
35
  }
@@ -5,6 +5,7 @@ export * from "./contacts.js";
5
5
  export * from "./dm-relays.js";
6
6
  export * from "./favorite-relays.js";
7
7
  export * from "./follow-sets.js";
8
+ export * from "./legacy-messages.js";
8
9
  export * from "./lists.js";
9
10
  export * from "./mailboxes.js";
10
11
  export * from "./mute.js";
@@ -12,3 +13,4 @@ export * from "./pins.js";
12
13
  export * from "./profile.js";
13
14
  export * from "./relay-sets.js";
14
15
  export * from "./search-relays.js";
16
+ export * from "./wrapped-messages.js";
@@ -5,6 +5,7 @@ export * from "./contacts.js";
5
5
  export * from "./dm-relays.js";
6
6
  export * from "./favorite-relays.js";
7
7
  export * from "./follow-sets.js";
8
+ export * from "./legacy-messages.js";
8
9
  export * from "./lists.js";
9
10
  export * from "./mailboxes.js";
10
11
  export * from "./mute.js";
@@ -12,3 +13,4 @@ export * from "./pins.js";
12
13
  export * from "./profile.js";
13
14
  export * from "./relay-sets.js";
14
15
  export * from "./search-relays.js";
16
+ export * from "./wrapped-messages.js";
@@ -0,0 +1,7 @@
1
+ import { LegacyMessageBlueprintOptions } from "applesauce-factory/blueprints";
2
+ import { NostrEvent } from "nostr-tools";
3
+ import { Action } from "../action-hub.js";
4
+ /** Sends a legacy NIP-04 message to a recipient */
5
+ export declare function SendLegacyMessage(recipient: string, message: string, opts?: LegacyMessageBlueprintOptions): Action;
6
+ /** Send a reply to a legacy message */
7
+ export declare function ReplyToLegacyMessage(parent: NostrEvent, message: string, opts?: LegacyMessageBlueprintOptions): Action;
@@ -0,0 +1,20 @@
1
+ import { LegacyMessageBlueprint, LegacyMessageReplyBlueprint, } from "applesauce-factory/blueprints";
2
+ import { kinds } from "nostr-tools";
3
+ /** Sends a legacy NIP-04 message to a recipient */
4
+ export function SendLegacyMessage(recipient, message, opts) {
5
+ return async function* ({ factory }) {
6
+ const draft = await factory.create(LegacyMessageBlueprint, recipient, message, opts);
7
+ // Return the signed message
8
+ yield await factory.sign(draft);
9
+ };
10
+ }
11
+ /** Send a reply to a legacy message */
12
+ export function ReplyToLegacyMessage(parent, message, opts) {
13
+ return async function* ({ factory }) {
14
+ if (parent.kind !== kinds.EncryptedDirectMessage)
15
+ throw new Error("Legacy messages can only reply to other legacy messages");
16
+ const draft = await factory.create(LegacyMessageReplyBlueprint, parent, message, opts);
17
+ // Return the signed message
18
+ yield await factory.sign(draft);
19
+ };
20
+ }
@@ -27,7 +27,7 @@ export function CreatePinList(pins = []) {
27
27
  const existing = events.getReplaceable(kinds.Pinlist, self);
28
28
  if (existing)
29
29
  throw new Error("Pin list already exists");
30
- const draft = await factory.process({ kind: kinds.Pinlist }, modifyPublicTags(...pins.map((event) => addEventTag(event.id))));
30
+ const draft = await factory.build({ kind: kinds.Pinlist }, modifyPublicTags(...pins.map((event) => addEventTag(event.id))));
31
31
  yield await factory.sign(draft);
32
32
  };
33
33
  }
@@ -0,0 +1,20 @@
1
+ import { Rumor } from "applesauce-core/helpers";
2
+ import { WrappedMessageBlueprintOptions } from "applesauce-factory/blueprints";
3
+ import { GiftWrapOptions } from "applesauce-factory/operations/event";
4
+ import { Action } from "../action-hub.js";
5
+ /**
6
+ * Sends a NIP-17 wrapped message to a conversation
7
+ * @param participants - A conversation identifier, user pubkey, or a list of participant pubkeys
8
+ * @param message - The message to send
9
+ * @param opts - Options for the wrapped message and gift wrap
10
+ * @returns Signed gift wrapped messages to send
11
+ */
12
+ export declare function SendWrappedMessage(participants: string | string[], message: string, opts?: WrappedMessageBlueprintOptions & GiftWrapOptions): Action;
13
+ /**
14
+ * Sends a NIP-17 reply to a wrapped message
15
+ * @param parent - The parent wrapped message
16
+ * @param message - The message to send
17
+ * @param opts - Options for the wrapped message and gift wrap
18
+ * @returns Signed gift wrapped messages to send
19
+ */
20
+ export declare function ReplyToWrappedMessage(parent: Rumor, message: string, opts?: WrappedMessageBlueprintOptions & GiftWrapOptions): Action;
@@ -0,0 +1,37 @@
1
+ import { getConversationParticipants } from "applesauce-core/helpers";
2
+ import { GiftWrapBlueprint, WrappedMessageBlueprint, WrappedMessageReplyBlueprint, } from "applesauce-factory/blueprints";
3
+ /**
4
+ * Sends a NIP-17 wrapped message to a conversation
5
+ * @param participants - A conversation identifier, user pubkey, or a list of participant pubkeys
6
+ * @param message - The message to send
7
+ * @param opts - Options for the wrapped message and gift wrap
8
+ * @returns Signed gift wrapped messages to send
9
+ */
10
+ export function SendWrappedMessage(participants, message, opts) {
11
+ return async function* ({ factory }) {
12
+ const rumor = await factory.create(WrappedMessageBlueprint, participants, message, opts);
13
+ // Get the pubkeys to send this message to (will include the sender)
14
+ const pubkeys = getConversationParticipants(rumor);
15
+ for (const pubkey of pubkeys) {
16
+ yield await factory.create(GiftWrapBlueprint, pubkey, rumor, opts);
17
+ }
18
+ };
19
+ }
20
+ /**
21
+ * Sends a NIP-17 reply to a wrapped message
22
+ * @param parent - The parent wrapped message
23
+ * @param message - The message to send
24
+ * @param opts - Options for the wrapped message and gift wrap
25
+ * @returns Signed gift wrapped messages to send
26
+ */
27
+ export function ReplyToWrappedMessage(parent, message, opts) {
28
+ return async function* ({ factory }) {
29
+ // Create the reply message
30
+ const rumor = await factory.create(WrappedMessageReplyBlueprint, parent, message, opts);
31
+ // Get the pubkeys to send this message to (will include the sender)
32
+ const pubkeys = getConversationParticipants(parent);
33
+ for (const pubkey of pubkeys) {
34
+ yield await factory.create(GiftWrapBlueprint, pubkey, rumor, opts);
35
+ }
36
+ };
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-actions",
3
- "version": "0.0.0-next-20250526151506",
3
+ "version": "0.0.0-next-20250609183606",
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-20250526151506",
36
- "applesauce-factory": "0.0.0-next-20250526151506",
35
+ "applesauce-core": "0.0.0-next-20250609183606",
36
+ "applesauce-factory": "0.0.0-next-20250609183606",
37
37
  "nostr-tools": "^2.13",
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-20250526151506",
43
+ "applesauce-signers": "0.0.0-next-20250609183606",
44
44
  "nanoid": "^5.1.5",
45
45
  "typescript": "^5.8.3",
46
46
  "vitest": "^3.1.1"