applesauce-core 0.0.0-next-20250425210455 → 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 +28 -10
- package/dist/helpers/lists.d.ts +40 -12
- package/dist/helpers/lists.js +60 -21
- package/dist/queries/index.d.ts +1 -0
- package/dist/queries/index.js +1 -0
- package/dist/queries/relays.d.ts +27 -0
- package/dist/queries/relays.js +44 -0
- package/package.json +1 -1
- package/dist/event-store/event-store.test.d.ts +0 -1
- package/dist/event-store/event-store.test.js +0 -74
- package/dist/helpers/blossom.test.d.ts +0 -1
- package/dist/helpers/blossom.test.js +0 -13
- package/dist/helpers/bookmark.d.ts +0 -15
- package/dist/helpers/bookmark.js +0 -27
- package/dist/helpers/event.test.d.ts +0 -1
- package/dist/helpers/event.test.js +0 -36
- package/dist/helpers/file-metadata.test.d.ts +0 -1
- package/dist/helpers/file-metadata.test.js +0 -103
- package/dist/helpers/hidden-tags.test.d.ts +0 -1
- package/dist/helpers/hidden-tags.test.js +0 -29
- package/dist/helpers/mailboxes.test.d.ts +0 -1
- package/dist/helpers/mailboxes.test.js +0 -81
- package/dist/helpers/media-attachment.d.ts +0 -42
- package/dist/helpers/media-attachment.js +0 -72
- package/dist/helpers/media-attachment.test.d.ts +0 -1
- package/dist/helpers/media-attachment.test.js +0 -59
- package/dist/helpers/media-post.d.ts +0 -4
- package/dist/helpers/media-post.js +0 -6
- package/dist/helpers/mute.d.ts +0 -14
- package/dist/helpers/mute.js +0 -23
- package/dist/helpers/pipe.d.ts +0 -10
- package/dist/helpers/pipe.js +0 -3
- package/dist/helpers/tags.test.d.ts +0 -1
- package/dist/helpers/tags.test.js +0 -24
- package/dist/helpers/threading.test.d.ts +0 -1
- package/dist/helpers/threading.test.js +0 -41
- package/dist/observable/get-value.d.ts +0 -3
- package/dist/observable/get-value.js +0 -14
- package/dist/observable/share-latest-value.d.ts +0 -6
- package/dist/observable/share-latest-value.js +0 -24
- package/dist/observable/simple-timeout.test.d.ts +0 -1
- package/dist/observable/simple-timeout.test.js +0 -34
- package/dist/queries/comment.d.ts +0 -4
- package/dist/queries/comment.js +0 -14
- package/dist/query-store/query-store.test.d.ts +0 -1
- package/dist/query-store/query-store.test.js +0 -33
package/README.md
CHANGED
|
@@ -1,37 +1,55 @@
|
|
|
1
1
|
# applesauce-core
|
|
2
2
|
|
|
3
|
-
AppleSauce
|
|
3
|
+
AppleSauce is a collection of utilities for building reactive nostr applications. The core package provides an in-memory event database and reactive queries to help you build nostr UIs with less code.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Key Components
|
|
6
|
+
|
|
7
|
+
- **Helpers**: Core utility methods for parsing and extracting data from nostr events
|
|
8
|
+
- **EventStore**: In-memory database for storing and subscribing to nostr events
|
|
9
|
+
- **QueryStore**: Manages queries and ensures efficient subscription handling
|
|
10
|
+
- **Queries**: Complex subscriptions for common nostr data patterns
|
|
11
|
+
|
|
12
|
+
## Documentation
|
|
13
|
+
|
|
14
|
+
For detailed documentation and guides, visit:
|
|
15
|
+
|
|
16
|
+
- [Getting Started](https://hzrd149.github.io/applesauce/introduction/getting-started)
|
|
17
|
+
- [API Reference](https://hzrd149.github.io/applesauce/typedoc/)
|
|
18
|
+
|
|
19
|
+
## Example
|
|
6
20
|
|
|
7
21
|
```js
|
|
8
22
|
import { EventStore, QueryStore } from "applesauce-core";
|
|
9
23
|
import { Relay } from "nostr-tools/relay";
|
|
10
24
|
|
|
11
|
-
//
|
|
25
|
+
// Create a single EventStore instance for your app
|
|
12
26
|
const eventStore = new EventStore();
|
|
13
27
|
|
|
14
|
-
//
|
|
28
|
+
// Create a QueryStore to manage subscriptions efficiently
|
|
15
29
|
const queryStore = new QueryStore(eventStore);
|
|
16
30
|
|
|
17
|
-
// Use nostr
|
|
31
|
+
// Use any nostr library for relay connections (nostr-tools, ndk, nostrify, etc...)
|
|
18
32
|
const relay = await Relay.connect("wss://relay.example.com");
|
|
19
33
|
|
|
20
|
-
|
|
34
|
+
// Subscribe to events and add them to the store
|
|
35
|
+
const sub = relay.subscribe([{ authors: ["3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"] }], {
|
|
21
36
|
onevent(event) {
|
|
22
37
|
eventStore.add(event);
|
|
23
38
|
},
|
|
24
39
|
});
|
|
25
40
|
|
|
26
|
-
//
|
|
27
|
-
const profile = queryStore.
|
|
41
|
+
// Subscribe to profile changes using ProfileQuery
|
|
42
|
+
const profile = queryStore.createQuery(
|
|
43
|
+
ProfileQuery,
|
|
44
|
+
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
|
|
45
|
+
);
|
|
28
46
|
|
|
29
47
|
profile.subscribe((parsed) => {
|
|
30
48
|
if (parsed) console.log(parsed);
|
|
31
49
|
});
|
|
32
50
|
|
|
33
|
-
//
|
|
34
|
-
const timeline = queryStore.
|
|
51
|
+
// Subscribe to a timeline of events
|
|
52
|
+
const timeline = queryStore.createQuery(TimelineQuery, { kinds: [1] });
|
|
35
53
|
|
|
36
54
|
timeline.subscribe((events) => {
|
|
37
55
|
console.log(events);
|
package/dist/helpers/lists.d.ts
CHANGED
|
@@ -1,28 +1,56 @@
|
|
|
1
1
|
import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
|
|
2
2
|
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
export declare const FAVORITE_RELAYS_KIND = 10012;
|
|
4
|
+
export type ReadListTags = "public" | "hidden" | "all";
|
|
5
|
+
/** Returns all the tags of a list or set */
|
|
6
|
+
export declare function getListTags(list: NostrEvent, type?: ReadListTags): string[][];
|
|
3
7
|
/**
|
|
4
8
|
* Checks if an event pointer is anywhere in a list or set
|
|
5
9
|
* NOTE: Ignores the `relay` field in EventPointer
|
|
6
|
-
*
|
|
10
|
+
* @param list - The list or set to check
|
|
11
|
+
* @param pointer - The event pointer to check
|
|
12
|
+
* @param type - Which types of tags to check
|
|
7
13
|
*/
|
|
8
|
-
export declare function isEventPointerInList(list: NostrEvent, pointer: string | EventPointer): boolean;
|
|
14
|
+
export declare function isEventPointerInList(list: NostrEvent, pointer: string | EventPointer, type?: ReadListTags): boolean;
|
|
9
15
|
/**
|
|
10
16
|
* Checks if an address pointer is anywhere in a list or set
|
|
11
17
|
* NOTE: Ignores the `relay` field in AddressPointer
|
|
12
|
-
*
|
|
18
|
+
* @param list - The list or set to check
|
|
19
|
+
* @param pointer - The address pointer to check
|
|
20
|
+
* @param type - Which types of tags to check
|
|
13
21
|
*/
|
|
14
|
-
export declare function isAddressPointerInList(list: NostrEvent, pointer: string | AddressPointer): boolean;
|
|
22
|
+
export declare function isAddressPointerInList(list: NostrEvent, pointer: string | AddressPointer, type?: ReadListTags): boolean;
|
|
15
23
|
/**
|
|
16
24
|
* Checks if an profile pointer is anywhere in a list or set
|
|
17
25
|
* NOTE: Ignores the `relay` field in ProfilePointer
|
|
18
|
-
*
|
|
26
|
+
* @param list - The list or set to check
|
|
27
|
+
* @param pointer - The profile pointer to check
|
|
28
|
+
* @param type - Which types of tags to check
|
|
19
29
|
*/
|
|
20
|
-
export declare function isProfilePointerInList(list: NostrEvent, pointer: string | ProfilePointer): boolean;
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export declare function
|
|
30
|
+
export declare function isProfilePointerInList(list: NostrEvent, pointer: string | ProfilePointer, type?: ReadListTags): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Returns all the EventPointer in a list or set
|
|
33
|
+
* @param list - The list or set to get the event pointers from
|
|
34
|
+
* @param type - Which types of tags to read
|
|
35
|
+
*/
|
|
36
|
+
export declare function getEventPointersFromList(list: NostrEvent, type?: ReadListTags): EventPointer[];
|
|
37
|
+
/**
|
|
38
|
+
* Returns all the AddressPointer in a list or set
|
|
39
|
+
* @param list - The list or set to get the address pointers from
|
|
40
|
+
* @param type - Which types of tags to read
|
|
41
|
+
*/
|
|
42
|
+
export declare function getAddressPointersFromList(list: NostrEvent, type?: ReadListTags): AddressPointer[];
|
|
43
|
+
/**
|
|
44
|
+
* Returns all the ProfilePointer in a list or set
|
|
45
|
+
* @param list - The list or set to get the profile pointers from
|
|
46
|
+
* @param type - Which types of tags to read
|
|
47
|
+
*/
|
|
48
|
+
export declare function getProfilePointersFromList(list: NostrEvent, type?: ReadListTags): ProfilePointer[];
|
|
49
|
+
/**
|
|
50
|
+
* Returns a deduplicated array of all 'relay' tags in a list or set
|
|
51
|
+
* @param list - The list or set to get the relays from
|
|
52
|
+
* @param type - Which types of tags to read
|
|
53
|
+
*/
|
|
54
|
+
export declare function getRelaysFromList(list: NostrEvent, type?: ReadListTags): string[];
|
|
27
55
|
/** Returns if an event is a valid list or set */
|
|
28
56
|
export declare function isValidList(event: NostrEvent): boolean;
|
package/dist/helpers/lists.js
CHANGED
|
@@ -3,48 +3,87 @@ import { getHiddenTags } from "./hidden-tags.js";
|
|
|
3
3
|
import { getAddressPointerFromATag, getCoordinateFromAddressPointer, getEventPointerFromETag, getProfilePointerFromPTag, } from "./pointers.js";
|
|
4
4
|
import { isATag, isETag, isPTag, processTags } from "./tags.js";
|
|
5
5
|
import { getReplaceableIdentifier } from "./event.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import { mergeRelaySets } from "./relays.js";
|
|
7
|
+
export const FAVORITE_RELAYS_KIND = 10012;
|
|
8
|
+
/** Returns all the tags of a list or set */
|
|
9
|
+
export function getListTags(list, type) {
|
|
10
|
+
switch (type) {
|
|
11
|
+
case "public":
|
|
12
|
+
return list.tags;
|
|
13
|
+
case "hidden":
|
|
14
|
+
return getHiddenTags(list) ?? [];
|
|
15
|
+
default:
|
|
16
|
+
case "all":
|
|
17
|
+
return [...(getHiddenTags(list) ?? []), ...list.tags];
|
|
18
|
+
}
|
|
9
19
|
}
|
|
10
20
|
/**
|
|
11
21
|
* Checks if an event pointer is anywhere in a list or set
|
|
12
22
|
* NOTE: Ignores the `relay` field in EventPointer
|
|
13
|
-
*
|
|
23
|
+
* @param list - The list or set to check
|
|
24
|
+
* @param pointer - The event pointer to check
|
|
25
|
+
* @param type - Which types of tags to check
|
|
14
26
|
*/
|
|
15
|
-
export function isEventPointerInList(list, pointer) {
|
|
27
|
+
export function isEventPointerInList(list, pointer, type) {
|
|
16
28
|
const id = typeof pointer === "string" ? pointer : pointer.id;
|
|
17
|
-
|
|
29
|
+
const tags = getListTags(list, type);
|
|
30
|
+
return tags.some((t) => t[0] === "e" && t[1] === id);
|
|
18
31
|
}
|
|
19
32
|
/**
|
|
20
33
|
* Checks if an address pointer is anywhere in a list or set
|
|
21
34
|
* NOTE: Ignores the `relay` field in AddressPointer
|
|
22
|
-
*
|
|
35
|
+
* @param list - The list or set to check
|
|
36
|
+
* @param pointer - The address pointer to check
|
|
37
|
+
* @param type - Which types of tags to check
|
|
23
38
|
*/
|
|
24
|
-
export function isAddressPointerInList(list, pointer) {
|
|
39
|
+
export function isAddressPointerInList(list, pointer, type) {
|
|
25
40
|
const cord = typeof pointer === "string" ? pointer : getCoordinateFromAddressPointer(pointer);
|
|
26
|
-
|
|
41
|
+
const tags = getListTags(list, type);
|
|
42
|
+
return tags.some((t) => t[0] === "a" && t[1] === cord);
|
|
27
43
|
}
|
|
28
44
|
/**
|
|
29
45
|
* Checks if an profile pointer is anywhere in a list or set
|
|
30
46
|
* NOTE: Ignores the `relay` field in ProfilePointer
|
|
31
|
-
*
|
|
47
|
+
* @param list - The list or set to check
|
|
48
|
+
* @param pointer - The profile pointer to check
|
|
49
|
+
* @param type - Which types of tags to check
|
|
32
50
|
*/
|
|
33
|
-
export function isProfilePointerInList(list, pointer) {
|
|
51
|
+
export function isProfilePointerInList(list, pointer, type) {
|
|
34
52
|
const pubkey = typeof pointer === "string" ? pointer : pointer.pubkey;
|
|
35
|
-
|
|
53
|
+
const tags = getListTags(list, type);
|
|
54
|
+
return tags.some((t) => t[0] === "p" && t[1] === pubkey);
|
|
36
55
|
}
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Returns all the EventPointer in a list or set
|
|
58
|
+
* @param list - The list or set to get the event pointers from
|
|
59
|
+
* @param type - Which types of tags to read
|
|
60
|
+
*/
|
|
61
|
+
export function getEventPointersFromList(list, type) {
|
|
62
|
+
return processTags(getListTags(list, type), (tag) => (isETag(tag) ? tag : undefined), getEventPointerFromETag);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Returns all the AddressPointer in a list or set
|
|
66
|
+
* @param list - The list or set to get the address pointers from
|
|
67
|
+
* @param type - Which types of tags to read
|
|
68
|
+
*/
|
|
69
|
+
export function getAddressPointersFromList(list, type) {
|
|
70
|
+
return processTags(getListTags(list, type), (t) => (isATag(t) ? t : undefined), getAddressPointerFromATag);
|
|
40
71
|
}
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Returns all the ProfilePointer in a list or set
|
|
74
|
+
* @param list - The list or set to get the profile pointers from
|
|
75
|
+
* @param type - Which types of tags to read
|
|
76
|
+
*/
|
|
77
|
+
export function getProfilePointersFromList(list, type) {
|
|
78
|
+
return processTags(getListTags(list, type), (t) => (isPTag(t) ? t : undefined), getProfilePointerFromPTag);
|
|
44
79
|
}
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Returns a deduplicated array of all 'relay' tags in a list or set
|
|
82
|
+
* @param list - The list or set to get the relays from
|
|
83
|
+
* @param type - Which types of tags to read
|
|
84
|
+
*/
|
|
85
|
+
export function getRelaysFromList(list, type) {
|
|
86
|
+
return mergeRelaySets(processTags(getListTags(list, type), (t) => (t[0] === "relay" ? t[1] : undefined)));
|
|
48
87
|
}
|
|
49
88
|
/** Returns if an event is a valid list or set */
|
|
50
89
|
export function isValidList(event) {
|
package/dist/queries/index.d.ts
CHANGED
package/dist/queries/index.js
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AddressPointer } from "nostr-tools/nip19";
|
|
2
|
+
import { ReadListTags } from "../helpers/lists.js";
|
|
3
|
+
import { Query } from "../query-store/query-store.js";
|
|
4
|
+
/**
|
|
5
|
+
* A query that returns all favorite relays for a pubkey
|
|
6
|
+
* @param pubkey - The pubkey to get the favorite relays for
|
|
7
|
+
* @param type - Which types of tags to read
|
|
8
|
+
*/
|
|
9
|
+
export declare function FavoriteRelays(pubkey: string, type?: ReadListTags): Query<string[] | undefined>;
|
|
10
|
+
/**
|
|
11
|
+
* A query that returns all favorite relay sets for a pubkey
|
|
12
|
+
* @param pubkey - The pubkey to get the favorite relay sets for
|
|
13
|
+
* @param type - Which types of tags to read
|
|
14
|
+
*/
|
|
15
|
+
export declare function FavoriteRelaySets(pubkey: string, type?: ReadListTags): Query<AddressPointer[] | undefined>;
|
|
16
|
+
/**
|
|
17
|
+
* A query that returns all search relays for a pubkey
|
|
18
|
+
* @param pubkey - The pubkey to get the search relays for
|
|
19
|
+
* @param type - Which types of tags to read
|
|
20
|
+
*/
|
|
21
|
+
export declare function SearchRelays(pubkey: string, type?: ReadListTags): Query<string[] | undefined>;
|
|
22
|
+
/**
|
|
23
|
+
* A query that returns all blocked relays for a pubkey
|
|
24
|
+
* @param pubkey - The pubkey to get the blocked relays for
|
|
25
|
+
* @param type - Which types of tags to read
|
|
26
|
+
*/
|
|
27
|
+
export declare function BlockedRelays(pubkey: string, type?: ReadListTags): Query<string[] | undefined>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { identity, map } from "rxjs";
|
|
3
|
+
import { FAVORITE_RELAYS_KIND, getAddressPointersFromList, getRelaysFromList } from "../helpers/lists.js";
|
|
4
|
+
import { listenLatestUpdates } from "../observable/listen-latest-updates.js";
|
|
5
|
+
/**
|
|
6
|
+
* A query that returns all favorite relays for a pubkey
|
|
7
|
+
* @param pubkey - The pubkey to get the favorite relays for
|
|
8
|
+
* @param type - Which types of tags to read
|
|
9
|
+
*/
|
|
10
|
+
export function FavoriteRelays(pubkey, type) {
|
|
11
|
+
return (events) => {
|
|
12
|
+
return events.replaceable(FAVORITE_RELAYS_KIND, pubkey).pipe(type !== "public" ? listenLatestUpdates(events) : map(identity), map((e) => e && getRelaysFromList(e, type)));
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A query that returns all favorite relay sets for a pubkey
|
|
17
|
+
* @param pubkey - The pubkey to get the favorite relay sets for
|
|
18
|
+
* @param type - Which types of tags to read
|
|
19
|
+
*/
|
|
20
|
+
export function FavoriteRelaySets(pubkey, type) {
|
|
21
|
+
return (events) => {
|
|
22
|
+
return events.replaceable(FAVORITE_RELAYS_KIND, pubkey).pipe(type !== "public" ? listenLatestUpdates(events) : map(identity), map((e) => e && getAddressPointersFromList(e, type)));
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A query that returns all search relays for a pubkey
|
|
27
|
+
* @param pubkey - The pubkey to get the search relays for
|
|
28
|
+
* @param type - Which types of tags to read
|
|
29
|
+
*/
|
|
30
|
+
export function SearchRelays(pubkey, type) {
|
|
31
|
+
return (events) => {
|
|
32
|
+
return events.replaceable(kinds.SearchRelaysList, pubkey).pipe(type !== "public" ? listenLatestUpdates(events) : map(identity), map((e) => e && getRelaysFromList(e, type)));
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A query that returns all blocked relays for a pubkey
|
|
37
|
+
* @param pubkey - The pubkey to get the blocked relays for
|
|
38
|
+
* @param type - Which types of tags to read
|
|
39
|
+
*/
|
|
40
|
+
export function BlockedRelays(pubkey, type) {
|
|
41
|
+
return (events) => {
|
|
42
|
+
return events.replaceable(kinds.BlockedRelaysList, pubkey).pipe(type !== "public" ? listenLatestUpdates(events) : map(identity), map((e) => e && getRelaysFromList(e, type)));
|
|
43
|
+
};
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { kinds } from "nostr-tools";
|
|
3
|
-
import { EventStore } from "./event-store.js";
|
|
4
|
-
import { addSeenRelay, getSeenRelays } from "../helpers/relays.js";
|
|
5
|
-
let eventStore;
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
eventStore = new EventStore();
|
|
8
|
-
});
|
|
9
|
-
const event = {
|
|
10
|
-
content: '{"name":"hzrd149","picture":"https://cdn.hzrd149.com/5ed3fe5df09a74e8c126831eac999364f9eb7624e2b86d521521b8021de20bdc.png","about":"JavaScript developer working on some nostr stuff\\n- noStrudel https://nostrudel.ninja/ \\n- Blossom https://github.com/hzrd149/blossom \\n- Applesauce https://hzrd149.github.io/applesauce/","website":"https://hzrd149.com","nip05":"_@hzrd149.com","lud16":"hzrd1499@minibits.cash","pubkey":"266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5","display_name":"hzrd149","displayName":"hzrd149","banner":""}',
|
|
11
|
-
created_at: 1738362529,
|
|
12
|
-
id: "e9df8d5898c4ccfbd21fcd59f3f48abb3ff0ab7259b19570e2f1756de1e9306b",
|
|
13
|
-
kind: 0,
|
|
14
|
-
pubkey: "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
|
|
15
|
-
sig: "465a47b93626a587bf81dadc2b306b8f713a62db31d6ce1533198e9ae1e665a6eaf376a03250bf9ffbb02eb9059c8eafbd37ae1092d05d215757575bd8357586",
|
|
16
|
-
tags: [],
|
|
17
|
-
};
|
|
18
|
-
describe("add", () => {
|
|
19
|
-
it("should return original event in case of duplicates", () => {
|
|
20
|
-
const a = { ...event };
|
|
21
|
-
expect(eventStore.add(a)).toBe(a);
|
|
22
|
-
const b = { ...event };
|
|
23
|
-
expect(eventStore.add(b)).toBe(a);
|
|
24
|
-
const c = { ...event };
|
|
25
|
-
expect(eventStore.add(c)).toBe(a);
|
|
26
|
-
});
|
|
27
|
-
it("should merge seen relays on duplicate events", () => {
|
|
28
|
-
const a = { ...event };
|
|
29
|
-
addSeenRelay(a, "wss://relay.a.com");
|
|
30
|
-
eventStore.add(a);
|
|
31
|
-
const b = { ...event };
|
|
32
|
-
addSeenRelay(b, "wss://relay.b.com");
|
|
33
|
-
eventStore.add(b);
|
|
34
|
-
expect(eventStore.getEvent(event.id)).toBeDefined();
|
|
35
|
-
expect([...getSeenRelays(eventStore.getEvent(event.id))]).toEqual(expect.arrayContaining(["wss://relay.a.com", "wss://relay.b.com"]));
|
|
36
|
-
});
|
|
37
|
-
it("should ignore deleted events", () => {
|
|
38
|
-
const deleteEvent = {
|
|
39
|
-
id: "delete event id",
|
|
40
|
-
kind: kinds.EventDeletion,
|
|
41
|
-
created_at: event.created_at + 100,
|
|
42
|
-
pubkey: event.pubkey,
|
|
43
|
-
tags: [["e", event.id]],
|
|
44
|
-
sig: "this should be ignored for the test",
|
|
45
|
-
content: "test",
|
|
46
|
-
};
|
|
47
|
-
// add delete event first
|
|
48
|
-
eventStore.add(deleteEvent);
|
|
49
|
-
// now event should be ignored
|
|
50
|
-
eventStore.add(event);
|
|
51
|
-
expect(eventStore.getEvent(event.id)).toBeUndefined();
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
describe("verifyEvent", () => {
|
|
55
|
-
it("should be called for all events added", () => {
|
|
56
|
-
const verifyEvent = vi.fn().mockReturnValue(true);
|
|
57
|
-
eventStore.verifyEvent = verifyEvent;
|
|
58
|
-
eventStore.add(event);
|
|
59
|
-
expect(verifyEvent).toHaveBeenCalledWith(event);
|
|
60
|
-
});
|
|
61
|
-
it("should not be called for duplicate events", () => {
|
|
62
|
-
const verifyEvent = vi.fn().mockReturnValue(true);
|
|
63
|
-
eventStore.verifyEvent = verifyEvent;
|
|
64
|
-
const a = { ...event };
|
|
65
|
-
eventStore.add(a);
|
|
66
|
-
expect(verifyEvent).toHaveBeenCalledWith(a);
|
|
67
|
-
const b = { ...event };
|
|
68
|
-
eventStore.add(b);
|
|
69
|
-
expect(verifyEvent).toHaveBeenCalledTimes(1);
|
|
70
|
-
const c = { ...event };
|
|
71
|
-
eventStore.add(c);
|
|
72
|
-
expect(verifyEvent).toHaveBeenCalledTimes(1);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { areBlossomServersEqual } from "./blossom.js";
|
|
3
|
-
describe("areBlossomServersEqual", () => {
|
|
4
|
-
it("should ignore path", () => {
|
|
5
|
-
expect(areBlossomServersEqual("https://cdn.server.com/pathname", "https://cdn.server.com")).toBe(true);
|
|
6
|
-
});
|
|
7
|
-
it("should not ignore protocol", () => {
|
|
8
|
-
expect(areBlossomServersEqual("http://cdn.server.com", "https://cdn.server.com")).toBe(false);
|
|
9
|
-
});
|
|
10
|
-
it("should not ignore port", () => {
|
|
11
|
-
expect(areBlossomServersEqual("http://cdn.server.com:4658", "https://cdn.server.com")).toBe(false);
|
|
12
|
-
});
|
|
13
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { NostrEvent } from "nostr-tools";
|
|
2
|
-
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
3
|
-
export declare const BookmarkPublicSymbol: unique symbol;
|
|
4
|
-
export declare const BookmarkHiddenSymbol: unique symbol;
|
|
5
|
-
export type Bookmarks = {
|
|
6
|
-
notes: EventPointer[];
|
|
7
|
-
articles: AddressPointer[];
|
|
8
|
-
hashtags: string[];
|
|
9
|
-
urls: string[];
|
|
10
|
-
};
|
|
11
|
-
export declare function parseBookmarkTags(tags: string[][]): Bookmarks;
|
|
12
|
-
/** Returns the public bookmarks of the event */
|
|
13
|
-
export declare function getBookmarks(bookmark: NostrEvent): Bookmarks;
|
|
14
|
-
/** Returns the bookmarks of the event if its unlocked */
|
|
15
|
-
export declare function getHiddenBookmarks(bookmark: NostrEvent): Bookmarks | undefined;
|
package/dist/helpers/bookmark.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { kinds } from "nostr-tools";
|
|
2
|
-
import { getAddressPointerFromATag, getEventPointerFromETag } from "./pointers.js";
|
|
3
|
-
import { getOrComputeCachedValue } from "./cache.js";
|
|
4
|
-
import { getHiddenTags } from "./index.js";
|
|
5
|
-
export const BookmarkPublicSymbol = Symbol.for("bookmark-public");
|
|
6
|
-
export const BookmarkHiddenSymbol = Symbol.for("bookmark-hidden");
|
|
7
|
-
export function parseBookmarkTags(tags) {
|
|
8
|
-
const notes = tags.filter((t) => t[0] === "e" && t[1]).map(getEventPointerFromETag);
|
|
9
|
-
const articles = tags
|
|
10
|
-
.filter((t) => t[0] === "a" && t[1])
|
|
11
|
-
.map(getAddressPointerFromATag)
|
|
12
|
-
.filter((addr) => addr.kind === kinds.LongFormArticle);
|
|
13
|
-
const hashtags = tags.filter((t) => t[0] === "t" && t[1]).map((t) => t[1]);
|
|
14
|
-
const urls = tags.filter((t) => t[0] === "r" && t[1]).map((t) => t[1]);
|
|
15
|
-
return { notes, articles, hashtags, urls };
|
|
16
|
-
}
|
|
17
|
-
/** Returns the public bookmarks of the event */
|
|
18
|
-
export function getBookmarks(bookmark) {
|
|
19
|
-
return getOrComputeCachedValue(bookmark, BookmarkPublicSymbol, () => parseBookmarkTags(bookmark.tags));
|
|
20
|
-
}
|
|
21
|
-
/** Returns the bookmarks of the event if its unlocked */
|
|
22
|
-
export function getHiddenBookmarks(bookmark) {
|
|
23
|
-
return getOrComputeCachedValue(bookmark, BookmarkHiddenSymbol, () => {
|
|
24
|
-
const tags = getHiddenTags(bookmark);
|
|
25
|
-
return tags && parseBookmarkTags(tags);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { EventIndexableTagsSymbol, getIndexableTags, getTagValue } from "./event.js";
|
|
3
|
-
const event = {
|
|
4
|
-
content: "",
|
|
5
|
-
created_at: 1732889913,
|
|
6
|
-
id: "2d53511f321cc82dd13eedfb597c9fe834d12d271c10d8068e9d8cfb8f58d1b4",
|
|
7
|
-
kind: 30000,
|
|
8
|
-
pubkey: "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
|
|
9
|
-
sig: "e6a442487ef44a8a00ec1e0a852e547991fcd5cbf19aa1a4219fa65d6f41022675e0745207649f4b16fe9a6c5c7c3693dc3e13966ffa5b2891634867c874cf22",
|
|
10
|
-
tags: [
|
|
11
|
-
["d", "qRxLhBbTfRlxsvKSu0iUl"],
|
|
12
|
-
["title", "Musicians"],
|
|
13
|
-
["client", "noStrudel", "31990:266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5:1686066542546"],
|
|
14
|
-
["p", "2842e34860c59dfacd5df48ba7a65065e6760d08c35f779553d83c2c2310b493"],
|
|
15
|
-
["p", "28ca019b78b494c25a9da2d645975a8501c7e99b11302e5cbe748ee593fcb2cc"],
|
|
16
|
-
["p", "f46192b8b9be1b43fc30ea27c7cb16210aede17252b3aa9692fbb3f2ba153199"],
|
|
17
|
-
],
|
|
18
|
-
};
|
|
19
|
-
describe("getIndexableTags", () => {
|
|
20
|
-
it("should return a set of indexable tags for event", () => {
|
|
21
|
-
expect(Array.from(getIndexableTags(event))).toEqual(expect.arrayContaining([
|
|
22
|
-
"p:2842e34860c59dfacd5df48ba7a65065e6760d08c35f779553d83c2c2310b493",
|
|
23
|
-
"p:28ca019b78b494c25a9da2d645975a8501c7e99b11302e5cbe748ee593fcb2cc",
|
|
24
|
-
"p:f46192b8b9be1b43fc30ea27c7cb16210aede17252b3aa9692fbb3f2ba153199",
|
|
25
|
-
]));
|
|
26
|
-
});
|
|
27
|
-
it("should cache value on EventIndexableTagsSymbol", () => {
|
|
28
|
-
getIndexableTags(event);
|
|
29
|
-
expect(Reflect.has(event, EventIndexableTagsSymbol)).toBe(true);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
describe("getTagValue", () => {
|
|
33
|
-
it("should return value of tag if present", () => {
|
|
34
|
-
expect(getTagValue(event, "title")).toBe("Musicians");
|
|
35
|
-
});
|
|
36
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { getFileMetadataFromImetaTag, parseFileMetadataTags } from "./file-metadata.js";
|
|
3
|
-
describe("file metadata helpers", () => {
|
|
4
|
-
describe("parseFileMetadataTags", () => {
|
|
5
|
-
it("should parse a simple 1060 event", () => {
|
|
6
|
-
const tags = [
|
|
7
|
-
["url", "https://image.nostr.build/30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae.gif"],
|
|
8
|
-
["ox", "30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae"],
|
|
9
|
-
["fallback", "https://media.tenor.com/wpvrkjn192gAAAAC/daenerys-targaryen.gif"],
|
|
10
|
-
["x", "77fcf42b2b720babcdbe686eff67273d8a68862d74a2672db672bc48439a3ea5"],
|
|
11
|
-
["m", "image/gif"],
|
|
12
|
-
["dim", "360x306"],
|
|
13
|
-
["bh", "L38zleNL00~W^kRj0L-p0KM_^kx]"],
|
|
14
|
-
["blurhash", "L38zleNL00~W^kRj0L-p0KM_^kx]"],
|
|
15
|
-
[
|
|
16
|
-
"thumb",
|
|
17
|
-
"https://image.nostr.build/thumb/30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae.gif",
|
|
18
|
-
],
|
|
19
|
-
["t", "gifbuddy"],
|
|
20
|
-
["summary", "Khaleesi call dragons Daenerys Targaryen"],
|
|
21
|
-
["alt", "a woman with blonde hair and a brooch on her shoulder"],
|
|
22
|
-
[
|
|
23
|
-
"thumb",
|
|
24
|
-
"https://media.tenor.com/wpvrkjn192gAAAAx/daenerys-targaryen.webp",
|
|
25
|
-
"5d92423664fc15874b1d26c70a05a541ec09b5c438bf157977a87c8e64b31463",
|
|
26
|
-
],
|
|
27
|
-
[
|
|
28
|
-
"image",
|
|
29
|
-
"https://media.tenor.com/wpvrkjn192gAAAAe/daenerys-targaryen.png",
|
|
30
|
-
"5d92423664fc15874b1d26c70a05a541ec09b5c438bf157977a87c8e64b31463",
|
|
31
|
-
],
|
|
32
|
-
];
|
|
33
|
-
expect(parseFileMetadataTags(tags)).toEqual({
|
|
34
|
-
url: "https://image.nostr.build/30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae.gif",
|
|
35
|
-
type: "image/gif",
|
|
36
|
-
dimensions: "360x306",
|
|
37
|
-
blurhash: "L38zleNL00~W^kRj0L-p0KM_^kx]",
|
|
38
|
-
sha256: "77fcf42b2b720babcdbe686eff67273d8a68862d74a2672db672bc48439a3ea5",
|
|
39
|
-
originalSha256: "30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae",
|
|
40
|
-
thumbnail: "https://media.tenor.com/wpvrkjn192gAAAAx/daenerys-targaryen.webp",
|
|
41
|
-
image: "https://media.tenor.com/wpvrkjn192gAAAAe/daenerys-targaryen.png",
|
|
42
|
-
summary: "Khaleesi call dragons Daenerys Targaryen",
|
|
43
|
-
fallback: ["https://media.tenor.com/wpvrkjn192gAAAAC/daenerys-targaryen.gif"],
|
|
44
|
-
alt: "a woman with blonde hair and a brooch on her shoulder",
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
describe("getFileMetadataFromImetaTag", () => {
|
|
49
|
-
it("should parse simple imeta tag", () => {
|
|
50
|
-
expect(getFileMetadataFromImetaTag([
|
|
51
|
-
"imeta",
|
|
52
|
-
"url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
|
|
53
|
-
"x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
|
|
54
|
-
"dim 1024x1024",
|
|
55
|
-
"m image/jpeg",
|
|
56
|
-
"blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
|
|
57
|
-
])).toEqual({
|
|
58
|
-
url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
|
|
59
|
-
sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
|
|
60
|
-
dimensions: "1024x1024",
|
|
61
|
-
type: "image/jpeg",
|
|
62
|
-
blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
it("should parse thumbnail url", () => {
|
|
66
|
-
expect(getFileMetadataFromImetaTag([
|
|
67
|
-
"imeta",
|
|
68
|
-
"url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
|
|
69
|
-
"x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
|
|
70
|
-
"dim 1024x1024",
|
|
71
|
-
"m image/jpeg",
|
|
72
|
-
"blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
|
|
73
|
-
"thumb https://exmaple.com/thumb.jpg",
|
|
74
|
-
])).toEqual({
|
|
75
|
-
url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
|
|
76
|
-
sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
|
|
77
|
-
dimensions: "1024x1024",
|
|
78
|
-
type: "image/jpeg",
|
|
79
|
-
blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
|
|
80
|
-
thumbnail: "https://exmaple.com/thumb.jpg",
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
it("should parse multiple fallback urls", () => {
|
|
84
|
-
expect(getFileMetadataFromImetaTag([
|
|
85
|
-
"imeta",
|
|
86
|
-
"url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
|
|
87
|
-
"x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
|
|
88
|
-
"dim 1024x1024",
|
|
89
|
-
"m image/jpeg",
|
|
90
|
-
"blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
|
|
91
|
-
"fallback https://exmaple.com/image2.jpg",
|
|
92
|
-
"fallback https://exmaple.com/image3.jpg",
|
|
93
|
-
])).toEqual({
|
|
94
|
-
url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
|
|
95
|
-
sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
|
|
96
|
-
dimensions: "1024x1024",
|
|
97
|
-
type: "image/jpeg",
|
|
98
|
-
blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
|
|
99
|
-
fallback: ["https://exmaple.com/image2.jpg", "https://exmaple.com/image3.jpg"],
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|