applesauce-core 0.5.0 → 0.7.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.
Files changed (39) hide show
  1. package/dist/event-store/database.d.ts +21 -2
  2. package/dist/event-store/database.js +15 -1
  3. package/dist/event-store/event-store.d.ts +6 -1
  4. package/dist/event-store/event-store.js +104 -30
  5. package/dist/helpers/cache.d.ts +5 -0
  6. package/dist/helpers/cache.js +17 -0
  7. package/dist/helpers/emoji.d.ts +2 -0
  8. package/dist/helpers/emoji.js +4 -0
  9. package/dist/helpers/hashtag.d.ts +2 -0
  10. package/dist/helpers/hashtag.js +7 -0
  11. package/dist/helpers/index.d.ts +6 -0
  12. package/dist/helpers/index.js +6 -0
  13. package/dist/helpers/string.d.ts +2 -0
  14. package/dist/helpers/string.js +3 -0
  15. package/dist/helpers/tags.d.ts +6 -0
  16. package/dist/helpers/tags.js +18 -0
  17. package/dist/helpers/time.d.ts +2 -0
  18. package/dist/helpers/time.js +4 -0
  19. package/dist/helpers/url.d.ts +11 -0
  20. package/dist/helpers/url.js +29 -0
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/observable/getValue.d.ts +2 -0
  24. package/dist/observable/getValue.js +11 -0
  25. package/dist/observable/index.d.ts +1 -0
  26. package/dist/observable/index.js +1 -0
  27. package/dist/queries/index.d.ts +0 -1
  28. package/dist/queries/index.js +0 -1
  29. package/dist/queries/simple.d.ts +7 -1
  30. package/dist/queries/simple.js +12 -6
  31. package/dist/query-store/index.d.ts +10 -10
  32. package/dist/query-store/index.js +7 -7
  33. package/package.json +9 -1
  34. package/dist/helpers/mute.d.ts +0 -21
  35. package/dist/helpers/mute.js +0 -52
  36. package/dist/queries/mute.d.ts +0 -7
  37. package/dist/queries/mute.js +0 -16
  38. /package/dist/{utils → helpers}/lru.d.ts +0 -0
  39. /package/dist/{utils → helpers}/lru.js +0 -0
@@ -1,12 +1,12 @@
1
1
  /// <reference types="debug" />
2
2
  /// <reference types="zen-observable" />
3
3
  import { Filter, NostrEvent } from "nostr-tools";
4
- import { LRU } from "../utils/lru.js";
4
+ import { LRU } from "../helpers/lru.js";
5
5
  /**
6
6
  * An in-memory database for nostr events
7
7
  */
8
8
  export declare class Database {
9
- log: import("debug").Debugger;
9
+ protected log: import("debug").Debugger;
10
10
  /** Indexes */
11
11
  protected kinds: Map<number, Set<import("nostr-tools").Event>>;
12
12
  protected authors: Map<string, Set<import("nostr-tools").Event>>;
@@ -15,6 +15,7 @@ export declare class Database {
15
15
  /** LRU cache of last events touched */
16
16
  events: LRU<import("nostr-tools").Event>;
17
17
  private insertedSignal;
18
+ private updatedSignal;
18
19
  private deletedSignal;
19
20
  /** A stream of events inserted into the database */
20
21
  inserted: {
@@ -29,6 +30,19 @@ export declare class Database {
29
30
  flatMap<R_2>(callback: (value: import("nostr-tools").Event) => ZenObservable.ObservableLike<R_2>): import("zen-observable")<R_2>;
30
31
  concat<R_3>(...observable: import("zen-observable")<R_3>[]): import("zen-observable")<R_3>;
31
32
  };
33
+ /** A stream of events that have been updated */
34
+ updated: {
35
+ subscribe(observer: ZenObservable.Observer<import("nostr-tools").Event>): ZenObservable.Subscription;
36
+ subscribe(onNext: (value: import("nostr-tools").Event) => void, onError?: ((error: any) => void) | undefined, onComplete?: (() => void) | undefined): ZenObservable.Subscription;
37
+ forEach(callback: (value: import("nostr-tools").Event) => void): Promise<void>;
38
+ map<R>(callback: (value: import("nostr-tools").Event) => R): import("zen-observable")<R>;
39
+ filter<S extends import("nostr-tools").Event>(callback: (value: import("nostr-tools").Event) => value is S): import("zen-observable")<S>;
40
+ filter(callback: (value: import("nostr-tools").Event) => boolean): import("zen-observable")<import("nostr-tools").Event>;
41
+ reduce(callback: (previousValue: import("nostr-tools").Event, currentValue: import("nostr-tools").Event) => import("nostr-tools").Event, initialValue?: import("nostr-tools").Event | undefined): import("zen-observable")<import("nostr-tools").Event>;
42
+ reduce<R_1>(callback: (previousValue: R_1, currentValue: import("nostr-tools").Event) => R_1, initialValue?: R_1 | undefined): import("zen-observable")<R_1>;
43
+ flatMap<R_2>(callback: (value: import("nostr-tools").Event) => ZenObservable.ObservableLike<R_2>): import("zen-observable")<R_2>;
44
+ concat<R_3>(...observable: import("zen-observable")<R_3>[]): import("zen-observable")<R_3>;
45
+ };
32
46
  /** A stream of events removed of the database */
33
47
  deleted: {
34
48
  subscribe(observer: ZenObservable.Observer<import("nostr-tools").Event>): ZenObservable.Subscription;
@@ -42,6 +56,7 @@ export declare class Database {
42
56
  flatMap<R_2>(callback: (value: import("nostr-tools").Event) => ZenObservable.ObservableLike<R_2>): import("zen-observable")<R_2>;
43
57
  concat<R_3>(...observable: import("zen-observable")<R_3>[]): import("zen-observable")<R_3>;
44
58
  };
59
+ get size(): number;
45
60
  protected claims: WeakMap<import("nostr-tools").Event, any>;
46
61
  /** Index helper methods */
47
62
  protected getKindIndex(kind: number): Set<import("nostr-tools").Event>;
@@ -55,7 +70,11 @@ export declare class Database {
55
70
  hasReplaceable(kind: number, pubkey: string, d?: string): boolean;
56
71
  /** Gets a replaceable event and touches it */
57
72
  getReplaceable(kind: number, pubkey: string, d?: string): import("nostr-tools").Event | undefined;
73
+ /** Inserts an event into the database and notifies all subscriptions */
58
74
  addEvent(event: NostrEvent): import("nostr-tools").Event;
75
+ /** Inserts and event into the database and notifies all subscriptions that the event has updated */
76
+ updateEvent(event: NostrEvent): import("nostr-tools").Event;
77
+ /** Deletes an event from the database and notifies all subscriptions */
59
78
  deleteEvent(eventOrUID: string | NostrEvent): boolean;
60
79
  /** Sets the claim on the event and touches it */
61
80
  claimEvent(event: NostrEvent, claim: any): void;
@@ -3,7 +3,7 @@ import PushStream from "zen-push";
3
3
  import { getEventUID, getIndexableTags, getReplaceableUID } from "../helpers/event.js";
4
4
  import { INDEXABLE_TAGS } from "./common.js";
5
5
  import { logger } from "../logger.js";
6
- import { LRU } from "../utils/lru.js";
6
+ import { LRU } from "../helpers/lru.js";
7
7
  /**
8
8
  * An in-memory database for nostr events
9
9
  */
@@ -17,11 +17,17 @@ export class Database {
17
17
  /** LRU cache of last events touched */
18
18
  events = new LRU();
19
19
  insertedSignal = new PushStream();
20
+ updatedSignal = new PushStream();
20
21
  deletedSignal = new PushStream();
21
22
  /** A stream of events inserted into the database */
22
23
  inserted = this.insertedSignal.observable;
24
+ /** A stream of events that have been updated */
25
+ updated = this.updatedSignal.observable;
23
26
  /** A stream of events removed of the database */
24
27
  deleted = this.deletedSignal.observable;
28
+ get size() {
29
+ return this.events.size;
30
+ }
25
31
  claims = new WeakMap();
26
32
  /** Index helper methods */
27
33
  getKindIndex(kind) {
@@ -69,6 +75,7 @@ export class Database {
69
75
  getReplaceable(kind, pubkey, d) {
70
76
  return this.events.get(getReplaceableUID(kind, pubkey, d));
71
77
  }
78
+ /** Inserts an event into the database and notifies all subscriptions */
72
79
  addEvent(event) {
73
80
  const uid = getEventUID(event);
74
81
  const current = this.events.get(uid);
@@ -86,6 +93,13 @@ export class Database {
86
93
  this.insertedSignal.next(event);
87
94
  return event;
88
95
  }
96
+ /** Inserts and event into the database and notifies all subscriptions that the event has updated */
97
+ updateEvent(event) {
98
+ const inserted = this.addEvent(event);
99
+ this.updatedSignal.next(inserted);
100
+ return inserted;
101
+ }
102
+ /** Deletes an event from the database and notifies all subscriptions */
89
103
  deleteEvent(eventOrUID) {
90
104
  let event = typeof eventOrUID === "string" ? this.events.get(eventOrUID) : eventOrUID;
91
105
  if (!event)
@@ -2,12 +2,15 @@ import { Filter, NostrEvent } from "nostr-tools";
2
2
  import Observable from "zen-observable";
3
3
  import { Database } from "./database.js";
4
4
  export declare class EventStore {
5
- events: Database;
5
+ database: Database;
6
6
  private singles;
7
7
  private streams;
8
8
  private timelines;
9
9
  constructor();
10
+ /** Adds an event to the database */
10
11
  add(event: NostrEvent, fromRelay?: string): import("nostr-tools").Event;
12
+ /** Add an event to the store and notifies all subscribes it has updated */
13
+ update(event: NostrEvent): import("nostr-tools").Event;
11
14
  getAll(filters: Filter[]): Set<import("nostr-tools").Event>;
12
15
  hasEvent(uid: string): import("nostr-tools").Event | undefined;
13
16
  getEvent(uid: string): import("nostr-tools").Event | undefined;
@@ -15,6 +18,8 @@ export declare class EventStore {
15
18
  getReplaceable(kind: number, pubkey: string, d?: string): import("nostr-tools").Event | undefined;
16
19
  /** Creates an observable that updates a single event */
17
20
  event(uid: string): Observable<import("nostr-tools").Event | undefined>;
21
+ /** Creates an observable that subscribes to multiple events */
22
+ events(uids: string[]): Observable<Map<string, import("nostr-tools").Event>>;
18
23
  /** Creates an observable that updates a single replaceable event */
19
24
  replaceable(kind: number, pubkey: string, d?: string): Observable<import("nostr-tools").Event | undefined>;
20
25
  /** Creates an observable that streams all events that match the filter */
@@ -5,58 +5,68 @@ import { getEventUID, getReplaceableUID } from "../helpers/event.js";
5
5
  import { matchFilters } from "../helpers/filter.js";
6
6
  import { addSeenRelay } from "../helpers/relays.js";
7
7
  export class EventStore {
8
- events;
8
+ database;
9
9
  singles = new Map();
10
10
  streams = new Map();
11
11
  timelines = new Map();
12
12
  constructor() {
13
- this.events = new Database();
13
+ this.database = new Database();
14
14
  }
15
+ /** Adds an event to the database */
15
16
  add(event, fromRelay) {
16
- const inserted = this.events.addEvent(event);
17
+ const inserted = this.database.addEvent(event);
17
18
  if (fromRelay)
18
19
  addSeenRelay(inserted, fromRelay);
19
20
  return inserted;
20
21
  }
22
+ /** Add an event to the store and notifies all subscribes it has updated */
23
+ update(event) {
24
+ return this.database.updateEvent(event);
25
+ }
21
26
  getAll(filters) {
22
- return this.events.getForFilters(filters);
27
+ return this.database.getForFilters(filters);
23
28
  }
24
29
  hasEvent(uid) {
25
- return this.events.hasEvent(uid);
30
+ return this.database.hasEvent(uid);
26
31
  }
27
32
  getEvent(uid) {
28
- return this.events.getEvent(uid);
33
+ return this.database.getEvent(uid);
29
34
  }
30
35
  hasReplaceable(kind, pubkey, d) {
31
- return this.events.hasReplaceable(kind, pubkey, d);
36
+ return this.database.hasReplaceable(kind, pubkey, d);
32
37
  }
33
38
  getReplaceable(kind, pubkey, d) {
34
- return this.events.getReplaceable(kind, pubkey, d);
39
+ return this.database.getReplaceable(kind, pubkey, d);
35
40
  }
36
41
  /** Creates an observable that updates a single event */
37
42
  event(uid) {
38
43
  return new Observable((observer) => {
39
- let current = this.events.getEvent(uid);
44
+ let current = this.database.getEvent(uid);
40
45
  if (current) {
41
46
  observer.next(current);
42
- this.events.claimEvent(current, observer);
47
+ this.database.claimEvent(current, observer);
43
48
  }
44
49
  // subscribe to future events
45
- const inserted = this.events.inserted.subscribe((event) => {
50
+ const inserted = this.database.inserted.subscribe((event) => {
46
51
  if (getEventUID(event) === uid && (!current || event.created_at > current.created_at)) {
47
52
  // remove old claim
48
53
  if (current)
49
- this.events.removeClaim(current, observer);
54
+ this.database.removeClaim(current, observer);
50
55
  current = event;
51
56
  observer.next(event);
52
57
  // claim new event
53
- this.events.claimEvent(current, observer);
58
+ this.database.claimEvent(current, observer);
54
59
  }
55
60
  });
61
+ // subscribe to updates
62
+ const updated = this.database.updated.subscribe((event) => {
63
+ if (event === current)
64
+ observer.next(event);
65
+ });
56
66
  // subscribe to deleted events
57
- const deleted = this.events.deleted.subscribe((event) => {
67
+ const deleted = this.database.deleted.subscribe((event) => {
58
68
  if (getEventUID(event) === uid && current) {
59
- this.events.removeClaim(current, observer);
69
+ this.database.removeClaim(current, observer);
60
70
  current = undefined;
61
71
  observer.next(undefined);
62
72
  }
@@ -65,9 +75,66 @@ export class EventStore {
65
75
  return () => {
66
76
  inserted.unsubscribe();
67
77
  deleted.unsubscribe();
78
+ updated.unsubscribe();
68
79
  this.singles.delete(observer);
69
80
  if (current)
70
- this.events.removeClaim(current, observer);
81
+ this.database.removeClaim(current, observer);
82
+ };
83
+ });
84
+ }
85
+ /** Creates an observable that subscribes to multiple events */
86
+ events(uids) {
87
+ return new Observable((observer) => {
88
+ const events = new Map();
89
+ for (const uid of uids) {
90
+ const e = this.getEvent(uid);
91
+ if (e) {
92
+ events.set(uid, e);
93
+ this.database.claimEvent(e, observer);
94
+ }
95
+ }
96
+ observer.next(events);
97
+ // subscribe to future events
98
+ const inserted = this.database.inserted.subscribe((event) => {
99
+ const uid = getEventUID(event);
100
+ if (uids.includes(uid)) {
101
+ const current = events.get(uid);
102
+ // remove old claim
103
+ if (!current || event.created_at > current.created_at) {
104
+ if (current)
105
+ this.database.removeClaim(current, observer);
106
+ events.set(uid, event);
107
+ observer.next(events);
108
+ // claim new event
109
+ this.database.claimEvent(event, observer);
110
+ }
111
+ }
112
+ });
113
+ // subscribe to updates
114
+ const updated = this.database.updated.subscribe((event) => {
115
+ const uid = getEventUID(event);
116
+ if (uids.includes(uid))
117
+ observer.next(events);
118
+ });
119
+ // subscribe to deleted events
120
+ const deleted = this.database.deleted.subscribe((event) => {
121
+ const uid = getEventUID(event);
122
+ if (uids.includes(uid)) {
123
+ const current = events.get(uid);
124
+ if (current) {
125
+ this.database.removeClaim(current, observer);
126
+ events.delete(uid);
127
+ observer.next(events);
128
+ }
129
+ }
130
+ });
131
+ return () => {
132
+ inserted.unsubscribe();
133
+ deleted.unsubscribe();
134
+ updated.unsubscribe();
135
+ for (const [_uid, event] of events) {
136
+ this.database.removeClaim(event, observer);
137
+ }
71
138
  };
72
139
  });
73
140
  }
@@ -79,17 +146,17 @@ export class EventStore {
79
146
  stream(filters) {
80
147
  return new Observable((observer) => {
81
148
  let claimed = new Set();
82
- let events = this.events.getForFilters(filters);
149
+ let events = this.database.getForFilters(filters);
83
150
  for (const event of events) {
84
151
  observer.next(event);
85
- this.events.claimEvent(event, observer);
152
+ this.database.claimEvent(event, observer);
86
153
  claimed.add(event);
87
154
  }
88
155
  // subscribe to future events
89
- const sub = this.events.inserted.subscribe((event) => {
156
+ const sub = this.database.inserted.subscribe((event) => {
90
157
  if (matchFilters(filters, event)) {
91
158
  observer.next(event);
92
- this.events.claimEvent(event, observer);
159
+ this.database.claimEvent(event, observer);
93
160
  claimed.add(event);
94
161
  }
95
162
  });
@@ -99,7 +166,7 @@ export class EventStore {
99
166
  this.streams.delete(observer);
100
167
  // remove all claims
101
168
  for (const event of claimed)
102
- this.events.removeClaim(event, observer);
169
+ this.database.removeClaim(event, observer);
103
170
  claimed.clear();
104
171
  };
105
172
  });
@@ -110,15 +177,15 @@ export class EventStore {
110
177
  const seen = new Map();
111
178
  const timeline = [];
112
179
  // build initial timeline
113
- const events = this.events.getForFilters(filters);
180
+ const events = this.database.getForFilters(filters);
114
181
  for (const event of events) {
115
182
  insertEventIntoDescendingList(timeline, event);
116
- this.events.claimEvent(event, observer);
183
+ this.database.claimEvent(event, observer);
117
184
  seen.set(getEventUID(event), event);
118
185
  }
119
186
  observer.next([...timeline]);
120
187
  // subscribe to future events
121
- const inserted = this.events.inserted.subscribe((event) => {
188
+ const inserted = this.database.inserted.subscribe((event) => {
122
189
  if (matchFilters(filters, event)) {
123
190
  const uid = getEventUID(event);
124
191
  let current = seen.get(uid);
@@ -129,21 +196,27 @@ export class EventStore {
129
196
  observer.next([...timeline]);
130
197
  // update the claim
131
198
  seen.set(uid, event);
132
- this.events.removeClaim(current, observer);
133
- this.events.claimEvent(event, observer);
199
+ this.database.removeClaim(current, observer);
200
+ this.database.claimEvent(event, observer);
134
201
  }
135
202
  }
136
203
  else {
137
204
  insertEventIntoDescendingList(timeline, event);
138
205
  observer.next([...timeline]);
139
206
  // claim new event
140
- this.events.claimEvent(event, observer);
207
+ this.database.claimEvent(event, observer);
141
208
  seen.set(getEventUID(event), event);
142
209
  }
143
210
  }
144
211
  });
212
+ // subscribe to updates
213
+ const updated = this.database.updated.subscribe((event) => {
214
+ if (seen.has(getEventUID(event))) {
215
+ observer.next([...timeline]);
216
+ }
217
+ });
145
218
  // subscribe to removed events
146
- const deleted = this.events.deleted.subscribe((event) => {
219
+ const deleted = this.database.deleted.subscribe((event) => {
147
220
  const uid = getEventUID(event);
148
221
  let current = seen.get(uid);
149
222
  if (current) {
@@ -152,7 +225,7 @@ export class EventStore {
152
225
  observer.next([...timeline]);
153
226
  // remove the claim
154
227
  seen.delete(uid);
155
- this.events.removeClaim(current, observer);
228
+ this.database.removeClaim(current, observer);
156
229
  }
157
230
  });
158
231
  this.timelines.set(observer, filters);
@@ -160,9 +233,10 @@ export class EventStore {
160
233
  this.timelines.delete(observer);
161
234
  inserted.unsubscribe();
162
235
  deleted.unsubscribe();
236
+ updated.unsubscribe();
163
237
  // remove all claims
164
238
  for (const [_, event] of seen) {
165
- this.events.removeClaim(event, observer);
239
+ this.database.removeClaim(event, observer);
166
240
  }
167
241
  seen.clear();
168
242
  };
@@ -0,0 +1,5 @@
1
+ import { EventTemplate, NostrEvent } from "nostr-tools";
2
+ export declare function getCachedValue<T extends unknown>(event: NostrEvent | EventTemplate, symbol: symbol): T | undefined;
3
+ export declare function setCachedValue<T extends unknown>(event: NostrEvent | EventTemplate, symbol: symbol, value: T): void;
4
+ /** Internal method used to cache computed values on events */
5
+ export declare function getOrComputeCachedValue<T extends unknown>(event: NostrEvent | EventTemplate, symbol: symbol, compute: (event: NostrEvent | EventTemplate) => T): T;
@@ -0,0 +1,17 @@
1
+ export function getCachedValue(event, symbol) {
2
+ // @ts-expect-error
3
+ return event[symbol];
4
+ }
5
+ export function setCachedValue(event, symbol, value) {
6
+ // @ts-expect-error
7
+ event[symbol] = value;
8
+ }
9
+ /** Internal method used to cache computed values on events */
10
+ export function getOrComputeCachedValue(event, symbol, compute) {
11
+ let cached = getCachedValue(event, symbol);
12
+ if (!cached) {
13
+ // @ts-expect-error
14
+ cached = event[symbol] = compute(event);
15
+ }
16
+ return cached;
17
+ }
@@ -0,0 +1,2 @@
1
+ import { EventTemplate, NostrEvent } from "nostr-tools";
2
+ export declare function getEmojiTag(event: NostrEvent | EventTemplate, code: string): ["emoji", string, string];
@@ -0,0 +1,4 @@
1
+ export function getEmojiTag(event, code) {
2
+ code = code.replace(/^:|:$/g, "").toLocaleLowerCase();
3
+ return event.tags.filter((t) => t[0] === "emoji" && t[1] && t[2]).find((t) => t[1].toLowerCase() === code);
4
+ }
@@ -0,0 +1,2 @@
1
+ import { EventTemplate, NostrEvent } from "nostr-tools";
2
+ export declare function getHashtagTag(event: NostrEvent | EventTemplate, hashtag: string): ["t", string];
@@ -0,0 +1,7 @@
1
+ import { stripInvisibleChar } from "./string.js";
2
+ export function getHashtagTag(event, hashtag) {
3
+ hashtag = stripInvisibleChar(hashtag.replace(/^#/, "").toLocaleLowerCase());
4
+ return event.tags
5
+ .filter((t) => t[0] === "t" && t[1])
6
+ .find((t) => stripInvisibleChar(t[1].toLowerCase()) === hashtag);
7
+ }
@@ -6,3 +6,9 @@ export * from "./mailboxes.js";
6
6
  export * from "./threading.js";
7
7
  export * from "./pointers.js";
8
8
  export * from "./string.js";
9
+ export * from "./time.js";
10
+ export * from "./tags.js";
11
+ export * from "./emoji.js";
12
+ export * from "./lru.js";
13
+ export * from "./hashtag.js";
14
+ export * from "./url.js";
@@ -6,3 +6,9 @@ export * from "./mailboxes.js";
6
6
  export * from "./threading.js";
7
7
  export * from "./pointers.js";
8
8
  export * from "./string.js";
9
+ export * from "./time.js";
10
+ export * from "./tags.js";
11
+ export * from "./emoji.js";
12
+ export * from "./lru.js";
13
+ export * from "./hashtag.js";
14
+ export * from "./url.js";
@@ -1,2 +1,4 @@
1
1
  export declare function isHex(str?: string): boolean;
2
2
  export declare function isHexKey(key?: string): boolean;
3
+ export declare function stripInvisibleChar(str: string): string;
4
+ export declare function stripInvisibleChar(str?: string | undefined): string | undefined;
@@ -8,3 +8,6 @@ export function isHexKey(key) {
8
8
  return true;
9
9
  return false;
10
10
  }
11
+ export function stripInvisibleChar(str) {
12
+ return str && str.replaceAll(/[\p{Cf}\p{Zs}]/gu, "");
13
+ }
@@ -0,0 +1,6 @@
1
+ export declare function isETag(tag: string[]): tag is ["e", string, ...string[]];
2
+ export declare function isPTag(tag: string[]): tag is ["p", string, ...string[]];
3
+ export declare function isRTag(tag: string[]): tag is ["r", string, ...string[]];
4
+ export declare function isDTag(tag: string[]): tag is ["d", string, ...string[]];
5
+ export declare function isATag(tag: string[]): tag is ["a", string, ...string[]];
6
+ export declare function isTTag(tag: string[]): tag is ["t", string, ...string[]];
@@ -0,0 +1,18 @@
1
+ export function isETag(tag) {
2
+ return tag[0] === "e" && tag[1] !== undefined;
3
+ }
4
+ export function isPTag(tag) {
5
+ return tag[0] === "p" && tag[1] !== undefined;
6
+ }
7
+ export function isRTag(tag) {
8
+ return tag[0] === "r" && tag[1] !== undefined;
9
+ }
10
+ export function isDTag(tag) {
11
+ return tag[0] === "d" && tag[1] !== undefined;
12
+ }
13
+ export function isATag(tag) {
14
+ return tag[0] === "a" && tag[1] !== undefined;
15
+ }
16
+ export function isTTag(tag) {
17
+ return tag[0] === "a" && tag[1] !== undefined;
18
+ }
@@ -0,0 +1,2 @@
1
+ /** Returns the current unix timestamp */
2
+ export declare function unixNow(): number;
@@ -0,0 +1,4 @@
1
+ /** Returns the current unix timestamp */
2
+ export function unixNow() {
3
+ return Math.round(Date.now() / 1000);
4
+ }
@@ -0,0 +1,11 @@
1
+ export declare const convertToUrl: (url: string | URL) => URL;
2
+ export declare const getURLFilename: (url: URL) => string | undefined;
3
+ export declare const IMAGE_EXT: string[];
4
+ export declare const VIDEO_EXT: string[];
5
+ export declare const STREAM_EXT: string[];
6
+ export declare const AUDIO_EXT: string[];
7
+ export declare function isVisualMediaURL(url: string | URL): boolean;
8
+ export declare function isImageURL(url: string | URL): boolean;
9
+ export declare function isVideoURL(url: string | URL): boolean;
10
+ export declare function isStreamURL(url: string | URL): boolean;
11
+ export declare function isAudioURL(url: string | URL): boolean;
@@ -0,0 +1,29 @@
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
+ export function isVisualMediaURL(url) {
8
+ return isImageURL(url) || isVideoURL(url) || isStreamURL(url);
9
+ }
10
+ export function isImageURL(url) {
11
+ url = convertToUrl(url);
12
+ const filename = getURLFilename(url);
13
+ return !!filename && IMAGE_EXT.some((ext) => filename.endsWith(ext));
14
+ }
15
+ export function isVideoURL(url) {
16
+ url = convertToUrl(url);
17
+ const filename = getURLFilename(url);
18
+ return !!filename && VIDEO_EXT.some((ext) => filename.endsWith(ext));
19
+ }
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
+ export function isAudioURL(url) {
26
+ url = convertToUrl(url);
27
+ const filename = getURLFilename(url);
28
+ return !!filename && AUDIO_EXT.some((ext) => filename.endsWith(ext));
29
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from "./event-store/index.js";
2
2
  export * from "./query-store/index.js";
3
- export * as helpers from "./helpers/index.js";
3
+ export * as Helpers from "./helpers/index.js";
4
4
  export * as Queries from "./queries/index.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from "./event-store/index.js";
2
2
  export * from "./query-store/index.js";
3
- export * as helpers from "./helpers/index.js";
3
+ export * as Helpers from "./helpers/index.js";
4
4
  export * as Queries from "./queries/index.js";
@@ -0,0 +1,2 @@
1
+ import Observable from "zen-observable";
2
+ export declare function getValue<T>(observable: Observable<T>): T | Promise<T>;
@@ -0,0 +1,11 @@
1
+ import { isStateful } from "./stateful.js";
2
+ export function getValue(observable) {
3
+ if (isStateful(observable) && observable.value !== undefined)
4
+ return observable.value;
5
+ return new Promise((res) => {
6
+ const sub = observable.subscribe((v) => {
7
+ res(v);
8
+ sub.unsubscribe();
9
+ });
10
+ });
11
+ }
@@ -1,2 +1,3 @@
1
1
  export * from "./stateful.js";
2
2
  export * from "./throttle.js";
3
+ export * from "./getValue.js";
@@ -1,2 +1,3 @@
1
1
  export * from "./stateful.js";
2
2
  export * from "./throttle.js";
3
+ export * from "./getValue.js";
@@ -2,5 +2,4 @@ export * from "./simple.js";
2
2
  export * from "./profile.js";
3
3
  export * from "./mailboxes.js";
4
4
  export * from "./reactions.js";
5
- export * from "./mute.js";
6
5
  export * from "./thread.js";
@@ -2,5 +2,4 @@ export * from "./simple.js";
2
2
  export * from "./profile.js";
3
3
  export * from "./mailboxes.js";
4
4
  export * from "./reactions.js";
5
- export * from "./mute.js";
6
5
  export * from "./thread.js";
@@ -2,9 +2,15 @@ import { Filter, NostrEvent } from "nostr-tools";
2
2
  import { Query } from "../query-store/index.js";
3
3
  /** Creates a Query that returns a single event or undefined */
4
4
  export declare function SingleEventQuery(uid: string): Query<NostrEvent | undefined>;
5
+ /** Creates a Query that returns a multiple events in a map */
6
+ export declare function MultipleEventsQuery(uids: string[]): Query<Map<string, NostrEvent>>;
5
7
  /** Creates a Query returning the latest version of a replaceable event */
6
8
  export declare function ReplaceableQuery(kind: number, pubkey: string, d?: string): Query<NostrEvent | undefined>;
7
9
  /** Creates a Query that returns an array of sorted events matching the filters */
8
10
  export declare function TimelineQuery(filters: Filter | Filter[]): Query<NostrEvent[]>;
9
11
  /** Creates a Query that returns a directory of events by their UID */
10
- export declare function ReplaceableSetQuery(filters: Filter | Filter[]): Query<Record<string, NostrEvent>>;
12
+ export declare function ReplaceableSetQuery(pointers: {
13
+ kind: number;
14
+ pubkey: string;
15
+ identifier?: string;
16
+ }[]): Query<Map<string, NostrEvent>>;
@@ -1,5 +1,5 @@
1
1
  import stringify from "json-stringify-deterministic";
2
- import { getEventUID, getReplaceableUID } from "../helpers/event.js";
2
+ import { getReplaceableUID } from "../helpers/event.js";
3
3
  /** Creates a Query that returns a single event or undefined */
4
4
  export function SingleEventQuery(uid) {
5
5
  return {
@@ -7,6 +7,13 @@ export function SingleEventQuery(uid) {
7
7
  run: (events) => events.event(uid),
8
8
  };
9
9
  }
10
+ /** Creates a Query that returns a multiple events in a map */
11
+ export function MultipleEventsQuery(uids) {
12
+ return {
13
+ key: uids.join(","),
14
+ run: (events) => events.events(uids),
15
+ };
16
+ }
10
17
  /** Creates a Query returning the latest version of a replaceable event */
11
18
  export function ReplaceableQuery(kind, pubkey, d) {
12
19
  return {
@@ -22,11 +29,10 @@ export function TimelineQuery(filters) {
22
29
  };
23
30
  }
24
31
  /** Creates a Query that returns a directory of events by their UID */
25
- export function ReplaceableSetQuery(filters) {
32
+ export function ReplaceableSetQuery(pointers) {
33
+ const cords = pointers.map((pointer) => getReplaceableUID(pointer.kind, pointer.pubkey, pointer.identifier));
26
34
  return {
27
- key: stringify(filters),
28
- run: (events) => events
29
- .timeline(Array.isArray(filters) ? filters : [filters])
30
- .map((events) => events.reduce((dir, event) => ({ ...dir, [getEventUID(event)]: event }), {})),
35
+ key: stringify(pointers),
36
+ run: (events) => events.events(cords),
31
37
  };
32
38
  }
@@ -1,7 +1,8 @@
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 { LRU } from "../utils/lru.js";
4
+ import { StatefulObservable } from "../observable/stateful.js";
5
+ import { LRU } from "../helpers/lru.js";
5
6
  import * as Queries from "../queries/index.js";
6
7
  import { AddressPointer, EventPointer } from "nostr-tools/nip19";
7
8
  export type Query<T extends unknown> = {
@@ -13,7 +14,7 @@ export declare class QueryStore {
13
14
  static Queries: typeof Queries;
14
15
  store: EventStore;
15
16
  constructor(store: EventStore);
16
- queries: LRU<Observable<any>>;
17
+ queries: LRU<StatefulObservable<any>>;
17
18
  /** Creates a cached query */
18
19
  runQuery<T extends unknown, Args extends Array<any>>(queryConstructor: (...args: Args) => {
19
20
  key: string;
@@ -21,10 +22,16 @@ export declare class QueryStore {
21
22
  }): (...args: Args) => Observable<T>;
22
23
  /** Returns a single event */
23
24
  event(id: string): Observable<import("nostr-tools").Event | undefined>;
25
+ /** Returns a single event */
26
+ events(ids: string[]): Observable<Map<string, import("nostr-tools").Event>>;
24
27
  /** Returns the latest version of a replaceable event */
25
28
  replaceable(kind: number, pubkey: string, d?: string): Observable<import("nostr-tools").Event | undefined>;
26
29
  /** Returns a directory of events by their UID */
27
- replaceableSet(filters: Filter | Filter[]): Observable<Record<string, import("nostr-tools").Event>>;
30
+ replaceableSet(pointers: {
31
+ kind: number;
32
+ pubkey: string;
33
+ identifier?: string;
34
+ }[]): Observable<Map<string, import("nostr-tools").Event>>;
28
35
  /** Returns an array of events that match the filter */
29
36
  timeline(filters: Filter | Filter[]): Observable<import("nostr-tools").Event[]>;
30
37
  /** Returns the parsed profile (0) for a pubkey */
@@ -36,13 +43,6 @@ export declare class QueryStore {
36
43
  inboxes: Set<string>;
37
44
  outboxes: Set<string>;
38
45
  } | undefined>;
39
- /** Returns the parsed mute list for the pubkey */
40
- mute(pubkey: string): Observable<{
41
- words: Set<string>;
42
- pubkeys: Set<string>;
43
- threads: Set<string>;
44
- hashtags: Set<string>;
45
- } | undefined>;
46
46
  thread(root: string | EventPointer | AddressPointer): Observable<Queries.Thread>;
47
47
  }
48
48
  export { Queries };
@@ -1,5 +1,5 @@
1
1
  import { stateful } from "../observable/stateful.js";
2
- import { LRU } from "../utils/lru.js";
2
+ import { LRU } from "../helpers/lru.js";
3
3
  import * as Queries from "../queries/index.js";
4
4
  export class QueryStore {
5
5
  static Queries = Queries;
@@ -25,13 +25,17 @@ export class QueryStore {
25
25
  event(id) {
26
26
  return this.runQuery(Queries.SingleEventQuery)(id);
27
27
  }
28
+ /** Returns a single event */
29
+ events(ids) {
30
+ return this.runQuery(Queries.MultipleEventsQuery)(ids);
31
+ }
28
32
  /** Returns the latest version of a replaceable event */
29
33
  replaceable(kind, pubkey, d) {
30
34
  return this.runQuery(Queries.ReplaceableQuery)(kind, pubkey, d);
31
35
  }
32
36
  /** Returns a directory of events by their UID */
33
- replaceableSet(filters) {
34
- return this.runQuery(Queries.ReplaceableSetQuery)(filters);
37
+ replaceableSet(pointers) {
38
+ return this.runQuery(Queries.ReplaceableSetQuery)(pointers);
35
39
  }
36
40
  /** Returns an array of events that match the filter */
37
41
  timeline(filters) {
@@ -49,10 +53,6 @@ export class QueryStore {
49
53
  mailboxes(pubkey) {
50
54
  return this.runQuery(Queries.MailboxesQuery)(pubkey);
51
55
  }
52
- /** Returns the parsed mute list for the pubkey */
53
- mute(pubkey) {
54
- return this.runQuery(Queries.UserMuteQuery)(pubkey);
55
- }
56
56
  thread(root) {
57
57
  return this.runQuery(Queries.ThreadQuery)(root);
58
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-core",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,10 +22,18 @@
22
22
  "import": "./dist/helpers/index.js",
23
23
  "types": "./dist/helpers/index.d.ts"
24
24
  },
25
+ "./helpers/*": {
26
+ "import": "./dist/helpers/*.js",
27
+ "types": "./dist/helpers/*.d.ts"
28
+ },
25
29
  "./queries": {
26
30
  "import": "./dist/queries/index.js",
27
31
  "types": "./dist/queries/index.d.ts"
28
32
  },
33
+ "./queries/*": {
34
+ "import": "./dist/queries/*.js",
35
+ "types": "./dist/queries/*.d.ts"
36
+ },
29
37
  "./observable": {
30
38
  "import": "./dist/observable/index.js",
31
39
  "types": "./dist/observable/index.d.ts"
@@ -1,21 +0,0 @@
1
- import { NostrEvent } from "nostr-tools";
2
- export declare const MutePubkeysSymbol: unique symbol;
3
- export declare const MuteThreadsSymbol: unique symbol;
4
- export declare const MuteHashtagsSymbol: unique symbol;
5
- export declare const MuteWordsSymbol: unique symbol;
6
- declare module "nostr-tools" {
7
- interface Event {
8
- [MutePubkeysSymbol]?: Set<string>;
9
- [MuteThreadsSymbol]?: Set<string>;
10
- [MuteHashtagsSymbol]?: Set<string>;
11
- [MuteWordsSymbol]?: Set<string>;
12
- }
13
- }
14
- /** Returns a set of muted pubkeys */
15
- export declare function getMutedPubkeys(mute: NostrEvent): Set<string>;
16
- /** Returns a set of muted threads */
17
- export declare function getMutedThreads(mute: NostrEvent): Set<string>;
18
- /** Returns a set of muted words ( lowercase ) */
19
- export declare function getMutedWords(mute: NostrEvent): Set<string>;
20
- /** Returns a set of muted hashtags ( lowercase ) */
21
- export declare function getMutedHashtags(mute: NostrEvent): Set<string>;
@@ -1,52 +0,0 @@
1
- export const MutePubkeysSymbol = Symbol.for("mute-pubkeys");
2
- export const MuteThreadsSymbol = Symbol.for("mute-threads");
3
- export const MuteHashtagsSymbol = Symbol.for("mute-hashtags");
4
- export const MuteWordsSymbol = Symbol.for("mute-words");
5
- /** Returns a set of muted pubkeys */
6
- export function getMutedPubkeys(mute) {
7
- let pubkeys = mute[MutePubkeysSymbol];
8
- if (!pubkeys) {
9
- pubkeys = mute[MutePubkeysSymbol] = new Set();
10
- for (const tag of mute.tags) {
11
- if (tag[0] === "p" && tag[1])
12
- pubkeys.add(tag[1]);
13
- }
14
- }
15
- return pubkeys;
16
- }
17
- /** Returns a set of muted threads */
18
- export function getMutedThreads(mute) {
19
- let threads = mute[MuteThreadsSymbol];
20
- if (!threads) {
21
- threads = mute[MuteThreadsSymbol] = new Set();
22
- for (const tag of mute.tags) {
23
- if (tag[0] === "e" && tag[1])
24
- threads.add(tag[1]);
25
- }
26
- }
27
- return threads;
28
- }
29
- /** Returns a set of muted words ( lowercase ) */
30
- export function getMutedWords(mute) {
31
- let words = mute[MuteWordsSymbol];
32
- if (!words) {
33
- words = mute[MuteWordsSymbol] = new Set();
34
- for (const tag of mute.tags) {
35
- if (tag[0] === "word" && tag[1])
36
- words.add(tag[1].toLocaleLowerCase());
37
- }
38
- }
39
- return words;
40
- }
41
- /** Returns a set of muted hashtags ( lowercase ) */
42
- export function getMutedHashtags(mute) {
43
- let hashtags = mute[MuteHashtagsSymbol];
44
- if (!hashtags) {
45
- hashtags = mute[MuteHashtagsSymbol] = new Set();
46
- for (const tag of mute.tags) {
47
- if (tag[0] === "t" && tag[1])
48
- hashtags.add(tag[1].toLocaleLowerCase());
49
- }
50
- }
51
- return hashtags;
52
- }
@@ -1,7 +0,0 @@
1
- import { Query } from "../query-store/index.js";
2
- export declare function UserMuteQuery(pubkey: string): Query<{
3
- words: Set<string>;
4
- pubkeys: Set<string>;
5
- threads: Set<string>;
6
- hashtags: Set<string>;
7
- } | undefined>;
@@ -1,16 +0,0 @@
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
- }
File without changes
File without changes