applesauce-core 0.6.0 → 0.8.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/event-store/database.d.ts +7 -42
- package/dist/event-store/database.js +19 -13
- package/dist/event-store/event-store.d.ts +1 -4
- package/dist/event-store/event-store.js +1 -10
- package/dist/helpers/emoji.d.ts +2 -0
- package/dist/helpers/emoji.js +4 -0
- package/dist/helpers/event.d.ts +9 -1
- package/dist/helpers/event.js +14 -1
- package/dist/helpers/filter.d.ts +4 -0
- package/dist/helpers/filter.js +9 -0
- package/dist/helpers/hashtag.d.ts +2 -0
- package/dist/helpers/hashtag.js +7 -0
- package/dist/helpers/index.d.ts +4 -0
- package/dist/helpers/index.js +4 -0
- package/dist/helpers/mailboxes.d.ts +4 -4
- package/dist/helpers/mailboxes.js +6 -6
- package/dist/helpers/string.d.ts +2 -0
- package/dist/helpers/string.js +3 -0
- package/dist/helpers/url.d.ts +11 -0
- package/dist/helpers/url.js +29 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/observable/getValue.d.ts +2 -0
- package/dist/observable/getValue.js +11 -0
- package/dist/observable/index.d.ts +2 -2
- package/dist/observable/index.js +2 -2
- package/dist/observable/share-latest-value.d.ts +8 -0
- package/dist/observable/share-latest-value.js +21 -0
- package/dist/queries/mailboxes.d.ts +2 -2
- package/dist/queries/mailboxes.js +3 -2
- package/dist/queries/profile.js +2 -1
- package/dist/queries/thread.js +3 -2
- package/dist/query-store/index.d.ts +5 -5
- package/dist/query-store/index.js +3 -3
- package/package.json +2 -5
- package/dist/observable/stateful.d.ts +0 -10
- package/dist/observable/stateful.js +0 -60
- package/dist/observable/throttle.d.ts +0 -3
- package/dist/observable/throttle.js +0 -23
- /package/dist/{utils → helpers}/lru.d.ts +0 -0
- /package/dist/{utils → helpers}/lru.js +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/// <reference types="debug" />
|
|
2
|
-
/// <reference types="zen-observable" />
|
|
3
2
|
import { Filter, NostrEvent } from "nostr-tools";
|
|
4
|
-
import {
|
|
3
|
+
import { Subject } from "rxjs";
|
|
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>>;
|
|
@@ -14,48 +14,13 @@ export declare class Database {
|
|
|
14
14
|
protected created_at: NostrEvent[];
|
|
15
15
|
/** LRU cache of last events touched */
|
|
16
16
|
events: LRU<import("nostr-tools").Event>;
|
|
17
|
-
private insertedSignal;
|
|
18
|
-
private updatedSignal;
|
|
19
|
-
private deletedSignal;
|
|
20
17
|
/** A stream of events inserted into the database */
|
|
21
|
-
inserted:
|
|
22
|
-
subscribe(observer: ZenObservable.Observer<import("nostr-tools").Event>): ZenObservable.Subscription;
|
|
23
|
-
subscribe(onNext: (value: import("nostr-tools").Event) => void, onError?: ((error: any) => void) | undefined, onComplete?: (() => void) | undefined): ZenObservable.Subscription;
|
|
24
|
-
forEach(callback: (value: import("nostr-tools").Event) => void): Promise<void>;
|
|
25
|
-
map<R>(callback: (value: import("nostr-tools").Event) => R): import("zen-observable")<R>;
|
|
26
|
-
filter<S extends import("nostr-tools").Event>(callback: (value: import("nostr-tools").Event) => value is S): import("zen-observable")<S>;
|
|
27
|
-
filter(callback: (value: import("nostr-tools").Event) => boolean): import("zen-observable")<import("nostr-tools").Event>;
|
|
28
|
-
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>;
|
|
29
|
-
reduce<R_1>(callback: (previousValue: R_1, currentValue: import("nostr-tools").Event) => R_1, initialValue?: R_1 | undefined): import("zen-observable")<R_1>;
|
|
30
|
-
flatMap<R_2>(callback: (value: import("nostr-tools").Event) => ZenObservable.ObservableLike<R_2>): import("zen-observable")<R_2>;
|
|
31
|
-
concat<R_3>(...observable: import("zen-observable")<R_3>[]): import("zen-observable")<R_3>;
|
|
32
|
-
};
|
|
18
|
+
inserted: Subject<import("nostr-tools").Event>;
|
|
33
19
|
/** 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
|
-
};
|
|
20
|
+
updated: Subject<import("nostr-tools").Event>;
|
|
46
21
|
/** A stream of events removed of the database */
|
|
47
|
-
deleted:
|
|
48
|
-
|
|
49
|
-
subscribe(onNext: (value: import("nostr-tools").Event) => void, onError?: ((error: any) => void) | undefined, onComplete?: (() => void) | undefined): ZenObservable.Subscription;
|
|
50
|
-
forEach(callback: (value: import("nostr-tools").Event) => void): Promise<void>;
|
|
51
|
-
map<R>(callback: (value: import("nostr-tools").Event) => R): import("zen-observable")<R>;
|
|
52
|
-
filter<S extends import("nostr-tools").Event>(callback: (value: import("nostr-tools").Event) => value is S): import("zen-observable")<S>;
|
|
53
|
-
filter(callback: (value: import("nostr-tools").Event) => boolean): import("zen-observable")<import("nostr-tools").Event>;
|
|
54
|
-
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>;
|
|
55
|
-
reduce<R_1>(callback: (previousValue: R_1, currentValue: import("nostr-tools").Event) => R_1, initialValue?: R_1 | undefined): import("zen-observable")<R_1>;
|
|
56
|
-
flatMap<R_2>(callback: (value: import("nostr-tools").Event) => ZenObservable.ObservableLike<R_2>): import("zen-observable")<R_2>;
|
|
57
|
-
concat<R_3>(...observable: import("zen-observable")<R_3>[]): import("zen-observable")<R_3>;
|
|
58
|
-
};
|
|
22
|
+
deleted: Subject<import("nostr-tools").Event>;
|
|
23
|
+
get size(): number;
|
|
59
24
|
protected claims: WeakMap<import("nostr-tools").Event, any>;
|
|
60
25
|
/** Index helper methods */
|
|
61
26
|
protected getKindIndex(kind: number): Set<import("nostr-tools").Event>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { binarySearch, insertEventIntoDescendingList } from "nostr-tools/utils";
|
|
2
|
-
import
|
|
3
|
-
import { getEventUID, getIndexableTags, getReplaceableUID } from "../helpers/event.js";
|
|
2
|
+
import { Subject } from "rxjs";
|
|
3
|
+
import { FromCacheSymbol, 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 "../
|
|
6
|
+
import { LRU } from "../helpers/lru.js";
|
|
7
7
|
/**
|
|
8
8
|
* An in-memory database for nostr events
|
|
9
9
|
*/
|
|
@@ -16,15 +16,15 @@ export class Database {
|
|
|
16
16
|
created_at = [];
|
|
17
17
|
/** LRU cache of last events touched */
|
|
18
18
|
events = new LRU();
|
|
19
|
-
insertedSignal = new PushStream();
|
|
20
|
-
updatedSignal = new PushStream();
|
|
21
|
-
deletedSignal = new PushStream();
|
|
22
19
|
/** A stream of events inserted into the database */
|
|
23
|
-
inserted =
|
|
20
|
+
inserted = new Subject();
|
|
24
21
|
/** A stream of events that have been updated */
|
|
25
|
-
updated =
|
|
22
|
+
updated = new Subject();
|
|
26
23
|
/** A stream of events removed of the database */
|
|
27
|
-
deleted =
|
|
24
|
+
deleted = new Subject();
|
|
25
|
+
get size() {
|
|
26
|
+
return this.events.size;
|
|
27
|
+
}
|
|
28
28
|
claims = new WeakMap();
|
|
29
29
|
/** Index helper methods */
|
|
30
30
|
getKindIndex(kind) {
|
|
@@ -76,8 +76,14 @@ export class Database {
|
|
|
76
76
|
addEvent(event) {
|
|
77
77
|
const uid = getEventUID(event);
|
|
78
78
|
const current = this.events.get(uid);
|
|
79
|
-
if (current && event.created_at <= current.created_at)
|
|
79
|
+
if (current && event.created_at <= current.created_at) {
|
|
80
|
+
// if this is a duplicate event, transfer some import symbols
|
|
81
|
+
if (current.id === event.id) {
|
|
82
|
+
if (event[FromCacheSymbol])
|
|
83
|
+
current[FromCacheSymbol] = event[FromCacheSymbol];
|
|
84
|
+
}
|
|
80
85
|
return current;
|
|
86
|
+
}
|
|
81
87
|
this.events.set(uid, event);
|
|
82
88
|
this.getKindIndex(event.kind).add(event);
|
|
83
89
|
this.getAuthorsIndex(event.pubkey).add(event);
|
|
@@ -87,13 +93,13 @@ export class Database {
|
|
|
87
93
|
}
|
|
88
94
|
}
|
|
89
95
|
insertEventIntoDescendingList(this.created_at, event);
|
|
90
|
-
this.
|
|
96
|
+
this.inserted.next(event);
|
|
91
97
|
return event;
|
|
92
98
|
}
|
|
93
99
|
/** Inserts and event into the database and notifies all subscriptions that the event has updated */
|
|
94
100
|
updateEvent(event) {
|
|
95
101
|
const inserted = this.addEvent(event);
|
|
96
|
-
this.
|
|
102
|
+
this.updated.next(inserted);
|
|
97
103
|
return inserted;
|
|
98
104
|
}
|
|
99
105
|
/** Deletes an event from the database and notifies all subscriptions */
|
|
@@ -116,7 +122,7 @@ export class Database {
|
|
|
116
122
|
const i = this.created_at.indexOf(event);
|
|
117
123
|
this.created_at.splice(i, 1);
|
|
118
124
|
this.events.delete(uid);
|
|
119
|
-
this.
|
|
125
|
+
this.deleted.next(event);
|
|
120
126
|
return true;
|
|
121
127
|
}
|
|
122
128
|
/** Sets the claim on the event and touches it */
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
-
import Observable from "
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
3
|
import { Database } from "./database.js";
|
|
4
4
|
export declare class EventStore {
|
|
5
5
|
database: Database;
|
|
6
|
-
private singles;
|
|
7
|
-
private streams;
|
|
8
|
-
private timelines;
|
|
9
6
|
constructor();
|
|
10
7
|
/** Adds an event to the database */
|
|
11
8
|
add(event: NostrEvent, fromRelay?: string): import("nostr-tools").Event;
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { insertEventIntoDescendingList } from "nostr-tools/utils";
|
|
2
|
-
import Observable from "
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
3
|
import { Database } from "./database.js";
|
|
4
4
|
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
8
|
database;
|
|
9
|
-
singles = new Map();
|
|
10
|
-
streams = new Map();
|
|
11
|
-
timelines = new Map();
|
|
12
9
|
constructor() {
|
|
13
10
|
this.database = new Database();
|
|
14
11
|
}
|
|
@@ -71,12 +68,10 @@ export class EventStore {
|
|
|
71
68
|
observer.next(undefined);
|
|
72
69
|
}
|
|
73
70
|
});
|
|
74
|
-
this.singles.set(observer, uid);
|
|
75
71
|
return () => {
|
|
76
72
|
inserted.unsubscribe();
|
|
77
73
|
deleted.unsubscribe();
|
|
78
74
|
updated.unsubscribe();
|
|
79
|
-
this.singles.delete(observer);
|
|
80
75
|
if (current)
|
|
81
76
|
this.database.removeClaim(current, observer);
|
|
82
77
|
};
|
|
@@ -160,10 +155,8 @@ export class EventStore {
|
|
|
160
155
|
claimed.add(event);
|
|
161
156
|
}
|
|
162
157
|
});
|
|
163
|
-
this.streams.set(observer, filters);
|
|
164
158
|
return () => {
|
|
165
159
|
sub.unsubscribe();
|
|
166
|
-
this.streams.delete(observer);
|
|
167
160
|
// remove all claims
|
|
168
161
|
for (const event of claimed)
|
|
169
162
|
this.database.removeClaim(event, observer);
|
|
@@ -228,9 +221,7 @@ export class EventStore {
|
|
|
228
221
|
this.database.removeClaim(current, observer);
|
|
229
222
|
}
|
|
230
223
|
});
|
|
231
|
-
this.timelines.set(observer, filters);
|
|
232
224
|
return () => {
|
|
233
|
-
this.timelines.delete(observer);
|
|
234
225
|
inserted.unsubscribe();
|
|
235
226
|
deleted.unsubscribe();
|
|
236
227
|
updated.unsubscribe();
|
package/dist/helpers/event.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { NostrEvent } from "nostr-tools";
|
|
1
|
+
import { NostrEvent, VerifiedEvent } from "nostr-tools";
|
|
2
2
|
export declare const EventUIDSymbol: unique symbol;
|
|
3
3
|
export declare const EventIndexableTagsSymbol: unique symbol;
|
|
4
|
+
export declare const FromCacheSymbol: unique symbol;
|
|
4
5
|
declare module "nostr-tools" {
|
|
5
6
|
interface Event {
|
|
6
7
|
[EventUIDSymbol]?: string;
|
|
7
8
|
[EventIndexableTagsSymbol]?: Set<string>;
|
|
9
|
+
[FromCacheSymbol]?: boolean;
|
|
8
10
|
}
|
|
9
11
|
}
|
|
10
12
|
/**
|
|
@@ -24,3 +26,9 @@ export declare function getReplaceableUID(kind: number, pubkey: string, d?: stri
|
|
|
24
26
|
export declare function getIndexableTags(event: NostrEvent): Set<string>;
|
|
25
27
|
/** Returns the second index ( tag[1] ) of the first tag that matches the name */
|
|
26
28
|
export declare function getTagValue(event: NostrEvent, name: string): string | undefined;
|
|
29
|
+
/** Sets events verified flag without checking anything */
|
|
30
|
+
export declare function fakeVerifyEvent(event: NostrEvent): event is VerifiedEvent;
|
|
31
|
+
/** Marks an event as being from a cache */
|
|
32
|
+
export declare function markFromCache(event: NostrEvent): void;
|
|
33
|
+
/** Returns if an event was from a cache */
|
|
34
|
+
export declare function isFromCache(event: NostrEvent): boolean;
|
package/dist/helpers/event.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { kinds } from "nostr-tools";
|
|
1
|
+
import { kinds, verifiedSymbol } from "nostr-tools";
|
|
2
2
|
import { INDEXABLE_TAGS } from "../event-store/common.js";
|
|
3
3
|
export const EventUIDSymbol = Symbol.for("event-uid");
|
|
4
4
|
export const EventIndexableTagsSymbol = Symbol.for("indexable-tags");
|
|
5
|
+
export const FromCacheSymbol = Symbol.for("from-cache");
|
|
5
6
|
/**
|
|
6
7
|
* Returns if a kind is replaceable ( 10000 <= n < 20000 || n == 0 || n == 3 )
|
|
7
8
|
* or parameterized replaceable ( 30000 <= n < 40000 )
|
|
@@ -49,3 +50,15 @@ export function getIndexableTags(event) {
|
|
|
49
50
|
export function getTagValue(event, name) {
|
|
50
51
|
return event.tags.find((t) => t[0] === name)?.[1];
|
|
51
52
|
}
|
|
53
|
+
/** Sets events verified flag without checking anything */
|
|
54
|
+
export function fakeVerifyEvent(event) {
|
|
55
|
+
return (event[verifiedSymbol] = true);
|
|
56
|
+
}
|
|
57
|
+
/** Marks an event as being from a cache */
|
|
58
|
+
export function markFromCache(event) {
|
|
59
|
+
event[FromCacheSymbol] = true;
|
|
60
|
+
}
|
|
61
|
+
/** Returns if an event was from a cache */
|
|
62
|
+
export function isFromCache(event) {
|
|
63
|
+
return !!event[FromCacheSymbol];
|
|
64
|
+
}
|
package/dist/helpers/filter.d.ts
CHANGED
|
@@ -6,3 +6,7 @@ import { Filter, NostrEvent } from "nostr-tools";
|
|
|
6
6
|
export declare function matchFilter(filter: Filter, event: NostrEvent): boolean;
|
|
7
7
|
/** Copied from nostr-tools */
|
|
8
8
|
export declare function matchFilters(filters: Filter[], event: NostrEvent): boolean;
|
|
9
|
+
/** Stringify filters in a predictable way */
|
|
10
|
+
export declare function stringifyFilter(filter: Filter | Filter[]): string;
|
|
11
|
+
/** Check if two filters are equal */
|
|
12
|
+
export declare function isFilterEqual(a: Filter | Filter[], b: Filter | Filter[]): boolean;
|
package/dist/helpers/filter.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getIndexableTags } from "./event.js";
|
|
2
|
+
import stringify from "json-stringify-deterministic";
|
|
2
3
|
/**
|
|
3
4
|
* Copied from nostr-tools and modified to use getIndexableTags
|
|
4
5
|
* @see https://github.com/nbd-wtf/nostr-tools/blob/a61cde77eacc9518001f11d7f67f1a50ae05fd80/filter.ts
|
|
@@ -39,3 +40,11 @@ export function matchFilters(filters, event) {
|
|
|
39
40
|
}
|
|
40
41
|
return false;
|
|
41
42
|
}
|
|
43
|
+
/** Stringify filters in a predictable way */
|
|
44
|
+
export function stringifyFilter(filter) {
|
|
45
|
+
return stringify(filter);
|
|
46
|
+
}
|
|
47
|
+
/** Check if two filters are equal */
|
|
48
|
+
export function isFilterEqual(a, b) {
|
|
49
|
+
return stringifyFilter(a) === stringifyFilter(b);
|
|
50
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/helpers/index.d.ts
CHANGED
package/dist/helpers/index.js
CHANGED
|
@@ -3,15 +3,15 @@ export declare const MailboxesInboxesSymbol: unique symbol;
|
|
|
3
3
|
export declare const MailboxesOutboxesSymbol: unique symbol;
|
|
4
4
|
declare module "nostr-tools" {
|
|
5
5
|
interface Event {
|
|
6
|
-
[MailboxesInboxesSymbol]?:
|
|
7
|
-
[MailboxesOutboxesSymbol]?:
|
|
6
|
+
[MailboxesInboxesSymbol]?: string[];
|
|
7
|
+
[MailboxesOutboxesSymbol]?: string[];
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* Parses a 10002 event and stores the inboxes in the event using the {@link MailboxesInboxesSymbol} symbol
|
|
12
12
|
*/
|
|
13
|
-
export declare function getInboxes(event: NostrEvent):
|
|
13
|
+
export declare function getInboxes(event: NostrEvent): string[];
|
|
14
14
|
/**
|
|
15
15
|
* Parses a 10002 event and stores the outboxes in the event using the {@link MailboxesOutboxesSymbol} symbol
|
|
16
16
|
*/
|
|
17
|
-
export declare function getOutboxes(event: NostrEvent):
|
|
17
|
+
export declare function getOutboxes(event: NostrEvent): string[];
|
|
@@ -6,12 +6,12 @@ export const MailboxesOutboxesSymbol = Symbol.for("mailboxes-outboxes");
|
|
|
6
6
|
*/
|
|
7
7
|
export function getInboxes(event) {
|
|
8
8
|
if (!event[MailboxesInboxesSymbol]) {
|
|
9
|
-
const inboxes =
|
|
9
|
+
const inboxes = [];
|
|
10
10
|
for (const tag of event.tags) {
|
|
11
11
|
if (tag[0] === "r" && tag[1] && (tag[2] === "read" || tag[2] === undefined)) {
|
|
12
12
|
const url = safeRelayUrl(tag[1]);
|
|
13
|
-
if (url)
|
|
14
|
-
inboxes.
|
|
13
|
+
if (url && !inboxes.includes(url))
|
|
14
|
+
inboxes.push(url);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
event[MailboxesInboxesSymbol] = inboxes;
|
|
@@ -23,12 +23,12 @@ export function getInboxes(event) {
|
|
|
23
23
|
*/
|
|
24
24
|
export function getOutboxes(event) {
|
|
25
25
|
if (!event[MailboxesOutboxesSymbol]) {
|
|
26
|
-
const outboxes =
|
|
26
|
+
const outboxes = [];
|
|
27
27
|
for (const tag of event.tags) {
|
|
28
28
|
if (tag[0] === "r" && tag[1] && (tag[2] === "write" || tag[2] === undefined)) {
|
|
29
29
|
const url = safeRelayUrl(tag[1]);
|
|
30
|
-
if (url)
|
|
31
|
-
outboxes.
|
|
30
|
+
if (url && !outboxes.includes(url))
|
|
31
|
+
outboxes.push(url);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
event[MailboxesOutboxesSymbol] = outboxes;
|
package/dist/helpers/string.d.ts
CHANGED
package/dist/helpers/string.js
CHANGED
|
@@ -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
package/dist/index.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BehaviorSubject } from "rxjs";
|
|
2
|
+
export function getValue(observable) {
|
|
3
|
+
if (observable instanceof BehaviorSubject)
|
|
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,2 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./
|
|
1
|
+
export * from "./getValue.js";
|
|
2
|
+
export * from "./share-latest-value.js";
|
package/dist/observable/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./
|
|
1
|
+
export * from "./getValue.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
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { kinds } from "nostr-tools";
|
|
2
|
+
import { map } from "rxjs/operators";
|
|
2
3
|
import { getInboxes, getOutboxes } from "../helpers/mailboxes.js";
|
|
3
4
|
export function MailboxesQuery(pubkey) {
|
|
4
5
|
return {
|
|
5
6
|
key: pubkey,
|
|
6
|
-
run: (events) => events.replaceable(kinds.RelayList, pubkey).map((event) => event && {
|
|
7
|
+
run: (events) => events.replaceable(kinds.RelayList, pubkey).pipe(map((event) => event && {
|
|
7
8
|
inboxes: getInboxes(event),
|
|
8
9
|
outboxes: getOutboxes(event),
|
|
9
|
-
}),
|
|
10
|
+
})),
|
|
10
11
|
};
|
|
11
12
|
}
|
package/dist/queries/profile.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { kinds } from "nostr-tools";
|
|
2
|
+
import { map } from "rxjs/operators";
|
|
2
3
|
import { getProfileContent } from "../helpers/profile.js";
|
|
3
4
|
export function ProfileQuery(pubkey) {
|
|
4
5
|
return {
|
|
5
6
|
key: pubkey,
|
|
6
7
|
run: (events) => {
|
|
7
|
-
return events.replaceable(kinds.Metadata, pubkey).map((event) => event && getProfileContent(event));
|
|
8
|
+
return events.replaceable(kinds.Metadata, pubkey).pipe(map((event) => event && getProfileContent(event)));
|
|
8
9
|
},
|
|
9
10
|
};
|
|
10
11
|
}
|
package/dist/queries/thread.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { kinds } from "nostr-tools";
|
|
2
|
+
import { map } from "rxjs/operators";
|
|
2
3
|
import { getNip10References } from "../helpers/threading.js";
|
|
3
4
|
import { getCoordinateFromAddressPointer, isAddressPointer } from "../helpers/pointers.js";
|
|
4
5
|
import { getEventUID } from "../helpers/event.js";
|
|
@@ -31,7 +32,7 @@ export function ThreadQuery(root, opts) {
|
|
|
31
32
|
}
|
|
32
33
|
return {
|
|
33
34
|
key: `${rootUID}-${kinds.join(",")}`,
|
|
34
|
-
run: (events) => events.stream([rootFilter, replyFilter]).map((event) => {
|
|
35
|
+
run: (events) => events.stream([rootFilter, replyFilter]).pipe(map((event) => {
|
|
35
36
|
if (!items.has(getEventUID(event))) {
|
|
36
37
|
const refs = getNip10References(event);
|
|
37
38
|
const replies = parentReferences.get(getEventUID(event)) || new Set();
|
|
@@ -60,6 +61,6 @@ export function ThreadQuery(root, opts) {
|
|
|
60
61
|
items.set(getEventUID(event), item);
|
|
61
62
|
}
|
|
62
63
|
return { root: items.get(rootUID), all: items };
|
|
63
|
-
}),
|
|
64
|
+
})),
|
|
64
65
|
};
|
|
65
66
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import Observable from "
|
|
1
|
+
import { BehaviorSubject, Observable } from "rxjs";
|
|
2
2
|
import { Filter, NostrEvent } from "nostr-tools";
|
|
3
3
|
import { EventStore } from "../event-store/event-store.js";
|
|
4
|
-
import { LRU } from "../
|
|
4
|
+
import { LRU } from "../helpers/lru.js";
|
|
5
5
|
import * as Queries from "../queries/index.js";
|
|
6
6
|
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
|
|
7
7
|
export type Query<T extends unknown> = {
|
|
@@ -13,7 +13,7 @@ export declare class QueryStore {
|
|
|
13
13
|
static Queries: typeof Queries;
|
|
14
14
|
store: EventStore;
|
|
15
15
|
constructor(store: EventStore);
|
|
16
|
-
queries: LRU<Observable<any>>;
|
|
16
|
+
queries: LRU<Observable<any> | BehaviorSubject<any>>;
|
|
17
17
|
/** Creates a cached query */
|
|
18
18
|
runQuery<T extends unknown, Args extends Array<any>>(queryConstructor: (...args: Args) => {
|
|
19
19
|
key: string;
|
|
@@ -39,8 +39,8 @@ export declare class QueryStore {
|
|
|
39
39
|
reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
|
|
40
40
|
/** Returns the parsed relay list (10002) for the pubkey */
|
|
41
41
|
mailboxes(pubkey: string): Observable<{
|
|
42
|
-
inboxes:
|
|
43
|
-
outboxes:
|
|
42
|
+
inboxes: string[];
|
|
43
|
+
outboxes: string[];
|
|
44
44
|
} | undefined>;
|
|
45
45
|
thread(root: string | EventPointer | AddressPointer): Observable<Queries.Thread>;
|
|
46
46
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { LRU } from "../utils/lru.js";
|
|
1
|
+
import { LRU } from "../helpers/lru.js";
|
|
3
2
|
import * as Queries from "../queries/index.js";
|
|
3
|
+
import { shareLatestValue } from "../observable/share-latest-value.js";
|
|
4
4
|
export class QueryStore {
|
|
5
5
|
static Queries = Queries;
|
|
6
6
|
store;
|
|
@@ -14,7 +14,7 @@ export class QueryStore {
|
|
|
14
14
|
const query = queryConstructor(...args);
|
|
15
15
|
const key = `${queryConstructor.name}|${query.key}`;
|
|
16
16
|
if (!this.queries.has(key)) {
|
|
17
|
-
const observable =
|
|
17
|
+
const observable = query.run(this.store, this).pipe(shareLatestValue());
|
|
18
18
|
this.queries.set(key, observable);
|
|
19
19
|
return observable;
|
|
20
20
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -56,15 +56,12 @@
|
|
|
56
56
|
"json-stringify-deterministic": "^1.0.12",
|
|
57
57
|
"nanoid": "^5.0.7",
|
|
58
58
|
"nostr-tools": "^2.7.2",
|
|
59
|
-
"
|
|
60
|
-
"zen-observable": "^0.10.0"
|
|
59
|
+
"rxjs": "^7.8.1"
|
|
61
60
|
},
|
|
62
61
|
"devDependencies": {
|
|
63
|
-
"@types/zen-push": "^0.1.4",
|
|
64
62
|
"@jest/globals": "^29.7.0",
|
|
65
63
|
"@types/debug": "^4.1.12",
|
|
66
64
|
"@types/jest": "^29.5.13",
|
|
67
|
-
"@types/zen-observable": "^0.8.7",
|
|
68
65
|
"jest": "^29.7.0",
|
|
69
66
|
"jest-extended": "^4.0.2"
|
|
70
67
|
},
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import Observable from "zen-observable";
|
|
2
|
-
export type StatefulObservable<T> = Observable<T> & {
|
|
3
|
-
_stateful?: true;
|
|
4
|
-
value?: T;
|
|
5
|
-
error?: Error;
|
|
6
|
-
complete?: boolean;
|
|
7
|
-
};
|
|
8
|
-
/** Wraps an {@link Observable} and makes it stateful */
|
|
9
|
-
export declare function stateful<T extends unknown>(observable: Observable<T>, cleanup?: boolean): StatefulObservable<T>;
|
|
10
|
-
export declare function isStateful<T extends unknown>(observable: Observable<T> | StatefulObservable<T>): observable is StatefulObservable<T>;
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import Observable from "zen-observable";
|
|
2
|
-
/** Wraps an {@link Observable} and makes it stateful */
|
|
3
|
-
export function stateful(observable, cleanup = false) {
|
|
4
|
-
let subscription = undefined;
|
|
5
|
-
let observers = [];
|
|
6
|
-
const self = new Observable((observer) => {
|
|
7
|
-
// add observer to list
|
|
8
|
-
observers.push(observer);
|
|
9
|
-
// pass any cached values
|
|
10
|
-
if (self.value)
|
|
11
|
-
observer.next(self.value);
|
|
12
|
-
if (self.error)
|
|
13
|
-
observer.error(self.error);
|
|
14
|
-
if (self.complete)
|
|
15
|
-
observer.complete();
|
|
16
|
-
// subscribe if not already
|
|
17
|
-
if (!subscription) {
|
|
18
|
-
subscription = observable.subscribe({
|
|
19
|
-
next: (v) => {
|
|
20
|
-
self.value = v;
|
|
21
|
-
for (const observer of observers)
|
|
22
|
-
observer.next(v);
|
|
23
|
-
},
|
|
24
|
-
error: (err) => {
|
|
25
|
-
self.error = err;
|
|
26
|
-
for (const observer of observers)
|
|
27
|
-
observer.error(err);
|
|
28
|
-
},
|
|
29
|
-
complete: () => {
|
|
30
|
-
self.complete = true;
|
|
31
|
-
for (const observer of observers)
|
|
32
|
-
observer.complete();
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
return () => {
|
|
37
|
-
let i = observers.indexOf(observer);
|
|
38
|
-
if (i !== -1) {
|
|
39
|
-
// remove observer from list
|
|
40
|
-
observers.splice(i, 1);
|
|
41
|
-
if (subscription && observers.length === 0) {
|
|
42
|
-
subscription.unsubscribe();
|
|
43
|
-
subscription = undefined;
|
|
44
|
-
// reset cached values
|
|
45
|
-
if (cleanup) {
|
|
46
|
-
delete self.value;
|
|
47
|
-
delete self.error;
|
|
48
|
-
delete self.complete;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
self._stateful = true;
|
|
55
|
-
return self;
|
|
56
|
-
}
|
|
57
|
-
export function isStateful(observable) {
|
|
58
|
-
// @ts-expect-error
|
|
59
|
-
return observable._stateful;
|
|
60
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import Observable from "zen-observable";
|
|
2
|
-
/** Throttles an {@link Observable} */
|
|
3
|
-
export function throttle(source, interval) {
|
|
4
|
-
return new Observable((observer) => {
|
|
5
|
-
let lastEmissionTime = 0;
|
|
6
|
-
let subscription = source.subscribe({
|
|
7
|
-
next(value) {
|
|
8
|
-
const currentTime = Date.now();
|
|
9
|
-
if (currentTime - lastEmissionTime >= interval) {
|
|
10
|
-
lastEmissionTime = currentTime;
|
|
11
|
-
observer.next(value);
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
error(err) {
|
|
15
|
-
observer.error(err);
|
|
16
|
-
},
|
|
17
|
-
complete() {
|
|
18
|
-
observer.complete();
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
return () => subscription.unsubscribe();
|
|
22
|
-
});
|
|
23
|
-
}
|
|
File without changes
|
|
File without changes
|