New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

applesauce-core

Package Overview
Dependencies
Maintainers
0
Versions
130
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

applesauce-core - npm Package Compare versions

Comparing version 0.9.0 to 0.10.0

dist/helpers/comment.d.ts

30

dist/event-store/database.d.ts

@@ -6,2 +6,3 @@ import { Filter, NostrEvent } from "nostr-tools";

* An in-memory database for nostr events
* NOTE: does not handle replaceable events
*/

@@ -17,2 +18,3 @@ export declare class Database {

events: LRU<import("nostr-tools").Event>;
protected replaceable: Map<string, import("nostr-tools").Event[]>;
/** A stream of events inserted into the database */

@@ -32,14 +34,16 @@ inserted: Subject<import("nostr-tools").Event>;

touch(event: NostrEvent): void;
hasEvent(uid: string): import("nostr-tools").Event | undefined;
getEvent(uid: string): import("nostr-tools").Event | undefined;
/** Checks if the database contains an event without touching it */
hasEvent(id: string): boolean;
/** Gets a single event based on id */
getEvent(id: string): NostrEvent | undefined;
/** Checks if the database contains a replaceable event without touching it */
hasReplaceable(kind: number, pubkey: string, d?: string): boolean;
/** Gets a replaceable event and touches it */
getReplaceable(kind: number, pubkey: string, d?: string): import("nostr-tools").Event | undefined;
/** Gets an array of replaceable events */
getReplaceable(kind: number, pubkey: string, d?: string): NostrEvent[] | undefined;
/** Inserts an event into the database and notifies all subscriptions */
addEvent(event: NostrEvent): import("nostr-tools").Event;
addEvent(event: NostrEvent): NostrEvent;
/** Inserts and event into the database and notifies all subscriptions that the event has updated */
updateEvent(event: NostrEvent): import("nostr-tools").Event;
updateEvent(event: NostrEvent): NostrEvent;
/** Deletes an event from the database and notifies all subscriptions */
deleteEvent(eventOrUID: string | NostrEvent): boolean;
deleteEvent(eventOrId: string | NostrEvent): boolean;
/** Sets the claim on the event and touches it */

@@ -53,12 +57,12 @@ claimEvent(event: NostrEvent, claim: any): void;

clearClaim(event: NostrEvent): void;
iterateAuthors(authors: Iterable<string>): Generator<import("nostr-tools").Event, void, unknown>;
iterateTag(tag: string, values: Iterable<string>): Generator<import("nostr-tools").Event, void, unknown>;
iterateKinds(kinds: Iterable<number>): Generator<import("nostr-tools").Event, void, unknown>;
iterateTime(since: number | undefined, until: number | undefined): Generator<never, Set<import("nostr-tools").Event>, unknown>;
iterateIds(ids: Iterable<string>): Generator<import("nostr-tools").Event, void, unknown>;
iterateAuthors(authors: Iterable<string>): Generator<NostrEvent>;
iterateTag(tag: string, values: Iterable<string>): Generator<NostrEvent>;
iterateKinds(kinds: Iterable<number>): Generator<NostrEvent>;
iterateTime(since: number | undefined, until: number | undefined): Generator<NostrEvent>;
iterateIds(ids: Iterable<string>): Generator<NostrEvent>;
/** Returns all events that match the filter */
getEventsForFilter(filter: Filter): Set<NostrEvent>;
getForFilters(filters: Filter[]): Set<import("nostr-tools").Event>;
getForFilters(filters: Filter[]): Set<NostrEvent>;
/** Remove the oldest events that are not claimed */
prune(limit?: number): number;
}
import { binarySearch, insertEventIntoDescendingList } from "nostr-tools/utils";
import { Subject } from "rxjs";
import { FromCacheSymbol, getEventUID, getIndexableTags, getReplaceableUID } from "../helpers/event.js";
import { FromCacheSymbol, getEventUID, getIndexableTags, getReplaceableUID, isReplaceable } from "../helpers/event.js";
import { INDEXABLE_TAGS } from "./common.js";

@@ -9,2 +9,3 @@ import { logger } from "../logger.js";

* An in-memory database for nostr events
* NOTE: does not handle replaceable events
*/

@@ -20,2 +21,3 @@ export class Database {

events = new LRU();
replaceable = new Map();
/** A stream of events inserted into the database */

@@ -61,31 +63,32 @@ inserted = new Subject();

touch(event) {
this.events.set(getEventUID(event), event);
this.events.set(event.id, event);
}
hasEvent(uid) {
return this.events.get(uid);
/** Checks if the database contains an event without touching it */
hasEvent(id) {
return this.events.has(id);
}
getEvent(uid) {
return this.events.get(uid);
/** Gets a single event based on id */
getEvent(id) {
return this.events.get(id);
}
/** Checks if the database contains a replaceable event without touching it */
hasReplaceable(kind, pubkey, d) {
return this.events.has(getReplaceableUID(kind, pubkey, d));
const events = this.replaceable.get(getReplaceableUID(kind, pubkey, d));
return !!events && events.length > 0;
}
/** Gets a replaceable event and touches it */
/** Gets an array of replaceable events */
getReplaceable(kind, pubkey, d) {
return this.events.get(getReplaceableUID(kind, pubkey, d));
return this.replaceable.get(getReplaceableUID(kind, pubkey, d));
}
/** Inserts an event into the database and notifies all subscriptions */
addEvent(event) {
const uid = getEventUID(event);
const current = this.events.get(uid);
if (current && event.created_at <= current.created_at) {
const id = event.id;
const current = this.events.get(id);
if (current) {
// if this is a duplicate event, transfer some import symbols
if (current.id === event.id) {
if (event[FromCacheSymbol])
current[FromCacheSymbol] = event[FromCacheSymbol];
}
if (event[FromCacheSymbol])
current[FromCacheSymbol] = event[FromCacheSymbol];
return current;
}
this.events.set(uid, event);
this.events.set(id, event);
this.getKindIndex(event.kind).add(event);

@@ -98,3 +101,14 @@ this.getAuthorsIndex(event.pubkey).add(event);

}
// insert into time index
insertEventIntoDescendingList(this.created_at, event);
// insert into replaceable index
if (isReplaceable(event.kind)) {
const uid = getEventUID(event);
let array = this.replaceable.get(uid);
if (!this.replaceable.has(uid)) {
array = [];
this.replaceable.set(uid, array);
}
insertEventIntoDescendingList(array, event);
}
this.inserted.next(event);

@@ -110,9 +124,9 @@ return event;

/** Deletes an event from the database and notifies all subscriptions */
deleteEvent(eventOrUID) {
let event = typeof eventOrUID === "string" ? this.events.get(eventOrUID) : eventOrUID;
deleteEvent(eventOrId) {
let event = typeof eventOrId === "string" ? this.events.get(eventOrId) : eventOrId;
if (!event)
throw new Error("Missing event");
const uid = getEventUID(event);
const id = event.id;
// only remove events that are known
if (!this.events.has(uid))
if (!this.events.has(id))
return false;

@@ -129,3 +143,12 @@ this.getAuthorsIndex(event.pubkey).delete(event);

this.created_at.splice(i, 1);
this.events.delete(uid);
this.events.delete(id);
// remove from replaceable index
if (isReplaceable(event.kind)) {
const uid = getEventUID(event);
const array = this.replaceable.get(uid);
if (array && array.includes(event)) {
const idx = array.indexOf(event);
array.splice(idx, 1);
}
}
this.deleted.next(event);

@@ -188,23 +211,17 @@ return true;

? binarySearch(this.created_at, (mid) => {
if (mid.created_at === until)
return -1;
return mid.created_at - until;
})
: undefined;
if (start && start[1])
if (start)
untilIndex = start[0];
const end = since
? binarySearch(this.created_at, (mid) => {
if (mid.created_at === since)
return 1;
return since - mid.created_at;
return mid.created_at - since;
})
: undefined;
if (end && end[1])
if (end)
sinceIndex = end[0];
const events = new Set();
for (let i = untilIndex; i <= sinceIndex; i++) {
events.add(this.created_at[i]);
for (let i = untilIndex; i < sinceIndex; i++) {
yield this.created_at[i];
}
return events;
}

@@ -211,0 +228,0 @@ *iterateIds(ids) {

@@ -6,22 +6,45 @@ import { Filter, NostrEvent } from "nostr-tools";

database: Database;
/** Enable this to keep old versions of replaceable events */
keepOldVersions: boolean;
constructor();
/** Adds an event to the database */
add(event: NostrEvent, fromRelay?: string): import("nostr-tools").Event;
/** Adds an event to the database and update subscriptions */
add(event: NostrEvent, fromRelay?: string): NostrEvent;
/** Removes an event from the database and updates subscriptions */
remove(event: string | NostrEvent): boolean;
protected deletedIds: Set<string>;
protected deletedCoords: Map<string, number>;
protected handleDeleteEvent(deleteEvent: NostrEvent): void;
protected checkDeleted(event: NostrEvent): boolean;
/** Removes any event that is not being used by a subscription */
prune(max?: number): number;
/** Add an event to the store and notifies all subscribes it has updated */
update(event: NostrEvent): import("nostr-tools").Event;
getAll(filters: Filter[]): Set<import("nostr-tools").Event>;
hasEvent(uid: string): import("nostr-tools").Event | undefined;
getEvent(uid: string): import("nostr-tools").Event | undefined;
update(event: NostrEvent): NostrEvent;
getAll(filters: Filter[]): Set<NostrEvent>;
hasEvent(uid: string): boolean;
getEvent(uid: string): NostrEvent | undefined;
hasReplaceable(kind: number, pubkey: string, d?: string): boolean;
getReplaceable(kind: number, pubkey: string, d?: string): import("nostr-tools").Event | undefined;
/** Gets the latest version of a replaceable event */
getReplaceable(kind: number, pubkey: string, d?: string): NostrEvent | undefined;
/** Returns all versions of a replaceable event */
getReplaceableHistory(kind: number, pubkey: string, d?: string): NostrEvent[] | undefined;
/** Creates an observable that updates a single event */
event(uid: string): Observable<import("nostr-tools").Event | undefined>;
event(id: string): Observable<NostrEvent | undefined>;
/** Creates an observable that subscribes to multiple events */
events(uids: string[]): Observable<Map<string, import("nostr-tools").Event>>;
/** Creates an observable that updates a single replaceable event */
replaceable(kind: number, pubkey: string, d?: string): Observable<import("nostr-tools").Event | undefined>;
/** Creates an observable that streams all events that match the filter */
stream(filters: Filter[]): Observable<import("nostr-tools").Event>;
events(ids: string[]): Observable<Map<string, NostrEvent>>;
/** Creates an observable with the latest version of a replaceable event */
replaceable(kind: number, pubkey: string, d?: string): Observable<NostrEvent | undefined>;
/** Creates an observable with the latest versions of replaceable events */
replaceableSet(pointers: {
kind: number;
pubkey: string;
identifier?: string;
}[]): Observable<Map<string, NostrEvent>>;
/**
* Creates an observable that streams all events that match the filter
* @param filters
* @param [onlyNew=false] Only subscribe to new events
*/
stream(filters: Filter | Filter[], onlyNew?: boolean): Observable<NostrEvent>;
/** Creates an observable that updates with an array of sorted events */
timeline(filters: Filter[]): Observable<import("nostr-tools").Event[]>;
timeline(filters: Filter | Filter[], keepOldVersions?: boolean): Observable<NostrEvent[]>;
}

@@ -0,15 +1,39 @@

import { kinds } from "nostr-tools";
import { insertEventIntoDescendingList } from "nostr-tools/utils";
import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
import { Observable } from "rxjs";
import { Database } from "./database.js";
import { getEventUID, getReplaceableUID } from "../helpers/event.js";
import { getEventUID, getReplaceableUID, getTagValue, isReplaceable } from "../helpers/event.js";
import { matchFilters } from "../helpers/filter.js";
import { addSeenRelay } from "../helpers/relays.js";
import { getDeleteCoordinates, getDeleteIds } from "../helpers/delete.js";
export class EventStore {
database;
/** Enable this to keep old versions of replaceable events */
keepOldVersions = false;
constructor() {
this.database = new Database();
}
/** Adds an event to the database */
/** Adds an event to the database and update subscriptions */
add(event, fromRelay) {
if (event.kind === kinds.EventDeletion)
this.handleDeleteEvent(event);
// ignore if the event was deleted
if (this.checkDeleted(event))
return event;
// insert event into database
const inserted = this.database.addEvent(event);
// remove all old version of the replaceable event
if (!this.keepOldVersions && isReplaceable(event.kind)) {
const current = this.database.getReplaceable(event.kind, event.pubkey, getTagValue(event, "d"));
if (current) {
const older = Array.from(current).filter((e) => e.created_at < event.created_at);
for (const old of older)
this.database.deleteEvent(old);
// skip inserting this event because its not the newest
if (current.length !== older.length)
return current[0];
}
}
// attach relay this event was from
if (fromRelay)

@@ -19,2 +43,46 @@ addSeenRelay(inserted, fromRelay);

}
/** Removes an event from the database and updates subscriptions */
remove(event) {
if (typeof event === "string")
return this.database.deleteEvent(event);
else if (this.database.hasEvent(event.id)) {
return this.database.deleteEvent(event.id);
}
else
return false;
}
deletedIds = new Set();
deletedCoords = new Map();
handleDeleteEvent(deleteEvent) {
const ids = getDeleteIds(deleteEvent);
for (const id of ids) {
this.deletedIds.add(id);
// remove deleted events in the database
const event = this.database.getEvent(id);
if (event)
this.database.deleteEvent(event);
}
const coords = getDeleteCoordinates(deleteEvent);
for (const coord of coords) {
this.deletedCoords.set(coord, Math.max(this.deletedCoords.get(coord) ?? 0, deleteEvent.created_at));
// remove deleted events in the database
const event = this.database.getEvent(coord);
if (event && event.created_at < deleteEvent.created_at)
this.database.deleteEvent(event);
}
}
checkDeleted(event) {
if (this.deletedIds.has(event.id))
return true;
if (isParameterizedReplaceableKind(event.kind)) {
const deleted = this.deletedCoords.get(getEventUID(event));
if (deleted)
return deleted > event.created_at;
}
return false;
}
/** Removes any event that is not being used by a subscription */
prune(max) {
return this.database.prune(max);
}
/** Add an event to the store and notifies all subscribes it has updated */

@@ -36,9 +104,14 @@ update(event) {

}
/** Gets the latest version of a replaceable event */
getReplaceable(kind, pubkey, d) {
return this.database.getReplaceable(kind, pubkey, d)?.[0];
}
/** Returns all versions of a replaceable event */
getReplaceableHistory(kind, pubkey, d) {
return this.database.getReplaceable(kind, pubkey, d);
}
/** Creates an observable that updates a single event */
event(uid) {
event(id) {
return new Observable((observer) => {
let current = this.database.getEvent(uid);
let current = this.database.getEvent(id);
if (current) {

@@ -50,20 +123,16 @@ observer.next(current);

const inserted = this.database.inserted.subscribe((event) => {
if (getEventUID(event) === uid && (!current || event.created_at > current.created_at)) {
// remove old claim
if (current)
this.database.removeClaim(current, observer);
if (event.id === id) {
current = event;
observer.next(event);
// claim new event
this.database.claimEvent(current, observer);
this.database.claimEvent(event, observer);
}
});
// subscribe to updates
// subscribe to updated events
const updated = this.database.updated.subscribe((event) => {
if (event === current)
observer.next(event);
if (event.id === id)
observer.next(current);
});
// subscribe to deleted events
const deleted = this.database.deleted.subscribe((event) => {
if (getEventUID(event) === uid && current) {
if (current?.id === event.id) {
this.database.removeClaim(current, observer);

@@ -75,5 +144,5 @@ current = undefined;

return () => {
inserted.unsubscribe();
deleted.unsubscribe();
updated.unsubscribe();
inserted.unsubscribe();
if (current)

@@ -85,10 +154,10 @@ this.database.removeClaim(current, observer);

/** Creates an observable that subscribes to multiple events */
events(uids) {
events(ids) {
return new Observable((observer) => {
const events = new Map();
for (const uid of uids) {
const e = this.getEvent(uid);
if (e) {
events.set(uid, e);
this.database.claimEvent(e, observer);
for (const id of ids) {
const event = this.getEvent(id);
if (event) {
events.set(id, event);
this.database.claimEvent(event, observer);
}

@@ -99,20 +168,13 @@ }

const inserted = this.database.inserted.subscribe((event) => {
const uid = getEventUID(event);
if (uids.includes(uid)) {
const current = events.get(uid);
// remove old claim
if (!current || event.created_at > current.created_at) {
if (current)
this.database.removeClaim(current, observer);
events.set(uid, event);
observer.next(events);
// claim new event
this.database.claimEvent(event, observer);
}
const id = event.id;
if (ids.includes(id) && !events.has(id)) {
events.set(id, event);
observer.next(events);
// claim new event
this.database.claimEvent(event, observer);
}
});
// subscribe to updates
// subscribe to updated events
const updated = this.database.updated.subscribe((event) => {
const uid = getEventUID(event);
if (uids.includes(uid))
if (ids.includes(event.id))
observer.next(events);

@@ -122,8 +184,8 @@ });

const deleted = this.database.deleted.subscribe((event) => {
const uid = getEventUID(event);
if (uids.includes(uid)) {
const current = events.get(uid);
const id = event.id;
if (ids.includes(id)) {
const current = events.get(id);
if (current) {
this.database.removeClaim(current, observer);
events.delete(uid);
events.delete(id);
observer.next(events);

@@ -143,45 +205,159 @@ }

}
/** Creates an observable that updates a single replaceable event */
/** Creates an observable with the latest version of a replaceable event */
replaceable(kind, pubkey, d) {
return this.event(getReplaceableUID(kind, pubkey, d));
return new Observable((observer) => {
const uid = getReplaceableUID(kind, pubkey, d);
// get latest version
let current = this.database.getReplaceable(kind, pubkey, d)?.[0];
if (current) {
observer.next(current);
this.database.claimEvent(current, observer);
}
// subscribe to future events
const inserted = this.database.inserted.subscribe((event) => {
if (getEventUID(event) === uid && (!current || event.created_at > current.created_at)) {
// remove old claim
if (current)
this.database.removeClaim(current, observer);
current = event;
observer.next(event);
// claim new event
this.database.claimEvent(current, observer);
}
});
// subscribe to updated events
const updated = this.database.updated.subscribe((event) => {
if (event === current)
observer.next(event);
});
// subscribe to deleted events
const deleted = this.database.deleted.subscribe((event) => {
if (getEventUID(event) === uid && event === current) {
this.database.removeClaim(current, observer);
current = undefined;
observer.next(undefined);
}
});
return () => {
inserted.unsubscribe();
deleted.unsubscribe();
updated.unsubscribe();
if (current)
this.database.removeClaim(current, observer);
};
});
}
/** Creates an observable that streams all events that match the filter */
stream(filters) {
/** Creates an observable with the latest versions of replaceable events */
replaceableSet(pointers) {
return new Observable((observer) => {
let claimed = new Set();
let events = this.database.getForFilters(filters);
for (const event of events) {
observer.next(event);
const coords = pointers.map((p) => getReplaceableUID(p.kind, p.pubkey, p.identifier));
const events = new Map();
const handleEvent = (event) => {
const uid = getEventUID(event);
const current = events.get(uid);
if (current) {
if (event.created_at > current.created_at) {
this.database.removeClaim(current, observer);
}
else
return;
}
events.set(uid, event);
this.database.claimEvent(event, observer);
claimed.add(event);
};
// get latest version
for (const pointer of pointers) {
const events = this.database.getReplaceable(pointer.kind, pointer.pubkey, pointer.identifier);
if (events)
handleEvent(events[0]);
}
observer.next(events);
// subscribe to future events
const sub = this.database.inserted.subscribe((event) => {
if (matchFilters(filters, event)) {
observer.next(event);
this.database.claimEvent(event, observer);
claimed.add(event);
const inserted = this.database.inserted.subscribe((event) => {
if (isReplaceable(event.kind) && coords.includes(getEventUID(event))) {
handleEvent(event);
observer.next(events);
}
});
// subscribe to updated events
const updated = this.database.updated.subscribe((event) => {
if (isReplaceable(event.kind) && coords.includes(getEventUID(event))) {
observer.next(events);
}
});
// subscribe to deleted events
const deleted = this.database.deleted.subscribe((event) => {
const uid = getEventUID(event);
if (events.has(uid)) {
events.delete(uid);
this.database.removeClaim(event, observer);
observer.next(events);
}
});
return () => {
sub.unsubscribe();
// remove all claims
for (const event of claimed)
inserted.unsubscribe();
deleted.unsubscribe();
updated.unsubscribe();
for (const [_id, event] of events) {
this.database.removeClaim(event, observer);
claimed.clear();
}
};
});
}
/**
* Creates an observable that streams all events that match the filter
* @param filters
* @param [onlyNew=false] Only subscribe to new events
*/
stream(filters, onlyNew = false) {
filters = Array.isArray(filters) ? filters : [filters];
return new Observable((observer) => {
if (!onlyNew) {
let events = this.database.getForFilters(filters);
for (const event of events)
observer.next(event);
}
// subscribe to future events
const sub = this.database.inserted.subscribe((event) => {
if (matchFilters(filters, event))
observer.next(event);
});
return () => sub.unsubscribe();
});
}
/** Creates an observable that updates with an array of sorted events */
timeline(filters) {
timeline(filters, keepOldVersions = false) {
filters = Array.isArray(filters) ? filters : [filters];
return new Observable((observer) => {
const seen = new Map();
const timeline = [];
// NOTE: only call this if we know the event is in timeline
const removeFromTimeline = (event) => {
timeline.splice(timeline.indexOf(event), 1);
if (!keepOldVersions && isReplaceable(event.kind))
seen.delete(getEventUID(event));
this.database.removeClaim(event, observer);
};
// inserts an event into the timeline and handles replaceable events
const insertIntoTimeline = (event) => {
// remove old versions
if (!keepOldVersions && isReplaceable(event.kind)) {
const uid = getEventUID(event);
const old = seen.get(uid);
if (old) {
if (event.created_at > old.created_at)
removeFromTimeline(old);
else
return;
}
seen.set(uid, event);
}
// insert into timeline
insertEventIntoDescendingList(timeline, event);
this.database.claimEvent(event, observer);
};
// build initial timeline
const events = this.database.getForFilters(filters);
for (const event of events) {
insertEventIntoDescendingList(timeline, event);
this.database.claimEvent(event, observer);
seen.set(getEventUID(event), event);
}
for (const event of events)
insertIntoTimeline(event);
observer.next([...timeline]);

@@ -191,41 +367,17 @@ // subscribe to future events

if (matchFilters(filters, event)) {
const uid = getEventUID(event);
let current = seen.get(uid);
if (current) {
if (event.created_at > current.created_at) {
// replace event
timeline.splice(timeline.indexOf(current), 1, event);
observer.next([...timeline]);
// update the claim
seen.set(uid, event);
this.database.removeClaim(current, observer);
this.database.claimEvent(event, observer);
}
}
else {
insertEventIntoDescendingList(timeline, event);
observer.next([...timeline]);
// claim new event
this.database.claimEvent(event, observer);
seen.set(getEventUID(event), event);
}
insertIntoTimeline(event);
observer.next([...timeline]);
}
});
// subscribe to updates
// subscribe to updated events
const updated = this.database.updated.subscribe((event) => {
if (seen.has(getEventUID(event))) {
if (timeline.includes(event)) {
observer.next([...timeline]);
}
});
// subscribe to removed events
// subscribe to deleted events
const deleted = this.database.deleted.subscribe((event) => {
const uid = getEventUID(event);
let current = seen.get(uid);
if (current) {
// remove the event
timeline.splice(timeline.indexOf(current), 1);
if (timeline.includes(event)) {
removeFromTimeline(event);
observer.next([...timeline]);
// remove the claim
seen.delete(uid);
this.database.removeClaim(current, observer);
}

@@ -238,5 +390,6 @@ });

// remove all claims
for (const [_, event] of seen) {
for (const event of timeline) {
this.database.removeClaim(event, observer);
}
// forget seen replaceable events
seen.clear();

@@ -243,0 +396,0 @@ };

@@ -8,2 +8,3 @@ export type ParsedInvoice = {

};
/** Parses a lightning invoice */
export declare function parseBolt11(paymentRequest: string): ParsedInvoice;
import { decode } from "light-bolt11-decoder";
/** Parses a lightning invoice */
export function parseBolt11(paymentRequest) {

@@ -3,0 +4,0 @@ const decoded = decode(paymentRequest);

import { EventTemplate, NostrEvent } from "nostr-tools";
export declare function getEmojiTag(event: NostrEvent | EventTemplate, code: string): ["emoji", string, string];
/** Gets an "emoji" tag that matches an emoji code */
export declare function getEmojiTag(event: NostrEvent | EventTemplate, code: string): ["emoji", string, string] | undefined;
/** Returns the name of a NIP-30 emoji pack */
export declare function getPackName(pack: NostrEvent): string | undefined;
export type Emoji = {
name: string;
url: string;
};
/** Returns an array of emojis from a NIP-30 emoji pack */
export declare function getEmojis(pack: NostrEvent): Emoji[];

@@ -0,1 +1,3 @@

import { getTagValue } from "./event.js";
/** Gets an "emoji" tag that matches an emoji code */
export function getEmojiTag(event, code) {

@@ -5,1 +7,11 @@ code = code.replace(/^:|:$/g, "").toLocaleLowerCase();

}
/** Returns the name of a NIP-30 emoji pack */
export function getPackName(pack) {
return getTagValue(pack, "title") || getTagValue(pack, "d");
}
/** Returns an array of emojis from a NIP-30 emoji pack */
export function getEmojis(pack) {
return pack.tags
.filter((t) => t[0] === "emoji" && t[1] && t[2])
.map((t) => ({ name: t[1], url: t[2] }));
}

@@ -13,2 +13,7 @@ import { NostrEvent, VerifiedEvent } from "nostr-tools";

/**
* Checks if an object is a nostr event
* NOTE: does not validation the signature on the event
*/
export declare function isEvent(event: any): event is NostrEvent;
/**
* Returns if a kind is replaceable ( 10000 <= n < 20000 || n == 0 || n == 3 )

@@ -28,3 +33,6 @@ * or parameterized replaceable ( 30000 <= n < 40000 )

export declare function getIndexableTags(event: NostrEvent): Set<string>;
/** Returns the second index ( tag[1] ) of the first tag that matches the name */
/**
* Returns the second index ( tag[1] ) of the first tag that matches the name
* If the event has any hidden tags they will be searched first
*/
export declare function getTagValue(event: NostrEvent, name: string): string | undefined;

@@ -31,0 +39,0 @@ /** Sets events verified flag without checking anything */

import { kinds, verifiedSymbol } from "nostr-tools";
import { INDEXABLE_TAGS } from "../event-store/common.js";
import { getHiddenTags } from "./hidden-tags.js";
export const EventUIDSymbol = Symbol.for("event-uid");

@@ -7,2 +8,18 @@ export const EventIndexableTagsSymbol = Symbol.for("indexable-tags");

/**
* Checks if an object is a nostr event
* NOTE: does not validation the signature on the event
*/
export function isEvent(event) {
if (event === undefined || event === null)
return false;
return (event.id?.length === 64 &&
typeof event.sig === "string" &&
typeof event.pubkey === "string" &&
event.pubkey.length === 64 &&
typeof event.content === "string" &&
Array.isArray(event.tags) &&
typeof event.created_at === "number" &&
event.created_at > 0);
}
/**
* Returns if a kind is replaceable ( 10000 <= n < 20000 || n == 0 || n == 3 )

@@ -50,4 +67,11 @@ * or parameterized replaceable ( 30000 <= n < 40000 )

}
/** Returns the second index ( tag[1] ) of the first tag that matches the name */
/**
* Returns the second index ( tag[1] ) of the first tag that matches the name
* If the event has any hidden tags they will be searched first
*/
export function getTagValue(event, name) {
const hidden = getHiddenTags(event);
const hiddenValue = hidden?.find((t) => t[0] === name)?.[1];
if (hiddenValue)
return hiddenValue;
return event.tags.find((t) => t[0] === name)?.[1];

@@ -54,0 +78,0 @@ }

@@ -9,5 +9,3 @@ import { Filter, NostrEvent } from "nostr-tools";

export declare function matchFilters(filters: Filter[], event: NostrEvent): boolean;
/** Stringify filters in a predictable way */
export declare function stringifyFilter(filter: Filter | Filter[]): string;
/** Check if two filters are equal */
export declare function isFilterEqual(a: Filter | Filter[], b: Filter | Filter[]): boolean;
import { getIndexableTags } from "./event.js";
import stringify from "json-stringify-deterministic";
import equal from "fast-deep-equal";
/**

@@ -23,3 +23,3 @@ * Copied from nostr-tools and modified to use getIndexableTags

const tags = getIndexableTags(event);
if (values.some((v) => !tags.has(tagName + ":" + v)))
if (values.some((v) => tags.has(tagName + ":" + v)) === false)
return false;

@@ -44,9 +44,5 @@ }

}
/** Stringify filters in a predictable way */
export function stringifyFilter(filter) {
return stringify(filter);
}
/** Check if two filters are equal */
export function isFilterEqual(a, b) {
return stringifyFilter(a) === stringifyFilter(b);
return equal(a, b);
}

@@ -1,16 +0,24 @@

export * from "./profile.js";
export * from "./relays.js";
export * from "./bolt11.js";
export * from "./cache.js";
export * from "./comment.js";
export * from "./content.js";
export * from "./delete.js";
export * from "./emoji.js";
export * from "./event.js";
export * from "./external-id.js";
export * from "./filter.js";
export * from "./hashtag.js";
export * from "./hidden-tags.js";
export * from "./lnurl.js";
export * from "./lru.js";
export * from "./mailboxes.js";
export * from "./threading.js";
export * from "./media-attachment.js";
export * from "./pointers.js";
export * from "./profile.js";
export * from "./relays.js";
export * from "./string.js";
export * from "./tags.js";
export * from "./threading.js";
export * from "./time.js";
export * from "./tags.js";
export * from "./emoji.js";
export * from "./lru.js";
export * from "./hashtag.js";
export * from "./url.js";
export * from "./zap.js";
export * from "./bolt11.js";

@@ -1,16 +0,24 @@

export * from "./profile.js";
export * from "./relays.js";
export * from "./bolt11.js";
export * from "./cache.js";
export * from "./comment.js";
export * from "./content.js";
export * from "./delete.js";
export * from "./emoji.js";
export * from "./event.js";
export * from "./external-id.js";
export * from "./filter.js";
export * from "./hashtag.js";
export * from "./hidden-tags.js";
export * from "./lnurl.js";
export * from "./lru.js";
export * from "./mailboxes.js";
export * from "./threading.js";
export * from "./media-attachment.js";
export * from "./pointers.js";
export * from "./profile.js";
export * from "./relays.js";
export * from "./string.js";
export * from "./tags.js";
export * from "./threading.js";
export * from "./time.js";
export * from "./tags.js";
export * from "./emoji.js";
export * from "./lru.js";
export * from "./hashtag.js";
export * from "./url.js";
export * from "./zap.js";
export * from "./bolt11.js";

@@ -0,1 +1,2 @@

/** Returns the parsed JSON or undefined if invalid */
export declare function safeParse<T extends unknown = any>(str: string): T | undefined;

@@ -0,1 +1,2 @@

/** Returns the parsed JSON or undefined if invalid */
export function safeParse(str) {

@@ -2,0 +3,0 @@ try {

@@ -0,1 +1,2 @@

import { describe, test, expect } from "vitest";
import { getInboxes, getOutboxes } from "./mailboxes.js";

@@ -17,3 +18,3 @@ const emptyEvent = {

tags: [["r", "wss://inbox.com"]],
}))).toIncludeAllMembers(["wss://inbox.com/"]);
}))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
});

@@ -24,11 +25,11 @@ test("should remove bad urls", () => {

tags: [["r", "bad://inbox.com"]],
}))).toBeArrayOfSize(0);
}))).toHaveLength(0);
expect(Array.from(getInboxes({
...emptyEvent,
tags: [["r", "something that is not a url"]],
}))).toBeArrayOfSize(0);
}))).toHaveLength(0);
expect(Array.from(getInboxes({
...emptyEvent,
tags: [["r", "wss://inbox.com,wss://inbox.org"]],
}))).toBeArrayOfSize(0);
}))).toHaveLength(0);
});

@@ -39,3 +40,3 @@ test("without marker", () => {

tags: [["r", "wss://inbox.com/"]],
}))).toIncludeAllMembers(["wss://inbox.com/"]);
}))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
});

@@ -46,3 +47,3 @@ test("with marker", () => {

tags: [["r", "wss://inbox.com/", "read"]],
}))).toIncludeAllMembers(["wss://inbox.com/"]);
}))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
});

@@ -55,3 +56,3 @@ });

tags: [["r", "wss://outbox.com"]],
}))).toIncludeAllMembers(["wss://outbox.com/"]);
}))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
});

@@ -62,11 +63,11 @@ test("should remove bad urls", () => {

tags: [["r", "bad://inbox.com"]],
}))).toBeArrayOfSize(0);
}))).toHaveLength(0);
expect(Array.from(getOutboxes({
...emptyEvent,
tags: [["r", "something that is not a url"]],
}))).toBeArrayOfSize(0);
}))).toHaveLength(0);
expect(Array.from(getOutboxes({
...emptyEvent,
tags: [["r", "wss://outbox.com,wss://inbox.org"]],
}))).toBeArrayOfSize(0);
}))).toHaveLength(0);
});

@@ -77,3 +78,3 @@ test("without marker", () => {

tags: [["r", "wss://outbox.com/"]],
}))).toIncludeAllMembers(["wss://outbox.com/"]);
}))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
});

@@ -84,5 +85,5 @@ test("with marker", () => {

tags: [["r", "wss://outbox.com/", "write"]],
}))).toIncludeAllMembers(["wss://outbox.com/"]);
}))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
});
});
});
import { AddressPointer, DecodeResult, EventPointer, ProfilePointer } from "nostr-tools/nip19";
import { NostrEvent } from "nostr-tools";
export type AddressPointerWithoutD = Omit<AddressPointer, "identifier"> & {
identifier?: string;
};
/** Parse the value of an "a" tag into an AddressPointer */
export declare function parseCoordinate(a: string): AddressPointerWithoutD | null;

@@ -12,12 +14,43 @@ export declare function parseCoordinate(a: string, requireD: false): AddressPointerWithoutD | null;

export declare function parseCoordinate(a: string, requireD: false, silent: true): AddressPointerWithoutD | null;
/** Extra a pubkey from the result of nip19.decode */
export declare function getPubkeyFromDecodeResult(result?: DecodeResult): string | undefined;
/** Encodes the result of nip19.decode */
export declare function encodeDecodeResult(result: DecodeResult): "" | `nprofile1${string}` | `nevent1${string}` | `naddr1${string}` | `nsec1${string}` | `npub1${string}` | `note1${string}`;
export declare function getEventPointerFromTag(tag: string[]): EventPointer;
export declare function getAddressPointerFromTag(tag: string[]): AddressPointer;
export declare function getProfilePointerFromTag(tag: string[]): ProfilePointer;
/**
* Gets an EventPointer form a common "e" tag
* @throws
*/
export declare function getEventPointerFromETag(tag: string[]): EventPointer;
/**
* Gets an EventPointer form a "q" tag
* @throws
*/
export declare function getEventPointerFromQTag(tag: string[]): EventPointer;
/**
* Get an AddressPointer from an "a" tag
* @throws
*/
export declare function getAddressPointerFromATag(tag: string[]): AddressPointer;
/**
* Gets a ProfilePointer from a "p" tag
* @throws
*/
export declare function getProfilePointerFromPTag(tag: string[]): ProfilePointer;
/** Parses "e", "a", "p", and "q" tags into a pointer */
export declare function getPointerFromTag(tag: string[]): DecodeResult | null;
export declare function isAddressPointer(pointer: DecodeResult["data"]): pointer is AddressPointer;
export declare function isEventPointer(pointer: DecodeResult["data"]): pointer is EventPointer;
/** Returns the coordinate string for an AddressPointer */
export declare function getCoordinateFromAddressPointer(pointer: AddressPointer): string;
export declare function getATagFromAddressPointer(pointer: AddressPointer): ["a", ...string[]];
export declare function getETagFromEventPointer(pointer: EventPointer): ["e", ...string[]];
/**
* Returns an AddressPointer for a replaceable event
* @throws
*/
export declare function getAddressPointerForEvent(event: NostrEvent, relays?: string[]): AddressPointer;
/**
* Returns an EventPointer for an event
* @throws
*/
export declare function getEventPointerForEvent(event: NostrEvent, relays?: string[]): EventPointer;
/** Returns a pointer for a given event */
export declare function getPointerForEvent(event: NostrEvent, relays?: string[]): DecodeResult;
import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, } from "nostr-tools/nip19";
import { getPublicKey } from "nostr-tools";
import { getPublicKey, kinds } from "nostr-tools";
import { safeRelayUrls } from "./relays.js";
import { getTagValue } from "./index.js";
import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
export function parseCoordinate(a, requireD = false, silent = true) {

@@ -33,2 +35,3 @@ const parts = a.split(":");

}
/** Extra a pubkey from the result of nip19.decode */
export function getPubkeyFromDecodeResult(result) {

@@ -49,2 +52,3 @@ if (!result)

}
/** Encodes the result of nip19.decode */
export function encodeDecodeResult(result) {

@@ -67,3 +71,7 @@ switch (result.type) {

}
export function getEventPointerFromTag(tag) {
/**
* Gets an EventPointer form a common "e" tag
* @throws
*/
export function getEventPointerFromETag(tag) {
if (!tag[1])

@@ -76,4 +84,22 @@ throw new Error("Missing event id in tag");

}
export function getAddressPointerFromTag(tag) {
/**
* Gets an EventPointer form a "q" tag
* @throws
*/
export function getEventPointerFromQTag(tag) {
if (!tag[1])
throw new Error("Missing event id in tag");
let pointer = { id: tag[1] };
if (tag[2])
pointer.relays = safeRelayUrls([tag[2]]);
if (tag[3] && tag[3].length === 64)
pointer.author = tag[3];
return pointer;
}
/**
* Get an AddressPointer from an "a" tag
* @throws
*/
export function getAddressPointerFromATag(tag) {
if (!tag[1])
throw new Error("Missing coordinate in tag");

@@ -85,3 +111,7 @@ const pointer = parseCoordinate(tag[1], true, false);

}
export function getProfilePointerFromTag(tag) {
/**
* Gets a ProfilePointer from a "p" tag
* @throws
*/
export function getProfilePointerFromPTag(tag) {
if (!tag[1])

@@ -94,2 +124,3 @@ throw new Error("Missing pubkey in tag");

}
/** Parses "e", "a", "p", and "q" tags into a pointer */
export function getPointerFromTag(tag) {

@@ -99,10 +130,13 @@ try {

case "e":
return { type: "nevent", data: getEventPointerFromTag(tag) };
return { type: "nevent", data: getEventPointerFromETag(tag) };
case "a":
return {
type: "naddr",
data: getAddressPointerFromTag(tag),
data: getAddressPointerFromATag(tag),
};
case "p":
return { type: "nprofile", data: getProfilePointerFromTag(tag) };
return { type: "nprofile", data: getProfilePointerFromPTag(tag) };
// NIP-18 quote tags
case "q":
return { type: "nevent", data: getEventPointerFromETag(tag) };
}

@@ -115,19 +149,69 @@ }

return (typeof pointer !== "string" &&
Object.hasOwn(pointer, "identifier") &&
Object.hasOwn(pointer, "pubkey") &&
Object.hasOwn(pointer, "kind"));
Reflect.has(pointer, "identifier") &&
Reflect.has(pointer, "pubkey") &&
Reflect.has(pointer, "kind"));
}
export function isEventPointer(pointer) {
return typeof pointer !== "string" && Object.hasOwn(pointer, "id");
return typeof pointer !== "string" && Reflect.has(pointer, "id");
}
/** Returns the coordinate string for an AddressPointer */
export function getCoordinateFromAddressPointer(pointer) {
return `${pointer.kind}:${pointer.pubkey}:${pointer.identifier}`;
}
export function getATagFromAddressPointer(pointer) {
const relay = pointer.relays?.[0];
const coordinate = getCoordinateFromAddressPointer(pointer);
return relay ? ["a", coordinate, relay] : ["a", coordinate];
/**
* Returns an AddressPointer for a replaceable event
* @throws
*/
export function getAddressPointerForEvent(event, relays) {
if (!isParameterizedReplaceableKind(event.kind))
throw new Error("Cant get AddressPointer for non-replaceable event");
const d = getTagValue(event, "d");
if (!d)
throw new Error("Event missing identifier");
return {
identifier: d,
kind: event.kind,
pubkey: event.pubkey,
relays,
};
}
export function getETagFromEventPointer(pointer) {
return pointer.relays?.length ? ["e", pointer.id, pointer.relays[0]] : ["e", pointer.id];
/**
* Returns an EventPointer for an event
* @throws
*/
export function getEventPointerForEvent(event, relays) {
return {
id: event.id,
kind: event.kind,
author: event.pubkey,
relays,
};
}
/** Returns a pointer for a given event */
export function getPointerForEvent(event, relays) {
if (kinds.isParameterizedReplaceableKind(event.kind)) {
const d = getTagValue(event, "d");
if (!d)
throw new Error("Event missing identifier");
return {
type: "naddr",
data: {
identifier: d,
kind: event.kind,
pubkey: event.pubkey,
relays,
},
};
}
else {
return {
type: "nevent",
data: {
id: event.id,
kind: event.kind,
author: event.pubkey,
relays,
},
};
}
}

@@ -12,3 +12,3 @@ import { kinds } from "nostr-tools";

// add missing protocol to website
if (profile.website?.startsWith("http") === false) {
if (profile.website && profile.website?.length > 0 && profile.website?.startsWith("http") === false) {
profile.website = "https://" + profile.website;

@@ -15,0 +15,0 @@ }

@@ -0,4 +1,10 @@

/** Tests if a string is hex */
export declare function isHex(str?: string): boolean;
/** Tests if a string is a 64 length hex string */
export declare function isHexKey(key?: string): boolean;
/**
* Remove invisible characters from a string
* @see read more https://www.regular-expressions.info/unicode.html#category
*/
export declare function stripInvisibleChar(str: string): string;
export declare function stripInvisibleChar(str?: string | undefined): string | undefined;

@@ -0,1 +1,2 @@

/** Tests if a string is hex */
export function isHex(str) {

@@ -6,2 +7,3 @@ if (str?.match(/^[0-9a-f]+$/i))

}
/** Tests if a string is a 64 length hex string */
export function isHexKey(key) {

@@ -8,0 +10,0 @@ if (key?.toLowerCase()?.match(/^[0-9a-f]{64}$/))

@@ -0,6 +1,12 @@

/** Checks if tag is an "e" tag and has at least one value */
export declare function isETag(tag: string[]): tag is ["e", string, ...string[]];
/** Checks if tag is an "p" tag and has at least one value */
export declare function isPTag(tag: string[]): tag is ["p", string, ...string[]];
/** Checks if tag is an "r" tag and has at least one value */
export declare function isRTag(tag: string[]): tag is ["r", string, ...string[]];
/** Checks if tag is an "d" tag and has at least one value */
export declare function isDTag(tag: string[]): tag is ["d", string, ...string[]];
/** Checks if tag is an "a" tag and has at least one value */
export declare function isATag(tag: string[]): tag is ["a", string, ...string[]];
/** Checks if tag is an "a" tag and has at least one value */
export declare function isTTag(tag: string[]): tag is ["t", string, ...string[]];

@@ -0,18 +1,24 @@

/** Checks if tag is an "e" tag and has at least one value */
export function isETag(tag) {
return tag[0] === "e" && tag[1] !== undefined;
}
/** Checks if tag is an "p" tag and has at least one value */
export function isPTag(tag) {
return tag[0] === "p" && tag[1] !== undefined;
}
/** Checks if tag is an "r" tag and has at least one value */
export function isRTag(tag) {
return tag[0] === "r" && tag[1] !== undefined;
}
/** Checks if tag is an "d" tag and has at least one value */
export function isDTag(tag) {
return tag[0] === "d" && tag[1] !== undefined;
}
/** Checks if tag is an "a" tag and has at least one value */
export function isATag(tag) {
return tag[0] === "a" && tag[1] !== undefined;
}
/** Checks if tag is an "a" tag and has at least one value */
export function isTTag(tag) {
return tag[0] === "a" && tag[1] !== undefined;
}

@@ -26,9 +26,9 @@ import { EventTemplate, NostrEvent } from "nostr-tools";

export declare const Nip10ThreadRefsSymbol: unique symbol;
declare module "nostr-tools" {
interface Event {
[Nip10ThreadRefsSymbol]?: ThreadReferences;
}
}
/**
* Gets an EventPointer form a NIP-10 threading "e" tag
* @throws
*/
export declare function getEventPointerFromThreadTag(tag: string[]): EventPointer;
/** Parses NIP-10 tags and handles legacy behavior */
export declare function interpretThreadTags(event: NostrEvent | EventTemplate): {
export declare function interpretThreadTags(tags: string[][]): {
root?: {

@@ -35,0 +35,0 @@ e: string[];

@@ -1,8 +0,29 @@

import { getAddressPointerFromTag, getEventPointerFromTag } from "./pointers.js";
import { getAddressPointerFromATag } from "./pointers.js";
import { getOrComputeCachedValue } from "./cache.js";
import { safeRelayUrls } from "./relays.js";
export const Nip10ThreadRefsSymbol = Symbol.for("nip10-thread-refs");
/**
* Gets an EventPointer form a NIP-10 threading "e" tag
* @throws
*/
export function getEventPointerFromThreadTag(tag) {
if (!tag[1])
throw new Error("Missing event id in tag");
let pointer = { id: tag[1] };
if (tag[2])
pointer.relays = safeRelayUrls([tag[2]]);
// get author from NIP-18 quote tags, nip-22 comments tags, or nip-10 thread tags
if (tag[0] === "e" &&
(tag[3] === "root" || tag[3] === "reply" || tag[3] === "mention") &&
tag[4] &&
tag[4].length === 64) {
// NIP-10 "e" tag
pointer.author = tag[4];
}
return pointer;
}
/** Parses NIP-10 tags and handles legacy behavior */
export function interpretThreadTags(event) {
const eTags = event.tags.filter((t) => t[0] === "e" && t[1]);
const aTags = event.tags.filter((t) => t[0] === "a" && t[1]);
export function interpretThreadTags(tags) {
const eTags = tags.filter((t) => t[0] === "e" && t[1]);
const aTags = tags.filter((t) => t[0] === "a" && t[1]);
// find the root and reply tags.

@@ -47,3 +68,3 @@ let rootETag = eTags.find((t) => t[3] === "root");

return getOrComputeCachedValue(event, Nip10ThreadRefsSymbol, () => {
const tags = interpretThreadTags(event);
const tags = interpretThreadTags(event.tags);
let root;

@@ -53,4 +74,4 @@ if (tags.root) {

root = {
e: tags.root.e && getEventPointerFromTag(tags.root.e),
a: tags.root.a && getAddressPointerFromTag(tags.root.a),
e: tags.root.e && getEventPointerFromThreadTag(tags.root.e),
a: tags.root.a && getAddressPointerFromATag(tags.root.a),
};

@@ -64,4 +85,4 @@ }

reply = {
e: tags.reply.e && getEventPointerFromTag(tags.reply.e),
a: tags.reply.a && getAddressPointerFromTag(tags.reply.a),
e: tags.reply.e && getEventPointerFromThreadTag(tags.reply.e),
a: tags.reply.a && getAddressPointerFromATag(tags.reply.a),
};

@@ -68,0 +89,0 @@ }

@@ -7,6 +7,9 @@ export declare const convertToUrl: (url: string | URL) => URL;

export declare const AUDIO_EXT: string[];
export declare function isVisualMediaURL(url: string | URL): boolean;
/** Checks if a url is a image URL */
export declare function isImageURL(url: string | URL): boolean;
/** Checks if a url is a video URL */
export declare function isVideoURL(url: string | URL): boolean;
/** Checks if a url is a stream URL */
export declare function isStreamURL(url: string | URL): boolean;
/** Checks if a url is a audio URL */
export declare function isAudioURL(url: string | URL): boolean;

@@ -7,5 +7,3 @@ export const convertToUrl = (url) => (url instanceof URL ? url : new URL(url));

export const AUDIO_EXT = [".mp3", ".wav", ".ogg", ".aac"];
export function isVisualMediaURL(url) {
return isImageURL(url) || isVideoURL(url) || isStreamURL(url);
}
/** Checks if a url is a image URL */
export function isImageURL(url) {

@@ -16,2 +14,3 @@ url = convertToUrl(url);

}
/** Checks if a url is a video URL */
export function isVideoURL(url) {

@@ -22,2 +21,3 @@ url = convertToUrl(url);

}
/** Checks if a url is a stream URL */
export function isStreamURL(url) {

@@ -28,2 +28,3 @@ url = convertToUrl(url);

}
/** Checks if a url is a audio URL */
export function isAudioURL(url) {

@@ -30,0 +31,0 @@ url = convertToUrl(url);

@@ -7,9 +7,34 @@ import { NostrEvent } from "nostr-tools";

export declare const ZapAddressPointerSymbol: unique symbol;
/** Returns the senders pubkey */
export declare function getZapSender(zap: NostrEvent): string;
/**
* Gets the receivers pubkey
* @throws
*/
export declare function getZapRecipient(zap: NostrEvent): string;
/** Returns the parsed bolt11 invoice */
export declare function getZapPayment(zap: NostrEvent): import("./bolt11.js").ParsedInvoice | undefined;
/** Gets the AddressPointer that was zapped */
export declare function getZapAddressPointer(zap: NostrEvent): import("nostr-tools/nip19").AddressPointer | null;
/** Gets the EventPointer that was zapped */
export declare function getZapEventPointer(zap: NostrEvent): import("nostr-tools/nip19").EventPointer | null;
/** Gets the preimage for the bolt11 invoice */
export declare function getZapPreimage(zap: NostrEvent): string | undefined;
/**
* Returns the zap request event inside the zap receipt
* @throws
*/
export declare function getZapRequest(zap: NostrEvent): import("nostr-tools").Event;
/**
* Checks if the zap is valid
* DOES NOT validate LNURL address
*/
export declare function isValidZap(zap?: NostrEvent): boolean;
export type ZapSplit = {
pubkey: string;
percent: number;
weight: number;
relay?: string;
};
/** Returns the zap splits for an event */
export declare function getZapSplits(event: NostrEvent): ZapSplit[] | undefined;

@@ -5,3 +5,3 @@ import { kinds, nip57 } from "nostr-tools";

import { isATag, isETag } from "./tags.js";
import { getAddressPointerFromTag, getEventPointerFromTag } from "./pointers.js";
import { getAddressPointerFromATag, getEventPointerFromETag } from "./pointers.js";
import { parseBolt11 } from "./bolt11.js";

@@ -13,5 +13,10 @@ export const ZapRequestSymbol = Symbol.for("zap-request");

export const ZapAddressPointerSymbol = Symbol.for("zap-address-pointer");
/** Returns the senders pubkey */
export function getZapSender(zap) {
return getTagValue(zap, "P") || getZapRequest(zap).pubkey;
}
/**
* Gets the receivers pubkey
* @throws
*/
export function getZapRecipient(zap) {

@@ -23,2 +28,3 @@ const recipient = getTagValue(zap, "p");

}
/** Returns the parsed bolt11 invoice */
export function getZapPayment(zap) {

@@ -30,17 +36,24 @@ return getOrComputeCachedValue(zap, ZapInvoiceSymbol, () => {

}
/** Gets the AddressPointer that was zapped */
export function getZapAddressPointer(zap) {
return getOrComputeCachedValue(zap, ZapAddressPointerSymbol, () => {
const a = zap.tags.find(isATag);
return a ? getAddressPointerFromTag(a) : null;
return a ? getAddressPointerFromATag(a) : null;
});
}
/** Gets the EventPointer that was zapped */
export function getZapEventPointer(zap) {
return getOrComputeCachedValue(zap, ZapEventPointerSymbol, () => {
const e = zap.tags.find(isETag);
return e ? getEventPointerFromTag(e) : null;
return e ? getEventPointerFromETag(e) : null;
});
}
/** Gets the preimage for the bolt11 invoice */
export function getZapPreimage(zap) {
return getTagValue(zap, "preimage");
}
/**
* Returns the zap request event inside the zap receipt
* @throws
*/
export function getZapRequest(zap) {

@@ -57,2 +70,6 @@ return getOrComputeCachedValue(zap, ZapRequestSymbol, () => {

}
/**
* Checks if the zap is valid
* DOES NOT validate LNURL address
*/
export function isValidZap(zap) {

@@ -72,1 +89,13 @@ if (!zap)

}
/** Returns the zap splits for an event */
export function getZapSplits(event) {
const tags = event.tags.filter((t) => t[0] === "zap" && t[1] && t[3]);
if (tags.length > 0) {
const targets = tags
.map((t) => ({ pubkey: t[1], relay: t[2], weight: parseFloat(t[3]) }))
.filter((p) => Number.isFinite(p.weight));
const total = targets.reduce((v, p) => v + p.weight, 0);
return targets.map((p) => ({ ...p, percent: p.weight / total }));
}
return undefined;
}

@@ -1,2 +0,2 @@

export * from "./getValue.js";
export * from "./get-value.js";
export * from "./share-latest-value.js";

@@ -1,2 +0,2 @@

export * from "./getValue.js";
export * from "./get-value.js";
export * from "./share-latest-value.js";

@@ -5,2 +5,3 @@ export type Deferred<T> = Promise<T> & {

};
/** Creates a controlled promise */
export declare function createDefer<T>(): Deferred<T>;

@@ -0,1 +1,2 @@

/** Creates a controlled promise */
export function createDefer() {

@@ -2,0 +3,0 @@ let _resolve;

@@ -1,6 +0,7 @@

export * from "./simple.js";
export * from "./comments.js";
export * from "./mailboxes.js";
export * from "./profile.js";
export * from "./mailboxes.js";
export * from "./reactions.js";
export * from "./simple.js";
export * from "./thread.js";
export * from "./zaps.js";

@@ -1,6 +0,7 @@

export * from "./simple.js";
export * from "./comments.js";
export * from "./mailboxes.js";
export * from "./profile.js";
export * from "./mailboxes.js";
export * from "./reactions.js";
export * from "./simple.js";
export * from "./thread.js";
export * from "./zaps.js";
import { Query } from "../query-store/index.js";
/** A query that gets and parses the inbox and outbox relays for a pubkey */
export declare function MailboxesQuery(pubkey: string): Query<{

@@ -3,0 +4,0 @@ inboxes: string[];

import { kinds } from "nostr-tools";
import { map } from "rxjs/operators";
import { getInboxes, getOutboxes } from "../helpers/mailboxes.js";
/** A query that gets and parses the inbox and outbox relays for a pubkey */
export function MailboxesQuery(pubkey) {

@@ -5,0 +6,0 @@ return {

import { ProfileContent } from "../helpers/profile.js";
import { Query } from "../query-store/index.js";
/** A query that gets and parses the kind 0 metadata for a pubkey */
export declare function ProfileQuery(pubkey: string): Query<ProfileContent | undefined>;
import { kinds } from "nostr-tools";
import { filter, map } from "rxjs/operators";
import { getProfileContent, isValidProfile } from "../helpers/profile.js";
/** A query that gets and parses the kind 0 metadata for a pubkey */
export function ProfileQuery(pubkey) {

@@ -5,0 +6,0 @@ return {

import { NostrEvent } from "nostr-tools";
import { Query } from "../query-store/index.js";
/** Creates a query that returns all reactions to an event (supports replaceable events) */
/** A query that returns all reactions to an event (supports replaceable events) */
export declare function ReactionsQuery(event: NostrEvent): Query<NostrEvent[]>;
import { kinds } from "nostr-tools";
import { getEventUID, isReplaceable } from "../helpers/event.js";
/** Creates a query that returns all reactions to an event (supports replaceable events) */
/** A query that returns all reactions to an event (supports replaceable events) */
export function ReactionsQuery(event) {

@@ -5,0 +5,0 @@ return {

import { Filter, NostrEvent } from "nostr-tools";
import { Query } from "../query-store/index.js";
/** Creates a Query that returns a single event or undefined */
export declare function SingleEventQuery(uid: string): Query<NostrEvent | undefined>;
export declare function SingleEventQuery(id: string): Query<NostrEvent | undefined>;
/** Creates a Query that returns a multiple events in a map */
export declare function MultipleEventsQuery(uids: string[]): Query<Map<string, NostrEvent>>;
export declare function MultipleEventsQuery(ids: string[]): Query<Map<string, NostrEvent>>;
/** Creates a Query returning the latest version of a replaceable event */
export declare function ReplaceableQuery(kind: number, pubkey: string, d?: string): Query<NostrEvent | undefined>;
/** Creates a Query that returns an array of sorted events matching the filters */
export declare function TimelineQuery(filters: Filter | Filter[]): Query<NostrEvent[]>;
export declare function TimelineQuery(filters: Filter | Filter[], keepOldVersions?: boolean): Query<NostrEvent[]>;
/** Creates a Query that returns a directory of events by their UID */

@@ -12,0 +12,0 @@ export declare function ReplaceableSetQuery(pointers: {

@@ -1,15 +0,15 @@

import stringify from "json-stringify-deterministic";
import hash_sum from "hash-sum";
import { getReplaceableUID } from "../helpers/event.js";
/** Creates a Query that returns a single event or undefined */
export function SingleEventQuery(uid) {
export function SingleEventQuery(id) {
return {
key: uid,
run: (events) => events.event(uid),
key: id,
run: (events) => events.event(id),
};
}
/** Creates a Query that returns a multiple events in a map */
export function MultipleEventsQuery(uids) {
export function MultipleEventsQuery(ids) {
return {
key: uids.join(","),
run: (events) => events.events(uids),
key: ids.join(","),
run: (events) => events.events(ids),
};

@@ -25,6 +25,7 @@ }

/** Creates a Query that returns an array of sorted events matching the filters */
export function TimelineQuery(filters) {
export function TimelineQuery(filters, keepOldVersions) {
filters = Array.isArray(filters) ? filters : [filters];
return {
key: stringify(filters),
run: (events) => events.timeline(Array.isArray(filters) ? filters : [filters]),
key: hash_sum(filters) + (keepOldVersions ? "-history" : ""),
run: (events) => events.timeline(filters, keepOldVersions),
};

@@ -34,7 +35,6 @@ }

export function ReplaceableSetQuery(pointers) {
const cords = pointers.map((pointer) => getReplaceableUID(pointer.kind, pointer.pubkey, pointer.identifier));
return {
key: stringify(pointers),
run: (events) => events.events(cords),
key: hash_sum(pointers),
run: (events) => events.replaceableSet(pointers),
};
}

@@ -24,1 +24,3 @@ import { NostrEvent } from "nostr-tools";

export declare function ThreadQuery(root: string | AddressPointer | EventPointer, opts?: ThreadQueryOptions): Query<Thread>;
/** A query that gets all legacy and NIP-10, and NIP-22 replies for an event */
export declare function RepliesQuery(event: NostrEvent, overrideKinds?: number[]): Query<NostrEvent[]>;
import { kinds } from "nostr-tools";
import { isParameterizedReplaceableKind } from "nostr-tools/kinds";
import { map } from "rxjs/operators";
import { getNip10References } from "../helpers/threading.js";
import { getCoordinateFromAddressPointer, isAddressPointer } from "../helpers/pointers.js";
import { getEventUID } from "../helpers/event.js";
import { getNip10References, interpretThreadTags } from "../helpers/threading.js";
import { getCoordinateFromAddressPointer, isAddressPointer, isEventPointer } from "../helpers/pointers.js";
import { getEventUID, getReplaceableUID, getTagValue, isEvent } from "../helpers/event.js";
import { COMMENT_KIND } from "../helpers/comment.js";
const defaultOptions = {

@@ -67,1 +69,25 @@ kinds: [kinds.ShortTextNote],

}
/** A query that gets all legacy and NIP-10, and NIP-22 replies for an event */
export function RepliesQuery(event, overrideKinds) {
return {
key: getEventUID(event),
run: (events) => {
const kinds = overrideKinds || event.kind === 1 ? [1, COMMENT_KIND] : [COMMENT_KIND];
const filter = { kinds };
if (isEvent(parent) || isEventPointer(event))
filter["#e"] = [event.id];
const address = isParameterizedReplaceableKind(event.kind)
? getReplaceableUID(event.kind, event.pubkey, getTagValue(event, "d"))
: undefined;
if (address) {
filter["#a"] = [address];
}
return events.timeline(filter).pipe(map((events) => {
return events.filter((e) => {
const refs = interpretThreadTags(e.tags);
return refs.reply?.e?.[1] === event.id || refs.reply?.a?.[1] === address;
});
}));
},
};
}
import { AddressPointer, EventPointer } from "nostr-tools/nip19";
import { NostrEvent } from "nostr-tools";
import { Query } from "../query-store/index.js";
/** A query that gets all zap events for an event */
export declare function EventZapsQuery(id: string | EventPointer | AddressPointer): Query<NostrEvent[]>;

@@ -5,2 +5,3 @@ import { map } from "rxjs";

import { isValidZap } from "../helpers/zap.js";
/** A query that gets all zap events for an event */
export function EventZapsQuery(id) {

@@ -7,0 +8,0 @@ return {

@@ -8,3 +8,11 @@ import { BehaviorSubject, Observable } from "rxjs";

export type Query<T extends unknown> = {
/**
* A unique key for this query. this is used to detect duplicate queries
*/
key: string;
/** The args array this query was created with. This is mostly for debugging */
args?: Array<any>;
/**
* The meat of the query, this should return an Observables that subscribes to the eventStore in some way
*/
run: (events: EventStore, store: QueryStore) => Observable<T>;

@@ -17,15 +25,16 @@ };

constructor(store: EventStore);
queries: LRU<Observable<any> | BehaviorSubject<any>>;
queries: LRU<Query<any>>;
observables: WeakMap<Query<any>, Observable<any> | BehaviorSubject<any>>;
/** Creates a cached query */
runQuery<T extends unknown, Args extends Array<any>>(queryConstructor: (...args: Args) => {
createQuery<T extends unknown, Args extends Array<any>>(queryConstructor: (...args: Args) => {
key: string;
run: (events: EventStore, store: QueryStore) => Observable<T>;
}): (...args: Args) => Observable<T>;
/** Returns a single event */
}, ...args: Args): Observable<T>;
/** Creates a SingleEventQuery */
event(id: string): Observable<import("nostr-tools").Event | undefined>;
/** Returns a single event */
/** Creates a MultipleEventsQuery */
events(ids: string[]): Observable<Map<string, import("nostr-tools").Event>>;
/** Returns the latest version of a replaceable event */
/** Creates a ReplaceableQuery */
replaceable(kind: number, pubkey: string, d?: string): Observable<import("nostr-tools").Event | undefined>;
/** Returns a directory of events by their UID */
/** Creates a ReplaceableSetQuery */
replaceableSet(pointers: {

@@ -36,9 +45,9 @@ kind: number;

}[]): Observable<Map<string, import("nostr-tools").Event>>;
/** Returns an array of events that match the filter */
timeline(filters: Filter | Filter[]): Observable<import("nostr-tools").Event[]>;
/** Returns the parsed profile (0) for a pubkey */
/** Creates a TimelineQuery */
timeline(filters: Filter | Filter[], keepOldVersions?: boolean): Observable<import("nostr-tools").Event[]>;
/** Creates a ProfileQuery */
profile(pubkey: string): Observable<import("../helpers/profile.js").ProfileContent | undefined>;
/** Returns all reactions for an event (supports replaceable events) */
/** Creates a ReactionsQuery */
reactions(event: NostrEvent): Observable<import("nostr-tools").Event[]>;
/** Returns the parsed relay list (10002) for the pubkey */
/** Creates a MailboxesQuery */
mailboxes(pubkey: string): Observable<{

@@ -48,4 +57,5 @@ inboxes: string[];

} | undefined>;
/** Creates a ThreadQuery */
thread(root: string | EventPointer | AddressPointer): Observable<Queries.Thread>;
}
export { Queries };

@@ -11,51 +11,57 @@ import { LRU } from "../helpers/lru.js";

queries = new LRU();
observables = new WeakMap();
/** Creates a cached query */
runQuery(queryConstructor) {
return (...args) => {
const query = queryConstructor(...args);
const key = `${queryConstructor.name}|${query.key}`;
if (!this.queries.has(key)) {
const observable = query.run(this.store, this).pipe(shareLatestValue());
this.queries.set(key, observable);
return observable;
}
return this.queries.get(key);
};
createQuery(queryConstructor, ...args) {
const tempQuery = queryConstructor(...args);
const key = `${queryConstructor.name}|${tempQuery.key}`;
let query = this.queries.get(key);
if (!query) {
query = tempQuery;
this.queries.set(key, tempQuery);
}
if (!this.observables.has(query)) {
query.args = args;
const observable = query.run(this.store, this).pipe(shareLatestValue());
this.observables.set(query, observable);
return observable;
}
return this.observables.get(query);
}
/** Returns a single event */
/** Creates a SingleEventQuery */
event(id) {
return this.runQuery(Queries.SingleEventQuery)(id);
return this.createQuery(Queries.SingleEventQuery, id);
}
/** Returns a single event */
/** Creates a MultipleEventsQuery */
events(ids) {
return this.runQuery(Queries.MultipleEventsQuery)(ids);
return this.createQuery(Queries.MultipleEventsQuery, ids);
}
/** Returns the latest version of a replaceable event */
/** Creates a ReplaceableQuery */
replaceable(kind, pubkey, d) {
return this.runQuery(Queries.ReplaceableQuery)(kind, pubkey, d);
return this.createQuery(Queries.ReplaceableQuery, kind, pubkey, d);
}
/** Returns a directory of events by their UID */
/** Creates a ReplaceableSetQuery */
replaceableSet(pointers) {
return this.runQuery(Queries.ReplaceableSetQuery)(pointers);
return this.createQuery(Queries.ReplaceableSetQuery, pointers);
}
/** Returns an array of events that match the filter */
timeline(filters) {
return this.runQuery(Queries.TimelineQuery)(filters);
/** Creates a TimelineQuery */
timeline(filters, keepOldVersions) {
return this.createQuery(Queries.TimelineQuery, filters, keepOldVersions);
}
/** Returns the parsed profile (0) for a pubkey */
/** Creates a ProfileQuery */
profile(pubkey) {
return this.runQuery(Queries.ProfileQuery)(pubkey);
return this.createQuery(Queries.ProfileQuery, pubkey);
}
/** Returns all reactions for an event (supports replaceable events) */
/** Creates a ReactionsQuery */
reactions(event) {
return this.runQuery(Queries.ReactionsQuery)(event);
return this.createQuery(Queries.ReactionsQuery, event);
}
/** Returns the parsed relay list (10002) for the pubkey */
/** Creates a MailboxesQuery */
mailboxes(pubkey) {
return this.runQuery(Queries.MailboxesQuery)(pubkey);
return this.createQuery(Queries.MailboxesQuery, pubkey);
}
/** Creates a ThreadQuery */
thread(root) {
return this.runQuery(Queries.ThreadQuery)(root);
return this.createQuery(Queries.ThreadQuery, root);
}
}
export { Queries };
{
"name": "applesauce-core",
"version": "0.9.0",
"version": "0.10.0",
"description": "",

@@ -55,25 +55,17 @@ "type": "module",

"dependencies": {
"@scure/base": "^1.1.9",
"debug": "^4.3.7",
"json-stringify-deterministic": "^1.0.12",
"fast-deep-equal": "^3.1.3",
"hash-sum": "^2.0.0",
"light-bolt11-decoder": "^3.2.0",
"nanoid": "^5.0.7",
"nostr-tools": "^2.10.1",
"nostr-tools": "^2.10.3",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/debug": "^4.1.12",
"@types/jest": "^29.5.13",
"jest": "^29.7.0",
"jest-extended": "^4.0.2",
"typescript": "^5.6.3"
"@types/hash-sum": "^1.0.2",
"typescript": "^5.6.3",
"vitest": "^2.1.8"
},
"jest": {
"roots": [
"dist"
],
"setupFilesAfterEnv": [
"jest-extended/all"
]
},
"funding": {

@@ -86,5 +78,5 @@ "type": "lightning",

"watch:build": "tsc --watch > /dev/null",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
"watch:test": "(trap 'kill 0' SIGINT; pnpm run build -w > /dev/null & pnpm run test --watch)"
"test": "vitest run --passWithNoTests",
"watch:test": "vitest"
}
}
# applesauce-core
AppleSauce Core is an interpretation layer for nostr clients, Push events into the in-memory [database](https://hzrd149.github.io/applesauce/classes/Database.html) and get nicely formatted data out with [queries](https://hzrd149.github.io/applesauce/modules/Queries)
AppleSauce Core is an interpretation layer for nostr clients, Push events into the in-memory [database](https://hzrd149.github.io/applesauce/typedoc/classes/Database.html) and get nicely formatted data out with [queries](https://hzrd149.github.io/applesauce/typedoc/modules/Queries)

@@ -5,0 +5,0 @@ # Example

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc