applesauce-core 0.3.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/dist/helpers/event.d.ts +9 -1
- package/dist/helpers/event.js +20 -8
- package/dist/helpers/index.d.ts +2 -0
- package/dist/helpers/index.js +2 -0
- package/dist/helpers/pointers.d.ts +22 -0
- package/dist/helpers/pointers.js +127 -0
- package/dist/helpers/profile.d.ts +1 -0
- 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/{query-store/queries → queries}/channel.d.ts +2 -2
- package/dist/{query-store/queries → queries}/channel.js +2 -2
- package/dist/{query-store/queries → queries}/index.d.ts +1 -0
- package/dist/{query-store/queries → queries}/index.js +1 -0
- package/dist/{query-store/queries → queries}/mailboxes.d.ts +1 -1
- package/dist/{query-store/queries → queries}/mailboxes.js +1 -1
- package/dist/{query-store/queries → queries}/mute.d.ts +1 -1
- package/dist/{query-store/queries → queries}/mute.js +1 -1
- package/dist/queries/profile.d.ts +3 -0
- package/dist/{query-store/queries → queries}/profile.js +1 -1
- package/dist/{query-store/queries → queries}/reactions.d.ts +1 -1
- package/dist/{query-store/queries → queries}/reactions.js +1 -1
- package/dist/{query-store/queries → queries}/simple.d.ts +1 -1
- package/dist/{query-store/queries → queries}/simple.js +1 -1
- package/dist/queries/thread.d.ts +23 -0
- package/dist/queries/thread.js +65 -0
- package/dist/query-store/index.d.ts +3 -1
- package/dist/query-store/index.js +4 -1
- package/package.json +5 -1
- package/dist/query-store/queries/profile.d.ts +0 -3
package/dist/helpers/event.d.ts
CHANGED
|
@@ -12,7 +12,15 @@ declare module "nostr-tools" {
|
|
|
12
12
|
* or parameterized replaceable ( 30000 <= n < 40000 )
|
|
13
13
|
*/
|
|
14
14
|
export declare function isReplaceable(kind: number): boolean;
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Returns the events Unique ID
|
|
17
|
+
* For normal or ephemeral events this is ( event.id )
|
|
18
|
+
* For replaceable events this is ( event.kind + ":" + event.pubkey )
|
|
19
|
+
* For parametrized replaceable events this is ( event.kind + ":" + event.pubkey + ":" + event.tags.d.1 )
|
|
20
|
+
*/
|
|
16
21
|
export declare function getEventUID(event: NostrEvent): string;
|
|
17
22
|
export declare function getReplaceableUID(kind: number, pubkey: string, d?: string): string;
|
|
23
|
+
/** Returns a Set of tag names and values that are indexable */
|
|
18
24
|
export declare function getIndexableTags(event: NostrEvent): Set<string>;
|
|
25
|
+
/** Returns the second index ( tag[1] ) of the first tag that matches the name */
|
|
26
|
+
export declare function getTagValue(event: NostrEvent, name: string): string | undefined;
|
package/dist/helpers/event.js
CHANGED
|
@@ -9,31 +9,43 @@ export const EventIndexableTagsSymbol = Symbol.for("indexable-tags");
|
|
|
9
9
|
export function isReplaceable(kind) {
|
|
10
10
|
return kinds.isReplaceableKind(kind) || kinds.isParameterizedReplaceableKind(kind);
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Returns the events Unique ID
|
|
14
|
+
* For normal or ephemeral events this is ( event.id )
|
|
15
|
+
* For replaceable events this is ( event.kind + ":" + event.pubkey )
|
|
16
|
+
* For parametrized replaceable events this is ( event.kind + ":" + event.pubkey + ":" + event.tags.d.1 )
|
|
17
|
+
*/
|
|
13
18
|
export function getEventUID(event) {
|
|
14
|
-
|
|
19
|
+
let id = event[EventUIDSymbol];
|
|
20
|
+
if (!id) {
|
|
15
21
|
if (isReplaceable(event.kind)) {
|
|
16
22
|
const d = event.tags.find((t) => t[0] === "d")?.[1];
|
|
17
|
-
|
|
23
|
+
id = getReplaceableUID(event.kind, event.pubkey, d);
|
|
18
24
|
}
|
|
19
25
|
else {
|
|
20
|
-
|
|
26
|
+
id = event.id;
|
|
21
27
|
}
|
|
22
28
|
}
|
|
23
|
-
return
|
|
29
|
+
return id;
|
|
24
30
|
}
|
|
25
31
|
export function getReplaceableUID(kind, pubkey, d) {
|
|
26
32
|
return d ? `${kind}:${pubkey}:${d}` : `${kind}:${pubkey}`;
|
|
27
33
|
}
|
|
34
|
+
/** Returns a Set of tag names and values that are indexable */
|
|
28
35
|
export function getIndexableTags(event) {
|
|
29
|
-
|
|
36
|
+
let indexable = event[EventIndexableTagsSymbol];
|
|
37
|
+
if (!indexable) {
|
|
30
38
|
const tags = new Set();
|
|
31
39
|
for (const tag of event.tags) {
|
|
32
40
|
if (tag[0] && INDEXABLE_TAGS.has(tag[0]) && tag[1]) {
|
|
33
41
|
tags.add(tag[0] + ":" + tag[1]);
|
|
34
42
|
}
|
|
35
43
|
}
|
|
36
|
-
event[EventIndexableTagsSymbol] = tags;
|
|
44
|
+
indexable = event[EventIndexableTagsSymbol] = tags;
|
|
37
45
|
}
|
|
38
|
-
return
|
|
46
|
+
return indexable;
|
|
47
|
+
}
|
|
48
|
+
/** Returns the second index ( tag[1] ) of the first tag that matches the name */
|
|
49
|
+
export function getTagValue(event, name) {
|
|
50
|
+
return event.tags.find((t) => t[0] === name)?.[1];
|
|
39
51
|
}
|
package/dist/helpers/index.d.ts
CHANGED
package/dist/helpers/index.js
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AddressPointer, DecodeResult, EventPointer, ProfilePointer } from "nostr-tools/nip19";
|
|
2
|
+
export type AddressPointerWithoutD = Omit<AddressPointer, "identifier"> & {
|
|
3
|
+
identifier?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function parseCoordinate(a: string): AddressPointerWithoutD | null;
|
|
6
|
+
export declare function parseCoordinate(a: string, requireD: false): AddressPointerWithoutD | null;
|
|
7
|
+
export declare function parseCoordinate(a: string, requireD: true): AddressPointer | null;
|
|
8
|
+
export declare function parseCoordinate(a: string, requireD: false, silent: false): AddressPointerWithoutD;
|
|
9
|
+
export declare function parseCoordinate(a: string, requireD: true, silent: false): AddressPointer;
|
|
10
|
+
export declare function parseCoordinate(a: string, requireD: true, silent: true): AddressPointer | null;
|
|
11
|
+
export declare function parseCoordinate(a: string, requireD: false, silent: true): AddressPointerWithoutD | null;
|
|
12
|
+
export declare function getPubkeyFromDecodeResult(result?: DecodeResult): string | undefined;
|
|
13
|
+
export declare function encodeDecodeResult(result: DecodeResult): "" | `nsec1${string}` | `npub1${string}` | `note1${string}` | `nprofile1${string}` | `nevent1${string}` | `naddr1${string}` | `nrelay1${string}`;
|
|
14
|
+
export declare function getEventPointerFromTag(tag: string[]): EventPointer;
|
|
15
|
+
export declare function getAddressPointerFromTag(tag: string[]): AddressPointer;
|
|
16
|
+
export declare function getProfilePointerFromTag(tag: string[]): ProfilePointer;
|
|
17
|
+
export declare function getPointerFromTag(tag: string[]): DecodeResult | null;
|
|
18
|
+
export declare function isAddressPointer(pointer: DecodeResult["data"]): pointer is AddressPointer;
|
|
19
|
+
export declare function isEventPointer(pointer: DecodeResult["data"]): pointer is EventPointer;
|
|
20
|
+
export declare function getCoordinateFromAddressPointer(pointer: AddressPointer): string;
|
|
21
|
+
export declare function getATagFromAddressPointer(pointer: AddressPointer): ["a", ...string[]];
|
|
22
|
+
export declare function getETagFromEventPointer(pointer: EventPointer): ["e", ...string[]];
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nrelayEncode, nsecEncode, } from "nostr-tools/nip19";
|
|
2
|
+
import { getPublicKey } from "nostr-tools";
|
|
3
|
+
import { safeRelayUrls } from "./relays.js";
|
|
4
|
+
export function parseCoordinate(a, requireD = false, silent = true) {
|
|
5
|
+
const parts = a.split(":");
|
|
6
|
+
const kind = parts[0] && parseInt(parts[0]);
|
|
7
|
+
const pubkey = parts[1];
|
|
8
|
+
const d = parts[2];
|
|
9
|
+
if (!kind) {
|
|
10
|
+
if (silent)
|
|
11
|
+
return null;
|
|
12
|
+
else
|
|
13
|
+
throw new Error("Missing kind");
|
|
14
|
+
}
|
|
15
|
+
if (!pubkey) {
|
|
16
|
+
if (silent)
|
|
17
|
+
return null;
|
|
18
|
+
else
|
|
19
|
+
throw new Error("Missing pubkey");
|
|
20
|
+
}
|
|
21
|
+
if (requireD && d === undefined) {
|
|
22
|
+
if (silent)
|
|
23
|
+
return null;
|
|
24
|
+
else
|
|
25
|
+
throw new Error("Missing identifier");
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
kind,
|
|
29
|
+
pubkey,
|
|
30
|
+
identifier: d,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function getPubkeyFromDecodeResult(result) {
|
|
34
|
+
if (!result)
|
|
35
|
+
return;
|
|
36
|
+
switch (result.type) {
|
|
37
|
+
case "naddr":
|
|
38
|
+
case "nprofile":
|
|
39
|
+
return result.data.pubkey;
|
|
40
|
+
case "npub":
|
|
41
|
+
return result.data;
|
|
42
|
+
case "nsec":
|
|
43
|
+
return getPublicKey(result.data);
|
|
44
|
+
default:
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function encodeDecodeResult(result) {
|
|
49
|
+
switch (result.type) {
|
|
50
|
+
case "naddr":
|
|
51
|
+
return naddrEncode(result.data);
|
|
52
|
+
case "nprofile":
|
|
53
|
+
return nprofileEncode(result.data);
|
|
54
|
+
case "nevent":
|
|
55
|
+
return neventEncode(result.data);
|
|
56
|
+
case "nrelay":
|
|
57
|
+
return nrelayEncode(result.data);
|
|
58
|
+
case "nsec":
|
|
59
|
+
return nsecEncode(result.data);
|
|
60
|
+
case "npub":
|
|
61
|
+
return npubEncode(result.data);
|
|
62
|
+
case "note":
|
|
63
|
+
return noteEncode(result.data);
|
|
64
|
+
}
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
export function getEventPointerFromTag(tag) {
|
|
68
|
+
if (!tag[1])
|
|
69
|
+
throw new Error("Missing event id in tag");
|
|
70
|
+
let pointer = { id: tag[1] };
|
|
71
|
+
if (tag[2])
|
|
72
|
+
pointer.relays = safeRelayUrls([tag[2]]);
|
|
73
|
+
return pointer;
|
|
74
|
+
}
|
|
75
|
+
export function getAddressPointerFromTag(tag) {
|
|
76
|
+
if (!tag[1])
|
|
77
|
+
throw new Error("Missing coordinate in tag");
|
|
78
|
+
const pointer = parseCoordinate(tag[1], true, false);
|
|
79
|
+
if (tag[2])
|
|
80
|
+
pointer.relays = safeRelayUrls([tag[2]]);
|
|
81
|
+
return pointer;
|
|
82
|
+
}
|
|
83
|
+
export function getProfilePointerFromTag(tag) {
|
|
84
|
+
if (!tag[1])
|
|
85
|
+
throw new Error("Missing pubkey in tag");
|
|
86
|
+
const pointer = { pubkey: tag[1] };
|
|
87
|
+
if (tag[2])
|
|
88
|
+
pointer.relays = safeRelayUrls([tag[2]]);
|
|
89
|
+
return pointer;
|
|
90
|
+
}
|
|
91
|
+
export function getPointerFromTag(tag) {
|
|
92
|
+
try {
|
|
93
|
+
switch (tag[0]) {
|
|
94
|
+
case "e":
|
|
95
|
+
return { type: "nevent", data: getEventPointerFromTag(tag) };
|
|
96
|
+
case "a":
|
|
97
|
+
return {
|
|
98
|
+
type: "naddr",
|
|
99
|
+
data: getAddressPointerFromTag(tag),
|
|
100
|
+
};
|
|
101
|
+
case "p":
|
|
102
|
+
return { type: "nprofile", data: getProfilePointerFromTag(tag) };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) { }
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
export function isAddressPointer(pointer) {
|
|
109
|
+
return (typeof pointer !== "string" &&
|
|
110
|
+
Object.hasOwn(pointer, "identifier") &&
|
|
111
|
+
Object.hasOwn(pointer, "pubkey") &&
|
|
112
|
+
Object.hasOwn(pointer, "kind"));
|
|
113
|
+
}
|
|
114
|
+
export function isEventPointer(pointer) {
|
|
115
|
+
return typeof pointer !== "string" && Object.hasOwn(pointer, "id");
|
|
116
|
+
}
|
|
117
|
+
export function getCoordinateFromAddressPointer(pointer) {
|
|
118
|
+
return `${pointer.kind}:${pointer.pubkey}:${pointer.identifier}`;
|
|
119
|
+
}
|
|
120
|
+
export function getATagFromAddressPointer(pointer) {
|
|
121
|
+
const relay = pointer.relays?.[0];
|
|
122
|
+
const coordinate = getCoordinateFromAddressPointer(pointer);
|
|
123
|
+
return relay ? ["a", coordinate, relay] : ["a", coordinate];
|
|
124
|
+
}
|
|
125
|
+
export function getETagFromEventPointer(pointer) {
|
|
126
|
+
return pointer.relays?.length ? ["e", pointer.id, pointer.relays[0]] : ["e", pointer.id];
|
|
127
|
+
}
|
|
@@ -19,6 +19,7 @@ export type ProfileContent = {
|
|
|
19
19
|
lud06?: string;
|
|
20
20
|
nip05?: string;
|
|
21
21
|
};
|
|
22
|
+
/** Returns the parsed profile content for a kind 0 event */
|
|
22
23
|
export declare function getProfileContent(event: NostrEvent): ProfileContent;
|
|
23
24
|
export declare function getProfileContent(event: NostrEvent, quite: false): ProfileContent;
|
|
24
25
|
export declare function getProfileContent(event: NostrEvent, quite: true): ProfileContent | Error;
|
|
@@ -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>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NostrEvent } from "nostr-tools";
|
|
2
|
-
import { Query } from "../index.js";
|
|
3
|
-
import { ChannelMetadataContent } from "
|
|
2
|
+
import { Query } from "../query-store/index.js";
|
|
3
|
+
import { ChannelMetadataContent } from "../helpers/channel.js";
|
|
4
4
|
/** Creates a query that returns the latest parsed metadata */
|
|
5
5
|
export declare function ChannelMetadataQuery(channel: NostrEvent): Query<ChannelMetadataContent | undefined>;
|
|
6
6
|
/** Creates a query that returns a map of hidden messages Map<id, reason> */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { kinds } from "nostr-tools";
|
|
2
|
-
import { getChannelMetadataContent } from "
|
|
3
|
-
import { safeParse } from "
|
|
2
|
+
import { getChannelMetadataContent } from "../helpers/channel.js";
|
|
3
|
+
import { safeParse } from "../helpers/json.js";
|
|
4
4
|
/** Creates a query that returns the latest parsed metadata */
|
|
5
5
|
export function ChannelMetadataQuery(channel) {
|
|
6
6
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { kinds } from "nostr-tools";
|
|
2
|
-
import { getMutedHashtags, getMutedPubkeys, getMutedThreads, getMutedWords } from "
|
|
2
|
+
import { getMutedHashtags, getMutedPubkeys, getMutedThreads, getMutedWords } from "../helpers/mute.js";
|
|
3
3
|
export function UserMuteQuery(pubkey) {
|
|
4
4
|
return {
|
|
5
5
|
key: pubkey,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { NostrEvent } from "nostr-tools";
|
|
2
|
-
import { Query } from "../index.js";
|
|
2
|
+
import { Query } from "../query-store/index.js";
|
|
3
3
|
/** Creates a query that returns all reactions to an event (supports replaceable events) */
|
|
4
4
|
export declare function ReactionsQuery(event: NostrEvent): Query<NostrEvent[]>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { kinds } from "nostr-tools";
|
|
2
|
-
import { getEventUID, isReplaceable } from "
|
|
2
|
+
import { getEventUID, isReplaceable } from "../helpers/event.js";
|
|
3
3
|
/** Creates a query that returns all reactions to an event (supports replaceable events) */
|
|
4
4
|
export function ReactionsQuery(event) {
|
|
5
5
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
-
import { Query } from "../index.js";
|
|
2
|
+
import { Query } from "../query-store/index.js";
|
|
3
3
|
export declare function SingleEventQuery(uid: string): Query<NostrEvent | undefined>;
|
|
4
4
|
export declare function ReplaceableQuery(kind: number, pubkey: string, d?: string): Query<NostrEvent | undefined>;
|
|
5
5
|
export declare function TimelineQuery(filters: Filter | Filter[]): Query<NostrEvent[]>;
|
|
@@ -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
|
+
}
|
|
@@ -2,7 +2,8 @@ import Observable from "zen-observable";
|
|
|
2
2
|
import { Filter, NostrEvent } from "nostr-tools";
|
|
3
3
|
import { EventStore } from "../event-store/event-store.js";
|
|
4
4
|
import { LRU } from "../utils/lru.js";
|
|
5
|
-
import * as Queries from "
|
|
5
|
+
import * as Queries from "../queries/index.js";
|
|
6
|
+
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
6
7
|
export type Query<T extends unknown> = {
|
|
7
8
|
key: string;
|
|
8
9
|
run: (events: EventStore, store: QueryStore) => Observable<T>;
|
|
@@ -40,5 +41,6 @@ export declare class QueryStore {
|
|
|
40
41
|
threads: Set<string>;
|
|
41
42
|
hashtags: Set<string>;
|
|
42
43
|
} | undefined>;
|
|
44
|
+
thread(root: string | EventPointer | AddressPointer): Observable<Queries.Thread>;
|
|
43
45
|
}
|
|
44
46
|
export { Queries };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { stateful } from "../observable/stateful.js";
|
|
2
2
|
import { LRU } from "../utils/lru.js";
|
|
3
|
-
import * as Queries from "
|
|
3
|
+
import * as Queries from "../queries/index.js";
|
|
4
4
|
export class QueryStore {
|
|
5
5
|
static Queries = Queries;
|
|
6
6
|
store;
|
|
@@ -49,5 +49,8 @@ export class QueryStore {
|
|
|
49
49
|
mute(pubkey) {
|
|
50
50
|
return this.runQuery(Queries.UserMuteQuery)(pubkey);
|
|
51
51
|
}
|
|
52
|
+
thread(root) {
|
|
53
|
+
return this.runQuery(Queries.ThreadQuery)(root);
|
|
54
|
+
}
|
|
52
55
|
}
|
|
53
56
|
export { Queries };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
"./event-store": {
|
|
34
34
|
"import": "./dist/event-store/index.js",
|
|
35
35
|
"types": "./dist/event-store/index.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./queries": {
|
|
38
|
+
"import": "./dist/queries/index.js",
|
|
39
|
+
"types": "./dist/queries/index.d.ts"
|
|
36
40
|
}
|
|
37
41
|
},
|
|
38
42
|
"dependencies": {
|