applesauce-core 0.2.0 → 0.4.0
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 -0
- package/dist/event-store/event-store.d.ts +3 -1
- package/dist/event-store/event-store.js +6 -2
- package/dist/helpers/channel.d.ts +15 -0
- package/dist/helpers/channel.js +27 -0
- package/dist/helpers/event.d.ts +17 -1
- package/dist/helpers/event.js +22 -9
- package/dist/helpers/index.d.ts +2 -1
- package/dist/helpers/index.js +2 -1
- package/dist/helpers/json.d.ts +1 -0
- package/dist/helpers/json.js +8 -0
- package/dist/helpers/mailboxes.d.ts +10 -2
- package/dist/helpers/mailboxes.js +10 -9
- package/dist/helpers/mailboxes.test.d.ts +1 -0
- package/dist/helpers/mailboxes.test.js +80 -0
- package/dist/helpers/mute.d.ts +21 -0
- package/dist/helpers/mute.js +52 -0
- package/dist/helpers/pointers.d.ts +22 -0
- package/dist/helpers/pointers.js +127 -0
- package/dist/helpers/profile.d.ts +7 -0
- package/dist/helpers/profile.js +4 -4
- package/dist/helpers/relays.d.ts +6 -0
- package/dist/helpers/relays.js +7 -6
- package/dist/helpers/threading.d.ts +55 -0
- package/dist/helpers/threading.js +61 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/observable/stateful.d.ts +1 -1
- package/dist/observable/stateful.js +1 -1
- package/dist/observable/throttle.d.ts +1 -0
- package/dist/observable/throttle.js +1 -0
- package/dist/promise/index.d.ts +1 -1
- package/dist/promise/index.js +1 -1
- package/dist/queries/channel.d.ts +11 -0
- package/dist/queries/channel.js +72 -0
- package/dist/queries/index.d.ts +7 -0
- package/dist/queries/index.js +7 -0
- package/dist/queries/mailboxes.d.ts +5 -0
- package/dist/queries/mailboxes.js +11 -0
- package/dist/queries/mute.d.ts +7 -0
- package/dist/queries/mute.js +16 -0
- package/dist/queries/profile.d.ts +3 -0
- package/dist/queries/profile.js +10 -0
- package/dist/queries/reactions.d.ts +4 -0
- package/dist/queries/reactions.js +19 -0
- package/dist/queries/simple.d.ts +5 -0
- package/dist/queries/simple.js +20 -0
- package/dist/queries/thread.d.ts +23 -0
- package/dist/queries/thread.js +65 -0
- package/dist/query-store/index.d.ts +35 -15
- package/dist/query-store/index.js +42 -57
- package/package.json +21 -2
- package/dist/helpers/symbols.d.ts +0 -17
- package/dist/helpers/symbols.js +0 -10
package/dist/helpers/relays.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
export declare const SeenRelaysSymbol: unique symbol;
|
|
3
|
+
declare module "nostr-tools" {
|
|
4
|
+
interface Event {
|
|
5
|
+
[SeenRelaysSymbol]?: Set<string>;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
2
8
|
export declare function addSeenRelay(event: NostrEvent, relay: string): Set<string>;
|
|
3
9
|
export declare function getSeenRelays(event: NostrEvent): Set<string> | undefined;
|
|
4
10
|
export declare function validateRelayURL(relay: string | URL): URL;
|
package/dist/helpers/relays.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
export const SeenRelaysSymbol = Symbol.for("seen-relays");
|
|
2
|
+
// Seen relays
|
|
2
3
|
export function addSeenRelay(event, relay) {
|
|
3
|
-
if (!event[
|
|
4
|
-
event[
|
|
5
|
-
event[
|
|
6
|
-
return event[
|
|
4
|
+
if (!event[SeenRelaysSymbol])
|
|
5
|
+
event[SeenRelaysSymbol] = new Set();
|
|
6
|
+
event[SeenRelaysSymbol].add(relay);
|
|
7
|
+
return event[SeenRelaysSymbol];
|
|
7
8
|
}
|
|
8
9
|
export function getSeenRelays(event) {
|
|
9
|
-
return event[
|
|
10
|
+
return event[SeenRelaysSymbol];
|
|
10
11
|
}
|
|
11
12
|
// Relay URLs
|
|
12
13
|
export function validateRelayURL(relay) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
3
|
+
export type ThreadReferences = {
|
|
4
|
+
root?: {
|
|
5
|
+
e: EventPointer;
|
|
6
|
+
a: undefined;
|
|
7
|
+
} | {
|
|
8
|
+
e: undefined;
|
|
9
|
+
a: AddressPointer;
|
|
10
|
+
} | {
|
|
11
|
+
e: EventPointer;
|
|
12
|
+
a: AddressPointer;
|
|
13
|
+
};
|
|
14
|
+
reply?: {
|
|
15
|
+
e: EventPointer;
|
|
16
|
+
a: undefined;
|
|
17
|
+
} | {
|
|
18
|
+
e: undefined;
|
|
19
|
+
a: AddressPointer;
|
|
20
|
+
} | {
|
|
21
|
+
e: EventPointer;
|
|
22
|
+
a: AddressPointer;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export declare const Nip10ThreadRefsSymbol: unique symbol;
|
|
26
|
+
declare module "nostr-tools" {
|
|
27
|
+
interface Event {
|
|
28
|
+
[Nip10ThreadRefsSymbol]?: ThreadReferences;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Parses NIP-10 tags and handles legacy behavior */
|
|
32
|
+
export declare function interpretThreadTags(event: NostrEvent): {
|
|
33
|
+
root?: {
|
|
34
|
+
e: string[];
|
|
35
|
+
a: undefined;
|
|
36
|
+
} | {
|
|
37
|
+
e: undefined;
|
|
38
|
+
a: string[];
|
|
39
|
+
} | {
|
|
40
|
+
e: string[];
|
|
41
|
+
a: string[];
|
|
42
|
+
} | undefined;
|
|
43
|
+
reply?: {
|
|
44
|
+
e: string[];
|
|
45
|
+
a: undefined;
|
|
46
|
+
} | {
|
|
47
|
+
e: undefined;
|
|
48
|
+
a: string[];
|
|
49
|
+
} | {
|
|
50
|
+
e: string[];
|
|
51
|
+
a: string[];
|
|
52
|
+
} | undefined;
|
|
53
|
+
};
|
|
54
|
+
/** Returns the parsed NIP-10 tags for an event */
|
|
55
|
+
export declare function getNip10References(event: NostrEvent): ThreadReferences;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getAddressPointerFromTag, getEventPointerFromTag } from "./pointers.js";
|
|
2
|
+
export const Nip10ThreadRefsSymbol = Symbol.for("nip10-thread-refs");
|
|
3
|
+
/** Parses NIP-10 tags and handles legacy behavior */
|
|
4
|
+
export function interpretThreadTags(event) {
|
|
5
|
+
const eTags = event.tags.filter((t) => t[0] === "e" && t[1]);
|
|
6
|
+
const aTags = event.tags.filter((t) => t[0] === "a" && t[1]);
|
|
7
|
+
// find the root and reply tags.
|
|
8
|
+
let rootETag = eTags.find((t) => t[3] === "root");
|
|
9
|
+
let replyETag = eTags.find((t) => t[3] === "reply");
|
|
10
|
+
let rootATag = aTags.find((t) => t[3] === "root");
|
|
11
|
+
let replyATag = aTags.find((t) => t[3] === "reply");
|
|
12
|
+
if (!rootETag || !replyETag) {
|
|
13
|
+
// a direct reply does not need a "reply" reference
|
|
14
|
+
// https://github.com/nostr-protocol/nips/blob/master/10.md
|
|
15
|
+
// this is not necessarily to spec. but if there is only one id (root or reply) then assign it to both
|
|
16
|
+
// this handles the cases where a client only set a "reply" tag and no root
|
|
17
|
+
rootETag = replyETag = rootETag || replyETag;
|
|
18
|
+
}
|
|
19
|
+
if (!rootATag || !replyATag) {
|
|
20
|
+
rootATag = replyATag = rootATag || replyATag;
|
|
21
|
+
}
|
|
22
|
+
if (!rootETag && !replyETag) {
|
|
23
|
+
// legacy behavior
|
|
24
|
+
// https://github.com/nostr-protocol/nips/blob/master/10.md#positional-e-tags-deprecated
|
|
25
|
+
const legacyETags = eTags.filter((t) => {
|
|
26
|
+
// ignore it if there is a marker
|
|
27
|
+
if (t[3])
|
|
28
|
+
return false;
|
|
29
|
+
return true;
|
|
30
|
+
});
|
|
31
|
+
if (legacyETags.length >= 1) {
|
|
32
|
+
// first tag is the root
|
|
33
|
+
rootETag = legacyETags[0];
|
|
34
|
+
// last tag is reply
|
|
35
|
+
replyETag = legacyETags[legacyETags.length - 1] ?? rootETag;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
root: rootETag || rootATag ? { e: rootETag, a: rootATag } : undefined,
|
|
40
|
+
reply: replyETag || replyATag ? { e: replyETag, a: replyATag } : undefined,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Returns the parsed NIP-10 tags for an event */
|
|
44
|
+
export function getNip10References(event) {
|
|
45
|
+
let refs = event[Nip10ThreadRefsSymbol];
|
|
46
|
+
if (!refs) {
|
|
47
|
+
const e = event;
|
|
48
|
+
const tags = interpretThreadTags(e);
|
|
49
|
+
refs = event[Nip10ThreadRefsSymbol] = {
|
|
50
|
+
root: tags.root && {
|
|
51
|
+
e: tags.root.e && getEventPointerFromTag(tags.root.e),
|
|
52
|
+
a: tags.root.a && getAddressPointerFromTag(tags.root.a),
|
|
53
|
+
},
|
|
54
|
+
reply: tags.reply && {
|
|
55
|
+
e: tags.reply.e && getEventPointerFromTag(tags.reply.e),
|
|
56
|
+
a: tags.reply.a && getAddressPointerFromTag(tags.reply.a),
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return refs;
|
|
61
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -5,6 +5,6 @@ export type StatefulObservable<T> = Observable<T> & {
|
|
|
5
5
|
error?: Error;
|
|
6
6
|
complete?: boolean;
|
|
7
7
|
};
|
|
8
|
-
/** Wraps an
|
|
8
|
+
/** Wraps an {@link Observable} and makes it stateful */
|
|
9
9
|
export declare function stateful<T extends unknown>(observable: Observable<T>, cleanup?: boolean): StatefulObservable<T>;
|
|
10
10
|
export declare function isStateful<T extends unknown>(observable: Observable<T> | StatefulObservable<T>): observable is StatefulObservable<T>;
|
package/dist/promise/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from "./deferred.js";
|
package/dist/promise/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from
|
|
1
|
+
export * from "./deferred.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { Query } from "../query-store/index.js";
|
|
3
|
+
import { ChannelMetadataContent } from "../helpers/channel.js";
|
|
4
|
+
/** Creates a query that returns the latest parsed metadata */
|
|
5
|
+
export declare function ChannelMetadataQuery(channel: NostrEvent): Query<ChannelMetadataContent | undefined>;
|
|
6
|
+
/** Creates a query that returns a map of hidden messages Map<id, reason> */
|
|
7
|
+
export declare function ChannelHiddenQuery(channel: NostrEvent, authors?: string[]): Query<Map<string, string>>;
|
|
8
|
+
/** Creates a query that returns a map of muted users Map<pubkey, reason> */
|
|
9
|
+
export declare function ChannelMutedQuery(channel: NostrEvent, authors?: string[]): Query<Map<string, string>>;
|
|
10
|
+
/** Creates a query that returns all messages in a channel */
|
|
11
|
+
export declare function ChannelMessagesQuery(channel: NostrEvent): Query<NostrEvent[]>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getChannelMetadataContent } from "../helpers/channel.js";
|
|
3
|
+
import { safeParse } from "../helpers/json.js";
|
|
4
|
+
/** Creates a query that returns the latest parsed metadata */
|
|
5
|
+
export function ChannelMetadataQuery(channel) {
|
|
6
|
+
return {
|
|
7
|
+
key: channel.id,
|
|
8
|
+
run: (events) => {
|
|
9
|
+
const filters = [
|
|
10
|
+
{ ids: [channel.id] },
|
|
11
|
+
{ kinds: [kinds.ChannelMetadata], "#e": [channel.id], authors: [channel.pubkey] },
|
|
12
|
+
];
|
|
13
|
+
let latest = channel;
|
|
14
|
+
return events.stream(filters).map((event) => {
|
|
15
|
+
try {
|
|
16
|
+
if (event.pubkey === latest.pubkey && event.created_at > latest.created_at) {
|
|
17
|
+
latest = event;
|
|
18
|
+
}
|
|
19
|
+
return getChannelMetadataContent(latest);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Creates a query that returns a map of hidden messages Map<id, reason> */
|
|
29
|
+
export function ChannelHiddenQuery(channel, authors = []) {
|
|
30
|
+
return {
|
|
31
|
+
key: channel.id,
|
|
32
|
+
run: (events) => {
|
|
33
|
+
const hidden = new Map();
|
|
34
|
+
return events
|
|
35
|
+
.stream([{ kinds: [kinds.ChannelHideMessage], "#e": [channel.id], authors: [channel.pubkey, ...authors] }])
|
|
36
|
+
.map((event) => {
|
|
37
|
+
const reason = safeParse(event.content)?.reason;
|
|
38
|
+
for (const tag of event.tags) {
|
|
39
|
+
if (tag[0] === "e" && tag[1])
|
|
40
|
+
hidden.set(tag[1], reason ?? "");
|
|
41
|
+
}
|
|
42
|
+
return hidden;
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Creates a query that returns a map of muted users Map<pubkey, reason> */
|
|
48
|
+
export function ChannelMutedQuery(channel, authors = []) {
|
|
49
|
+
return {
|
|
50
|
+
key: channel.id + authors.join(","),
|
|
51
|
+
run: (events) => {
|
|
52
|
+
const muted = new Map();
|
|
53
|
+
return events
|
|
54
|
+
.stream([{ kinds: [kinds.ChannelMuteUser], "#e": [channel.id], authors: [channel.pubkey, ...authors] }])
|
|
55
|
+
.map((event) => {
|
|
56
|
+
const reason = safeParse(event.content)?.reason;
|
|
57
|
+
for (const tag of event.tags) {
|
|
58
|
+
if (tag[0] === "p" && tag[1])
|
|
59
|
+
muted.set(tag[1], reason ?? "");
|
|
60
|
+
}
|
|
61
|
+
return muted;
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Creates a query that returns all messages in a channel */
|
|
67
|
+
export function ChannelMessagesQuery(channel) {
|
|
68
|
+
return {
|
|
69
|
+
key: channel.id,
|
|
70
|
+
run: (events) => events.timeline([{ kinds: [kinds.ChannelMessage], "#e": [channel.id] }]),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getInboxes, getOutboxes } from "../helpers/mailboxes.js";
|
|
3
|
+
export function MailboxesQuery(pubkey) {
|
|
4
|
+
return {
|
|
5
|
+
key: pubkey,
|
|
6
|
+
run: (events) => events.replaceable(kinds.RelayList, pubkey).map((event) => event && {
|
|
7
|
+
inboxes: getInboxes(event),
|
|
8
|
+
outboxes: getOutboxes(event),
|
|
9
|
+
}),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getMutedHashtags, getMutedPubkeys, getMutedThreads, getMutedWords } from "../helpers/mute.js";
|
|
3
|
+
export function UserMuteQuery(pubkey) {
|
|
4
|
+
return {
|
|
5
|
+
key: pubkey,
|
|
6
|
+
run: (store) => store.replaceable(kinds.Mutelist, pubkey).map((event) => {
|
|
7
|
+
if (!event)
|
|
8
|
+
return;
|
|
9
|
+
const pubkeys = getMutedPubkeys(event);
|
|
10
|
+
const threads = getMutedThreads(event);
|
|
11
|
+
const hashtags = getMutedHashtags(event);
|
|
12
|
+
const words = getMutedWords(event);
|
|
13
|
+
return { pubkeys, threads, hashtags, words };
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getProfileContent } from "../helpers/profile.js";
|
|
3
|
+
export function ProfileQuery(pubkey) {
|
|
4
|
+
return {
|
|
5
|
+
key: pubkey,
|
|
6
|
+
run: (events) => {
|
|
7
|
+
return events.replaceable(kinds.Metadata, pubkey).map((event) => event && getProfileContent(event));
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getEventUID, isReplaceable } from "../helpers/event.js";
|
|
3
|
+
/** Creates a query that returns all reactions to an event (supports replaceable events) */
|
|
4
|
+
export function ReactionsQuery(event) {
|
|
5
|
+
return {
|
|
6
|
+
key: getEventUID(event),
|
|
7
|
+
run: (events) => events.timeline(isReplaceable(event.kind)
|
|
8
|
+
? [
|
|
9
|
+
{ kinds: [kinds.Reaction], "#e": [event.id] },
|
|
10
|
+
{ kinds: [kinds.Reaction], "#a": [getEventUID(event)] },
|
|
11
|
+
]
|
|
12
|
+
: [
|
|
13
|
+
{
|
|
14
|
+
kinds: [kinds.Reaction],
|
|
15
|
+
"#e": [event.id],
|
|
16
|
+
},
|
|
17
|
+
]),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
+
import { Query } from "../query-store/index.js";
|
|
3
|
+
export declare function SingleEventQuery(uid: string): Query<NostrEvent | undefined>;
|
|
4
|
+
export declare function ReplaceableQuery(kind: number, pubkey: string, d?: string): Query<NostrEvent | undefined>;
|
|
5
|
+
export declare function TimelineQuery(filters: Filter | Filter[]): Query<NostrEvent[]>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import stringify from "json-stringify-deterministic";
|
|
2
|
+
import { getReplaceableUID } from "../helpers/event.js";
|
|
3
|
+
export function SingleEventQuery(uid) {
|
|
4
|
+
return {
|
|
5
|
+
key: uid,
|
|
6
|
+
run: (events) => events.event(uid),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function ReplaceableQuery(kind, pubkey, d) {
|
|
10
|
+
return {
|
|
11
|
+
key: getReplaceableUID(kind, pubkey, d),
|
|
12
|
+
run: (events) => events.replaceable(kind, pubkey, d),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function TimelineQuery(filters) {
|
|
16
|
+
return {
|
|
17
|
+
key: stringify(filters),
|
|
18
|
+
run: (events) => events.timeline(Array.isArray(filters) ? filters : [filters]),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { NostrEvent } from "nostr-tools";
|
|
2
|
+
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
3
|
+
import { Query } from "../query-store/index.js";
|
|
4
|
+
import { ThreadReferences } from "../helpers/threading.js";
|
|
5
|
+
export type Thread = {
|
|
6
|
+
root?: ThreadItem;
|
|
7
|
+
all: Map<string, ThreadItem>;
|
|
8
|
+
};
|
|
9
|
+
export type ThreadItem = {
|
|
10
|
+
/** underlying nostr event */
|
|
11
|
+
event: NostrEvent;
|
|
12
|
+
refs: ThreadReferences;
|
|
13
|
+
/** the thread root, according to this event */
|
|
14
|
+
root?: ThreadItem;
|
|
15
|
+
/** the parent event this is replying to */
|
|
16
|
+
parent?: ThreadItem;
|
|
17
|
+
/** direct child replies */
|
|
18
|
+
replies: Set<ThreadItem>;
|
|
19
|
+
};
|
|
20
|
+
export type ThreadQueryOptions = {
|
|
21
|
+
kinds?: number[];
|
|
22
|
+
};
|
|
23
|
+
export declare function ThreadQuery(root: string | AddressPointer | EventPointer, opts?: ThreadQueryOptions): Query<Thread>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { kinds } from "nostr-tools";
|
|
2
|
+
import { getNip10References } from "../helpers/threading.js";
|
|
3
|
+
import { getCoordinateFromAddressPointer, isAddressPointer } from "../helpers/pointers.js";
|
|
4
|
+
import { getEventUID } from "../helpers/event.js";
|
|
5
|
+
const defaultOptions = {
|
|
6
|
+
kinds: [kinds.ShortTextNote],
|
|
7
|
+
};
|
|
8
|
+
export function ThreadQuery(root, opts) {
|
|
9
|
+
const parentReferences = new Map();
|
|
10
|
+
const items = new Map();
|
|
11
|
+
const { kinds } = { ...defaultOptions, ...opts };
|
|
12
|
+
let rootUID = "";
|
|
13
|
+
const rootFilter = {};
|
|
14
|
+
const replyFilter = { kinds };
|
|
15
|
+
if (isAddressPointer(root)) {
|
|
16
|
+
rootUID = getCoordinateFromAddressPointer(root);
|
|
17
|
+
rootFilter.kinds = [root.kind];
|
|
18
|
+
rootFilter.authors = [root.pubkey];
|
|
19
|
+
rootFilter["#d"] = [root.identifier];
|
|
20
|
+
replyFilter["#a"] = [rootUID];
|
|
21
|
+
}
|
|
22
|
+
else if (typeof root === "string") {
|
|
23
|
+
rootUID = root;
|
|
24
|
+
rootFilter.ids = [root];
|
|
25
|
+
replyFilter["#e"] = [root];
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
rootUID = root.id;
|
|
29
|
+
rootFilter.ids = [root.id];
|
|
30
|
+
replyFilter["#e"] = [root.id];
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
key: `${rootUID}-${kinds.join(",")}`,
|
|
34
|
+
run: (events) => events.stream([rootFilter, replyFilter]).map((event) => {
|
|
35
|
+
if (!items.has(getEventUID(event))) {
|
|
36
|
+
const refs = getNip10References(event);
|
|
37
|
+
const replies = parentReferences.get(getEventUID(event)) || new Set();
|
|
38
|
+
const item = { event, refs, replies };
|
|
39
|
+
for (const child of replies) {
|
|
40
|
+
child.parent = item;
|
|
41
|
+
}
|
|
42
|
+
// add item to parent
|
|
43
|
+
if (refs.reply?.e || refs.reply?.a) {
|
|
44
|
+
let uid = refs.reply.e ? refs.reply.e.id : getCoordinateFromAddressPointer(refs.reply.a);
|
|
45
|
+
item.parent = items.get(uid);
|
|
46
|
+
if (item.parent) {
|
|
47
|
+
item.parent.replies.add(item);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// parent isn't created yet, store ref for later
|
|
51
|
+
let set = parentReferences.get(uid);
|
|
52
|
+
if (!set) {
|
|
53
|
+
set = new Set();
|
|
54
|
+
parentReferences.set(uid, set);
|
|
55
|
+
}
|
|
56
|
+
set.add(item);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// add item to map
|
|
60
|
+
items.set(getEventUID(event), item);
|
|
61
|
+
}
|
|
62
|
+
return { root: items.get(rootUID), all: items };
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -1,26 +1,46 @@
|
|
|
1
1
|
import Observable from "zen-observable";
|
|
2
2
|
import { Filter, NostrEvent } from "nostr-tools";
|
|
3
3
|
import { EventStore } from "../event-store/event-store.js";
|
|
4
|
-
import { ProfileContent } from "../helpers/profile.js";
|
|
5
4
|
import { LRU } from "../utils/lru.js";
|
|
5
|
+
import * as Queries from "../queries/index.js";
|
|
6
|
+
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
7
|
+
export type Query<T extends unknown> = {
|
|
8
|
+
key: string;
|
|
9
|
+
run: (events: EventStore, store: QueryStore) => Observable<T>;
|
|
10
|
+
};
|
|
11
|
+
export type QueryConstructor<T extends unknown, Args extends Array<any>> = (...args: Args) => Query<T>;
|
|
6
12
|
export declare class QueryStore {
|
|
13
|
+
static Queries: typeof Queries;
|
|
7
14
|
store: EventStore;
|
|
8
15
|
constructor(store: EventStore);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
queries: LRU<Observable<any>>;
|
|
17
|
+
/** Creates a cached query */
|
|
18
|
+
runQuery<T extends unknown, Args extends Array<any>>(queryConstructor: (...args: Args) => {
|
|
19
|
+
key: string;
|
|
20
|
+
run: (events: EventStore, store: QueryStore) => Observable<T>;
|
|
21
|
+
}): (...args: Args) => Observable<T>;
|
|
22
|
+
/** Returns a single event */
|
|
23
|
+
event(id: string): Observable<import("nostr-tools").Event | undefined>;
|
|
24
|
+
/** Returns the latest version of a replaceable event */
|
|
25
|
+
replaceable(kind: number, pubkey: string, d?: string): Observable<import("nostr-tools").Event | undefined>;
|
|
26
|
+
/** Returns an array of events that match the filter */
|
|
27
|
+
timeline(filters: Filter | Filter[]): Observable<import("nostr-tools").Event[]>;
|
|
28
|
+
/** Returns the parsed profile (0) for a pubkey */
|
|
29
|
+
profile(pubkey: string): Observable<import("../helpers/profile.js").ProfileContent | undefined>;
|
|
30
|
+
/** Returns all reactions for an event (supports replaceable events) */
|
|
31
|
+
reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
|
|
32
|
+
/** Returns the parsed relay list (10002) for the pubkey */
|
|
33
|
+
mailboxes(pubkey: string): Observable<{
|
|
23
34
|
inboxes: Set<string>;
|
|
24
35
|
outboxes: Set<string>;
|
|
25
36
|
} | undefined>;
|
|
37
|
+
/** Returns the parsed mute list for the pubkey */
|
|
38
|
+
mute(pubkey: string): Observable<{
|
|
39
|
+
words: Set<string>;
|
|
40
|
+
pubkeys: Set<string>;
|
|
41
|
+
threads: Set<string>;
|
|
42
|
+
hashtags: Set<string>;
|
|
43
|
+
} | undefined>;
|
|
44
|
+
thread(root: string | EventPointer | AddressPointer): Observable<Queries.Thread>;
|
|
26
45
|
}
|
|
46
|
+
export { Queries };
|