applesauce-core 0.0.0-next-20250123214405 → 0.0.0-next-20250124150519

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.
Files changed (109) hide show
  1. package/dist/event-store/common.d.ts +1 -0
  2. package/dist/event-store/common.js +2 -0
  3. package/dist/event-store/database.d.ts +64 -0
  4. package/dist/event-store/database.js +311 -0
  5. package/dist/event-store/event-store.d.ts +49 -0
  6. package/dist/event-store/event-store.js +389 -0
  7. package/dist/event-store/index.d.ts +2 -0
  8. package/dist/event-store/index.js +2 -0
  9. package/dist/helpers/bolt11.d.ts +9 -0
  10. package/dist/helpers/bolt11.js +15 -0
  11. package/dist/helpers/cache.d.ts +5 -0
  12. package/dist/helpers/cache.js +17 -0
  13. package/dist/helpers/comment.d.ts +47 -0
  14. package/dist/helpers/comment.js +116 -0
  15. package/dist/helpers/content.d.ts +3 -0
  16. package/dist/helpers/content.js +8 -0
  17. package/dist/helpers/delete.d.ts +3 -0
  18. package/dist/helpers/delete.js +7 -0
  19. package/dist/helpers/emoji.d.ts +11 -0
  20. package/dist/helpers/emoji.js +16 -0
  21. package/dist/helpers/event.d.ts +48 -0
  22. package/dist/helpers/event.js +105 -0
  23. package/dist/helpers/external-id.d.ts +29 -0
  24. package/dist/helpers/external-id.js +20 -0
  25. package/dist/helpers/file-metadata.d.ts +55 -0
  26. package/dist/helpers/file-metadata.js +99 -0
  27. package/dist/helpers/file-metadata.test.d.ts +1 -0
  28. package/dist/helpers/file-metadata.test.js +103 -0
  29. package/dist/helpers/filter.d.ts +10 -0
  30. package/dist/helpers/filter.js +46 -0
  31. package/dist/helpers/groups.d.ts +14 -0
  32. package/dist/helpers/groups.js +16 -0
  33. package/dist/helpers/hashtag.d.ts +2 -0
  34. package/dist/helpers/hashtag.js +7 -0
  35. package/dist/helpers/hidden-tags.d.ts +48 -0
  36. package/dist/helpers/hidden-tags.js +108 -0
  37. package/dist/helpers/hidden-tags.test.d.ts +1 -0
  38. package/dist/helpers/hidden-tags.test.js +28 -0
  39. package/dist/helpers/index.d.ts +28 -0
  40. package/dist/helpers/index.js +28 -0
  41. package/dist/helpers/json.d.ts +2 -0
  42. package/dist/helpers/json.js +9 -0
  43. package/dist/helpers/lnurl.d.ts +4 -0
  44. package/dist/helpers/lnurl.js +40 -0
  45. package/dist/helpers/lru.d.ts +32 -0
  46. package/dist/helpers/lru.js +148 -0
  47. package/dist/helpers/mailboxes.d.ts +11 -0
  48. package/dist/helpers/mailboxes.js +36 -0
  49. package/dist/helpers/mailboxes.test.d.ts +1 -0
  50. package/dist/helpers/mailboxes.test.js +81 -0
  51. package/dist/helpers/picture-post.d.ts +4 -0
  52. package/dist/helpers/picture-post.js +6 -0
  53. package/dist/helpers/pointers.d.ts +55 -0
  54. package/dist/helpers/pointers.js +205 -0
  55. package/dist/helpers/profile.d.ts +20 -0
  56. package/dist/helpers/profile.js +31 -0
  57. package/dist/helpers/relays.d.ts +12 -0
  58. package/dist/helpers/relays.js +31 -0
  59. package/dist/helpers/share.d.ts +4 -0
  60. package/dist/helpers/share.js +12 -0
  61. package/dist/helpers/string.d.ts +10 -0
  62. package/dist/helpers/string.js +15 -0
  63. package/dist/helpers/tags.d.ts +25 -0
  64. package/dist/helpers/tags.js +42 -0
  65. package/dist/helpers/tags.test.d.ts +1 -0
  66. package/dist/helpers/tags.test.js +16 -0
  67. package/dist/helpers/threading.d.ts +55 -0
  68. package/dist/helpers/threading.js +94 -0
  69. package/dist/helpers/threading.test.d.ts +1 -0
  70. package/dist/helpers/threading.test.js +41 -0
  71. package/dist/helpers/time.d.ts +2 -0
  72. package/dist/helpers/time.js +4 -0
  73. package/dist/helpers/url.d.ts +14 -0
  74. package/dist/helpers/url.js +30 -0
  75. package/dist/helpers/zap.d.ts +39 -0
  76. package/dist/helpers/zap.js +95 -0
  77. package/dist/index.d.ts +5 -0
  78. package/dist/index.js +5 -0
  79. package/dist/logger.d.ts +2 -0
  80. package/dist/logger.js +2 -0
  81. package/dist/observable/get-value.d.ts +3 -0
  82. package/dist/observable/get-value.js +14 -0
  83. package/dist/observable/index.d.ts +2 -0
  84. package/dist/observable/index.js +2 -0
  85. package/dist/observable/share-latest-value.d.ts +8 -0
  86. package/dist/observable/share-latest-value.js +21 -0
  87. package/dist/promise/deferred.d.ts +6 -0
  88. package/dist/promise/deferred.js +15 -0
  89. package/dist/promise/index.d.ts +1 -0
  90. package/dist/promise/index.js +1 -0
  91. package/dist/queries/comments.d.ts +4 -0
  92. package/dist/queries/comments.js +14 -0
  93. package/dist/queries/index.d.ts +7 -0
  94. package/dist/queries/index.js +7 -0
  95. package/dist/queries/mailboxes.d.ts +6 -0
  96. package/dist/queries/mailboxes.js +13 -0
  97. package/dist/queries/profile.d.ts +4 -0
  98. package/dist/queries/profile.js +12 -0
  99. package/dist/queries/reactions.d.ts +4 -0
  100. package/dist/queries/reactions.js +19 -0
  101. package/dist/queries/simple.d.ts +16 -0
  102. package/dist/queries/simple.js +38 -0
  103. package/dist/queries/thread.d.ts +25 -0
  104. package/dist/queries/thread.js +92 -0
  105. package/dist/queries/zaps.d.ts +5 -0
  106. package/dist/queries/zaps.js +21 -0
  107. package/dist/query-store/index.d.ts +57 -0
  108. package/dist/query-store/index.js +68 -0
  109. package/package.json +1 -1
@@ -0,0 +1,30 @@
1
+ export const convertToUrl = (url) => (url instanceof URL ? url : new URL(url));
2
+ export const getURLFilename = (url) => url.pathname.split("/").pop()?.toLocaleLowerCase() || url.searchParams.get("filename")?.toLocaleLowerCase();
3
+ export const IMAGE_EXT = [".svg", ".gif", ".png", ".jpg", ".jpeg", ".webp", ".avif"];
4
+ export const VIDEO_EXT = [".mp4", ".mkv", ".webm", ".mov"];
5
+ export const STREAM_EXT = [".m3u8"];
6
+ export const AUDIO_EXT = [".mp3", ".wav", ".ogg", ".aac"];
7
+ /** Checks if a url is a image URL */
8
+ export function isImageURL(url) {
9
+ url = convertToUrl(url);
10
+ const filename = getURLFilename(url);
11
+ return !!filename && IMAGE_EXT.some((ext) => filename.endsWith(ext));
12
+ }
13
+ /** Checks if a url is a video URL */
14
+ export function isVideoURL(url) {
15
+ url = convertToUrl(url);
16
+ const filename = getURLFilename(url);
17
+ return !!filename && VIDEO_EXT.some((ext) => filename.endsWith(ext));
18
+ }
19
+ /** Checks if a url is a stream URL */
20
+ export function isStreamURL(url) {
21
+ url = convertToUrl(url);
22
+ const filename = getURLFilename(url);
23
+ return !!filename && STREAM_EXT.some((ext) => filename.endsWith(ext));
24
+ }
25
+ /** Checks if a url is a audio URL */
26
+ export function isAudioURL(url) {
27
+ url = convertToUrl(url);
28
+ const filename = getURLFilename(url);
29
+ return !!filename && AUDIO_EXT.some((ext) => filename.endsWith(ext));
30
+ }
@@ -0,0 +1,39 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ export declare const ZapRequestSymbol: unique symbol;
3
+ export declare const ZapFromSymbol: unique symbol;
4
+ export declare const ZapInvoiceSymbol: unique symbol;
5
+ export declare const ZapEventPointerSymbol: unique symbol;
6
+ export declare const ZapAddressPointerSymbol: unique symbol;
7
+ /** Returns the senders pubkey */
8
+ export declare function getZapSender(zap: NostrEvent): string;
9
+ /**
10
+ * Gets the receivers pubkey
11
+ * @throws
12
+ */
13
+ export declare function getZapRecipient(zap: NostrEvent): string;
14
+ /** Returns the parsed bolt11 invoice */
15
+ export declare function getZapPayment(zap: NostrEvent): import("./bolt11.js").ParsedInvoice | undefined;
16
+ /** Gets the AddressPointer that was zapped */
17
+ export declare function getZapAddressPointer(zap: NostrEvent): import("nostr-tools/nip19").AddressPointer | null;
18
+ /** Gets the EventPointer that was zapped */
19
+ export declare function getZapEventPointer(zap: NostrEvent): import("nostr-tools/nip19").EventPointer | null;
20
+ /** Gets the preimage for the bolt11 invoice */
21
+ export declare function getZapPreimage(zap: NostrEvent): string | undefined;
22
+ /**
23
+ * Returns the zap request event inside the zap receipt
24
+ * @throws
25
+ */
26
+ export declare function getZapRequest(zap: NostrEvent): import("nostr-tools").Event;
27
+ /**
28
+ * Checks if the zap is valid
29
+ * DOES NOT validate LNURL address
30
+ */
31
+ export declare function isValidZap(zap?: NostrEvent): boolean;
32
+ export type ZapSplit = {
33
+ pubkey: string;
34
+ percent: number;
35
+ weight: number;
36
+ relay?: string;
37
+ };
38
+ /** Returns the zap splits for an event */
39
+ export declare function getZapSplits(event: NostrEvent): ZapSplit[] | undefined;
@@ -0,0 +1,95 @@
1
+ import { kinds, nip57 } from "nostr-tools";
2
+ import { getOrComputeCachedValue } from "./cache.js";
3
+ import { getTagValue } from "./event.js";
4
+ import { isATag, isETag } from "./tags.js";
5
+ import { getAddressPointerFromATag, getEventPointerFromETag } from "./pointers.js";
6
+ import { parseBolt11 } from "./bolt11.js";
7
+ export const ZapRequestSymbol = Symbol.for("zap-request");
8
+ export const ZapFromSymbol = Symbol.for("zap-from");
9
+ export const ZapInvoiceSymbol = Symbol.for("zap-bolt11");
10
+ export const ZapEventPointerSymbol = Symbol.for("zap-event-pointer");
11
+ export const ZapAddressPointerSymbol = Symbol.for("zap-address-pointer");
12
+ /** Returns the senders pubkey */
13
+ export function getZapSender(zap) {
14
+ return getTagValue(zap, "P") || getZapRequest(zap).pubkey;
15
+ }
16
+ /**
17
+ * Gets the receivers pubkey
18
+ * @throws
19
+ */
20
+ export function getZapRecipient(zap) {
21
+ const recipient = getTagValue(zap, "p");
22
+ if (!recipient)
23
+ throw new Error("Missing recipient");
24
+ return recipient;
25
+ }
26
+ /** Returns the parsed bolt11 invoice */
27
+ export function getZapPayment(zap) {
28
+ return getOrComputeCachedValue(zap, ZapInvoiceSymbol, () => {
29
+ const bolt11 = getTagValue(zap, "bolt11");
30
+ return bolt11 ? parseBolt11(bolt11) : undefined;
31
+ });
32
+ }
33
+ /** Gets the AddressPointer that was zapped */
34
+ export function getZapAddressPointer(zap) {
35
+ return getOrComputeCachedValue(zap, ZapAddressPointerSymbol, () => {
36
+ const a = zap.tags.find(isATag);
37
+ return a ? getAddressPointerFromATag(a) : null;
38
+ });
39
+ }
40
+ /** Gets the EventPointer that was zapped */
41
+ export function getZapEventPointer(zap) {
42
+ return getOrComputeCachedValue(zap, ZapEventPointerSymbol, () => {
43
+ const e = zap.tags.find(isETag);
44
+ return e ? getEventPointerFromETag(e) : null;
45
+ });
46
+ }
47
+ /** Gets the preimage for the bolt11 invoice */
48
+ export function getZapPreimage(zap) {
49
+ return getTagValue(zap, "preimage");
50
+ }
51
+ /**
52
+ * Returns the zap request event inside the zap receipt
53
+ * @throws
54
+ */
55
+ export function getZapRequest(zap) {
56
+ return getOrComputeCachedValue(zap, ZapRequestSymbol, () => {
57
+ const description = getTagValue(zap, "description");
58
+ if (!description)
59
+ throw new Error("Missing description tag");
60
+ const error = nip57.validateZapRequest(description);
61
+ if (error)
62
+ throw new Error(error);
63
+ return JSON.parse(description);
64
+ });
65
+ }
66
+ /**
67
+ * Checks if the zap is valid
68
+ * DOES NOT validate LNURL address
69
+ */
70
+ export function isValidZap(zap) {
71
+ if (!zap)
72
+ return false;
73
+ if (zap.kind !== kinds.Zap)
74
+ return false;
75
+ try {
76
+ getZapRequest(zap);
77
+ getZapRecipient(zap);
78
+ return true;
79
+ }
80
+ catch (error) {
81
+ return false;
82
+ }
83
+ }
84
+ /** Returns the zap splits for an event */
85
+ export function getZapSplits(event) {
86
+ const tags = event.tags.filter((t) => t[0] === "zap" && t[1] && t[3]);
87
+ if (tags.length > 0) {
88
+ const targets = tags
89
+ .map((t) => ({ pubkey: t[1], relay: t[2], weight: parseFloat(t[3]) }))
90
+ .filter((p) => Number.isFinite(p.weight));
91
+ const total = targets.reduce((v, p) => v + p.weight, 0);
92
+ return targets.map((p) => ({ ...p, percent: p.weight / total }));
93
+ }
94
+ return undefined;
95
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./event-store/index.js";
2
+ export * from "./query-store/index.js";
3
+ export * as Helpers from "./helpers/index.js";
4
+ export * as Queries from "./queries/index.js";
5
+ export * from "./logger.js";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./event-store/index.js";
2
+ export * from "./query-store/index.js";
3
+ export * as Helpers from "./helpers/index.js";
4
+ export * as Queries from "./queries/index.js";
5
+ export * from "./logger.js";
@@ -0,0 +1,2 @@
1
+ import debug from "debug";
2
+ export declare const logger: debug.Debugger;
package/dist/logger.js ADDED
@@ -0,0 +1,2 @@
1
+ import debug from "debug";
2
+ export const logger = debug("applesauce");
@@ -0,0 +1,3 @@
1
+ import { Observable } from "rxjs";
2
+ /** Subscribes and returns the observables current or next value */
3
+ export declare function getValue<T>(observable: Observable<T>): T | Promise<T>;
@@ -0,0 +1,14 @@
1
+ import { BehaviorSubject } from "rxjs";
2
+ /** Subscribes and returns the observables current or next value */
3
+ export function getValue(observable) {
4
+ if (observable instanceof BehaviorSubject)
5
+ return observable.value;
6
+ if (Reflect.has(observable, "value"))
7
+ return Reflect.get(observable, "value");
8
+ return new Promise((res) => {
9
+ const sub = observable.subscribe((v) => {
10
+ res(v);
11
+ sub.unsubscribe();
12
+ });
13
+ });
14
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./get-value.js";
2
+ export * from "./share-latest-value.js";
@@ -0,0 +1,2 @@
1
+ export * from "./get-value.js";
2
+ export * from "./share-latest-value.js";
@@ -0,0 +1,8 @@
1
+ import { Observable, ShareConfig } from "rxjs";
2
+ /**
3
+ * Creates an operator that adds a 'value' property and multiplexes the source
4
+ * @param config Optional ShareConfig for customizing sharing behavior
5
+ */
6
+ export declare function shareLatestValue<T>(config?: ShareConfig<T>): (source: Observable<T>) => Observable<T> & {
7
+ value: T | undefined;
8
+ };
@@ -0,0 +1,21 @@
1
+ import { share } from "rxjs";
2
+ import { tap } from "rxjs/operators";
3
+ /**
4
+ * Creates an operator that adds a 'value' property and multiplexes the source
5
+ * @param config Optional ShareConfig for customizing sharing behavior
6
+ */
7
+ export function shareLatestValue(config = {}) {
8
+ return (source) => {
9
+ // Create storage for latest value
10
+ let latestValue = undefined;
11
+ // Create shared source with value tracking
12
+ const shared$ = source.pipe(tap((value) => {
13
+ latestValue = value;
14
+ }), share(config));
15
+ // Add value property
16
+ Object.defineProperty(shared$, "value", {
17
+ get: () => latestValue,
18
+ });
19
+ return shared$;
20
+ };
21
+ }
@@ -0,0 +1,6 @@
1
+ export type Deferred<T> = Promise<T> & {
2
+ resolve: (value?: T | PromiseLike<T>) => void;
3
+ reject: (reason?: any) => void;
4
+ };
5
+ /** Creates a controlled promise */
6
+ export declare function createDefer<T>(): Deferred<T>;
@@ -0,0 +1,15 @@
1
+ /** Creates a controlled promise */
2
+ export function createDefer() {
3
+ let _resolve;
4
+ let _reject;
5
+ const promise = new Promise((resolve, reject) => {
6
+ // @ts-ignore
7
+ _resolve = resolve;
8
+ _reject = reject;
9
+ });
10
+ // @ts-ignore
11
+ promise.resolve = _resolve;
12
+ // @ts-ignore
13
+ promise.reject = _reject;
14
+ return promise;
15
+ }
@@ -0,0 +1 @@
1
+ export * from "./deferred.js";
@@ -0,0 +1 @@
1
+ export * from "./deferred.js";
@@ -0,0 +1,4 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { Query } from "../query-store/index.js";
3
+ /** Returns all NIP-22 comment replies for the event */
4
+ export declare function CommentsQuery(parent: NostrEvent): Query<NostrEvent[]>;
@@ -0,0 +1,14 @@
1
+ import { COMMENT_KIND, getEventUID } from "../helpers/index.js";
2
+ import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
3
+ /** Returns all NIP-22 comment replies for the event */
4
+ export function CommentsQuery(parent) {
5
+ return {
6
+ key: `${getEventUID(parent)}-comments`,
7
+ run: (events) => {
8
+ const filter = { kinds: [COMMENT_KIND], "#e": [parent.id] };
9
+ if (isParameterizedReplaceableKind(parent.kind))
10
+ filter["#a"] = [getEventUID(parent)];
11
+ return events.timeline(filter);
12
+ },
13
+ };
14
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./comments.js";
2
+ export * from "./mailboxes.js";
3
+ export * from "./profile.js";
4
+ export * from "./reactions.js";
5
+ export * from "./simple.js";
6
+ export * from "./thread.js";
7
+ export * from "./zaps.js";
@@ -0,0 +1,7 @@
1
+ export * from "./comments.js";
2
+ export * from "./mailboxes.js";
3
+ export * from "./profile.js";
4
+ export * from "./reactions.js";
5
+ export * from "./simple.js";
6
+ export * from "./thread.js";
7
+ export * from "./zaps.js";
@@ -0,0 +1,6 @@
1
+ import { Query } from "../query-store/index.js";
2
+ /** A query that gets and parses the inbox and outbox relays for a pubkey */
3
+ export declare function MailboxesQuery(pubkey: string): Query<{
4
+ inboxes: string[];
5
+ outboxes: string[];
6
+ } | undefined>;
@@ -0,0 +1,13 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { map } from "rxjs/operators";
3
+ import { getInboxes, getOutboxes } from "../helpers/mailboxes.js";
4
+ /** A query that gets and parses the inbox and outbox relays for a pubkey */
5
+ export function MailboxesQuery(pubkey) {
6
+ return {
7
+ key: pubkey,
8
+ run: (events) => events.replaceable(kinds.RelayList, pubkey).pipe(map((event) => event && {
9
+ inboxes: getInboxes(event),
10
+ outboxes: getOutboxes(event),
11
+ })),
12
+ };
13
+ }
@@ -0,0 +1,4 @@
1
+ import { ProfileContent } from "../helpers/profile.js";
2
+ import { Query } from "../query-store/index.js";
3
+ /** A query that gets and parses the kind 0 metadata for a pubkey */
4
+ export declare function ProfileQuery(pubkey: string): Query<ProfileContent | undefined>;
@@ -0,0 +1,12 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { filter, map } from "rxjs/operators";
3
+ import { getProfileContent, isValidProfile } from "../helpers/profile.js";
4
+ /** A query that gets and parses the kind 0 metadata for a pubkey */
5
+ export function ProfileQuery(pubkey) {
6
+ return {
7
+ key: pubkey,
8
+ run: (events) => {
9
+ return events.replaceable(kinds.Metadata, pubkey).pipe(filter(isValidProfile), map((event) => event && getProfileContent(event)));
10
+ },
11
+ };
12
+ }
@@ -0,0 +1,4 @@
1
+ import { NostrEvent } from "nostr-tools";
2
+ import { Query } from "../query-store/index.js";
3
+ /** A query that returns all reactions to an event (supports replaceable events) */
4
+ export declare function ReactionsQuery(event: NostrEvent): Query<NostrEvent[]>;
@@ -0,0 +1,19 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { getEventUID, isReplaceable } from "../helpers/event.js";
3
+ /** 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,16 @@
1
+ import { Filter, NostrEvent } from "nostr-tools";
2
+ import { Query } from "../query-store/index.js";
3
+ /** Creates a Query that returns a single event or undefined */
4
+ export declare function SingleEventQuery(id: string): Query<NostrEvent | undefined>;
5
+ /** Creates a Query that returns a multiple events in a map */
6
+ export declare function MultipleEventsQuery(ids: string[]): Query<Record<string, NostrEvent>>;
7
+ /** Creates a Query returning the latest version of a replaceable event */
8
+ export declare function ReplaceableQuery(kind: number, pubkey: string, d?: string): Query<NostrEvent | undefined>;
9
+ /** Creates a Query that returns an array of sorted events matching the filters */
10
+ export declare function TimelineQuery(filters: Filter | Filter[], keepOldVersions?: boolean): Query<NostrEvent[]>;
11
+ /** Creates a Query that returns a directory of events by their UID */
12
+ export declare function ReplaceableSetQuery(pointers: {
13
+ kind: number;
14
+ pubkey: string;
15
+ identifier?: string;
16
+ }[]): Query<Record<string, NostrEvent>>;
@@ -0,0 +1,38 @@
1
+ import hash_sum from "hash-sum";
2
+ import { getReplaceableUID } from "../helpers/event.js";
3
+ /** Creates a Query that returns a single event or undefined */
4
+ export function SingleEventQuery(id) {
5
+ return {
6
+ key: id,
7
+ run: (events) => events.event(id),
8
+ };
9
+ }
10
+ /** Creates a Query that returns a multiple events in a map */
11
+ export function MultipleEventsQuery(ids) {
12
+ return {
13
+ key: ids.join(","),
14
+ run: (events) => events.events(ids),
15
+ };
16
+ }
17
+ /** Creates a Query returning the latest version of a replaceable event */
18
+ export function ReplaceableQuery(kind, pubkey, d) {
19
+ return {
20
+ key: getReplaceableUID(kind, pubkey, d),
21
+ run: (events) => events.replaceable(kind, pubkey, d),
22
+ };
23
+ }
24
+ /** Creates a Query that returns an array of sorted events matching the filters */
25
+ export function TimelineQuery(filters, keepOldVersions) {
26
+ filters = Array.isArray(filters) ? filters : [filters];
27
+ return {
28
+ key: hash_sum(filters) + (keepOldVersions ? "-history" : ""),
29
+ run: (events) => events.timeline(filters, keepOldVersions),
30
+ };
31
+ }
32
+ /** Creates a Query that returns a directory of events by their UID */
33
+ export function ReplaceableSetQuery(pointers) {
34
+ return {
35
+ key: hash_sum(pointers),
36
+ run: (events) => events.replaceableSet(pointers),
37
+ };
38
+ }
@@ -0,0 +1,25 @@
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>;
24
+ /** A query that gets all legacy and NIP-10, and NIP-22 replies for an event */
25
+ export declare function RepliesQuery(event: NostrEvent, overrideKinds?: number[]): Query<NostrEvent[]>;
@@ -0,0 +1,92 @@
1
+ import { kinds } from "nostr-tools";
2
+ import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
3
+ import { map } from "rxjs/operators";
4
+ import { getNip10References, interpretThreadTags } from "../helpers/threading.js";
5
+ import { getCoordinateFromAddressPointer, isAddressPointer, isEventPointer } from "../helpers/pointers.js";
6
+ import { getEventUID, getReplaceableUID, getTagValue, isEvent } from "../helpers/event.js";
7
+ import { COMMENT_KIND } from "../helpers/comment.js";
8
+ const defaultOptions = {
9
+ kinds: [kinds.ShortTextNote],
10
+ };
11
+ export function ThreadQuery(root, opts) {
12
+ const parentReferences = new Map();
13
+ const items = new Map();
14
+ const { kinds } = { ...defaultOptions, ...opts };
15
+ let rootUID = "";
16
+ const rootFilter = {};
17
+ const replyFilter = { kinds };
18
+ if (isAddressPointer(root)) {
19
+ rootUID = getCoordinateFromAddressPointer(root);
20
+ rootFilter.kinds = [root.kind];
21
+ rootFilter.authors = [root.pubkey];
22
+ rootFilter["#d"] = [root.identifier];
23
+ replyFilter["#a"] = [rootUID];
24
+ }
25
+ else if (typeof root === "string") {
26
+ rootUID = root;
27
+ rootFilter.ids = [root];
28
+ replyFilter["#e"] = [root];
29
+ }
30
+ else {
31
+ rootUID = root.id;
32
+ rootFilter.ids = [root.id];
33
+ replyFilter["#e"] = [root.id];
34
+ }
35
+ return {
36
+ key: `${rootUID}-${kinds.join(",")}`,
37
+ run: (events) => events.stream([rootFilter, replyFilter]).pipe(map((event) => {
38
+ if (!items.has(getEventUID(event))) {
39
+ const refs = getNip10References(event);
40
+ const replies = parentReferences.get(getEventUID(event)) || new Set();
41
+ const item = { event, refs, replies };
42
+ for (const child of replies) {
43
+ child.parent = item;
44
+ }
45
+ // add item to parent
46
+ if (refs.reply?.e || refs.reply?.a) {
47
+ let uid = refs.reply.e ? refs.reply.e.id : getCoordinateFromAddressPointer(refs.reply.a);
48
+ item.parent = items.get(uid);
49
+ if (item.parent) {
50
+ item.parent.replies.add(item);
51
+ }
52
+ else {
53
+ // parent isn't created yet, store ref for later
54
+ let set = parentReferences.get(uid);
55
+ if (!set) {
56
+ set = new Set();
57
+ parentReferences.set(uid, set);
58
+ }
59
+ set.add(item);
60
+ }
61
+ }
62
+ // add item to map
63
+ items.set(getEventUID(event), item);
64
+ }
65
+ return { root: items.get(rootUID), all: items };
66
+ })),
67
+ };
68
+ }
69
+ /** A query that gets all legacy and NIP-10, and NIP-22 replies for an event */
70
+ export function RepliesQuery(event, overrideKinds) {
71
+ return {
72
+ key: getEventUID(event),
73
+ run: (events) => {
74
+ const kinds = overrideKinds || event.kind === 1 ? [1, COMMENT_KIND] : [COMMENT_KIND];
75
+ const filter = { kinds };
76
+ if (isEvent(parent) || isEventPointer(event))
77
+ filter["#e"] = [event.id];
78
+ const address = isParameterizedReplaceableKind(event.kind)
79
+ ? getReplaceableUID(event.kind, event.pubkey, getTagValue(event, "d"))
80
+ : undefined;
81
+ if (address) {
82
+ filter["#a"] = [address];
83
+ }
84
+ return events.timeline(filter).pipe(map((events) => {
85
+ return events.filter((e) => {
86
+ const refs = interpretThreadTags(e.tags);
87
+ return refs.reply?.e?.[1] === event.id || refs.reply?.a?.[1] === address;
88
+ });
89
+ }));
90
+ },
91
+ };
92
+ }
@@ -0,0 +1,5 @@
1
+ import { AddressPointer, EventPointer } from "nostr-tools/nip19";
2
+ import { NostrEvent } from "nostr-tools";
3
+ import { Query } from "../query-store/index.js";
4
+ /** A query that gets all zap events for an event */
5
+ export declare function EventZapsQuery(id: string | EventPointer | AddressPointer): Query<NostrEvent[]>;
@@ -0,0 +1,21 @@
1
+ import { map } from "rxjs";
2
+ import { kinds } from "nostr-tools";
3
+ import { getCoordinateFromAddressPointer, isAddressPointer } from "../helpers/pointers.js";
4
+ import { isValidZap } from "../helpers/zap.js";
5
+ /** A query that gets all zap events for an event */
6
+ export function EventZapsQuery(id) {
7
+ return {
8
+ key: JSON.stringify(id),
9
+ run: (events) => {
10
+ if (isAddressPointer(id)) {
11
+ return events
12
+ .timeline([{ kinds: [kinds.Zap], "#a": [getCoordinateFromAddressPointer(id)] }])
13
+ .pipe(map((events) => events.filter(isValidZap)));
14
+ }
15
+ else {
16
+ id = typeof id === "string" ? id : id.id;
17
+ return events.timeline([{ kinds: [kinds.Zap], "#e": [id] }]).pipe(map((events) => events.filter(isValidZap)));
18
+ }
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,57 @@
1
+ import { BehaviorSubject, Observable } from "rxjs";
2
+ import { Filter, NostrEvent } from "nostr-tools";
3
+ import { EventStore } from "../event-store/event-store.js";
4
+ import { LRU } from "../helpers/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
+ /**
9
+ * A unique key for this query. this is used to detect duplicate queries
10
+ */
11
+ key: string;
12
+ /** The args array this query was created with. This is mostly for debugging */
13
+ args?: Array<any>;
14
+ /**
15
+ * The meat of the query, this should return an Observables that subscribes to the eventStore in some way
16
+ */
17
+ run: (events: EventStore, store: QueryStore) => Observable<T>;
18
+ };
19
+ export type QueryConstructor<T extends unknown, Args extends Array<any>> = (...args: Args) => Query<T>;
20
+ export declare class QueryStore {
21
+ static Queries: typeof Queries;
22
+ store: EventStore;
23
+ constructor(store: EventStore);
24
+ queries: LRU<Query<any>>;
25
+ observables: WeakMap<Query<any>, Observable<any> | BehaviorSubject<any>>;
26
+ /** Creates a cached query */
27
+ createQuery<T extends unknown, Args extends Array<any>>(queryConstructor: (...args: Args) => {
28
+ key: string;
29
+ run: (events: EventStore, store: QueryStore) => Observable<T>;
30
+ }, ...args: Args): Observable<T>;
31
+ /** Creates a SingleEventQuery */
32
+ event(id: string): Observable<import("nostr-tools").Event | undefined>;
33
+ /** Creates a MultipleEventsQuery */
34
+ events(ids: string[]): Observable<Record<string, import("nostr-tools").Event>>;
35
+ /** Creates a ReplaceableQuery */
36
+ replaceable(kind: number, pubkey: string, d?: string): Observable<import("nostr-tools").Event | undefined>;
37
+ /** Creates a ReplaceableSetQuery */
38
+ replaceableSet(pointers: {
39
+ kind: number;
40
+ pubkey: string;
41
+ identifier?: string;
42
+ }[]): Observable<Record<string, import("nostr-tools").Event>>;
43
+ /** Creates a TimelineQuery */
44
+ timeline(filters: Filter | Filter[], keepOldVersions?: boolean): Observable<import("nostr-tools").Event[]>;
45
+ /** Creates a ProfileQuery */
46
+ profile(pubkey: string): Observable<import("../helpers/profile.js").ProfileContent | undefined>;
47
+ /** Creates a ReactionsQuery */
48
+ reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
49
+ /** Creates a MailboxesQuery */
50
+ mailboxes(pubkey: string): Observable<{
51
+ inboxes: string[];
52
+ outboxes: string[];
53
+ } | undefined>;
54
+ /** Creates a ThreadQuery */
55
+ thread(root: string | EventPointer | AddressPointer): Observable<Queries.Thread>;
56
+ }
57
+ export { Queries };