applesauce-core
Advanced tools
Comparing version 0.0.0-next-20241211183908 to 0.0.0-next-20241212192616
import { getExternalPointerFromTag } from "./external-id.js"; | ||
import { getOrComputeCachedValue } from "./cache.js"; | ||
import { getAddressPointerFromTag, getEventPointerFromTag } from "./pointers.js"; | ||
import { getAddressPointerFromATag } from "./pointers.js"; | ||
import { safeRelayUrl } from "./relays.js"; | ||
export const COMMENT_KIND = 1111; | ||
@@ -17,9 +18,9 @@ export const CommentRootPointerSymbol = Symbol.for("comment-root-pointer"); | ||
throw new Error("Missing kind tag"); | ||
const eventPointer = getEventPointerFromTag(tag); | ||
return { | ||
id: eventPointer.id, | ||
const pointer = { | ||
id: tag[1], | ||
kind: parseInt(kind), | ||
pubkey: eventPointer.author, | ||
relay: eventPointer.relays?.[0], | ||
pubkey: tag[3] || undefined, | ||
relay: tag[2] && (safeRelayUrl(tag[2]) ?? undefined), | ||
}; | ||
return pointer; | ||
} | ||
@@ -41,3 +42,3 @@ return null; | ||
id, | ||
...getAddressPointerFromTag(tag), | ||
...getAddressPointerFromATag(tag), | ||
kind: parseInt(kind), | ||
@@ -44,0 +45,0 @@ }; |
@@ -1,5 +0,3 @@ | ||
import { EventTemplate, NostrEvent } from "nostr-tools"; | ||
import { NostrEvent } from "nostr-tools"; | ||
export declare function getDeleteIds(deleteEvent: NostrEvent): string[]; | ||
export declare function getDeleteCoordinates(deleteEvent: NostrEvent): string[]; | ||
/** Creates a NIP-09 delete event for an array of events */ | ||
export declare function createDeleteEvent(events: NostrEvent[], message?: string): EventTemplate; |
@@ -1,7 +0,2 @@ | ||
import { kinds } from "nostr-tools"; | ||
import { isParameterizedReplaceableKind } from "nostr-tools/kinds"; | ||
import { isATag, isETag } from "./tags.js"; | ||
import { unixNow } from "./time.js"; | ||
import { getATagFromAddressPointer, getETagFromEventPointer } from "./pointers.js"; | ||
import { getTagValue } from "./index.js"; | ||
export function getDeleteIds(deleteEvent) { | ||
@@ -13,27 +8,1 @@ return deleteEvent.tags.filter(isETag).map((t) => t[1]); | ||
} | ||
/** Creates a NIP-09 delete event for an array of events */ | ||
export function createDeleteEvent(events, message) { | ||
const eventPointers = []; | ||
const addressPointers = []; | ||
const eventKinds = new Set(); | ||
for (const event of events) { | ||
eventKinds.add(event.kind); | ||
eventPointers.push({ id: event.id }); | ||
if (isParameterizedReplaceableKind(event.kind)) { | ||
const identifier = getTagValue(event, "d"); | ||
if (!identifier) | ||
throw new Error("Event missing identifier"); | ||
addressPointers.push({ pubkey: event.pubkey, kind: event.kind, identifier }); | ||
} | ||
} | ||
return { | ||
kind: kinds.EventDeletion, | ||
content: message ?? "", | ||
tags: [ | ||
...eventPointers.map(getETagFromEventPointer), | ||
...addressPointers.map(getATagFromAddressPointer), | ||
...Array.from(eventKinds).map((k) => ["k", String(k)]), | ||
], | ||
created_at: unixNow(), | ||
}; | ||
} |
@@ -0,1 +1,2 @@ |
import { describe, beforeEach, it, expect } from "vitest"; |
import { getHiddenTags, unixNow, unlockHiddenTags } from "applesauce-core/helpers"; |
@@ -2,0 +3,0 @@ import { finalizeEvent, generateSecretKey, getPublicKey, kinds, nip04 } from "nostr-tools"; |
@@ -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/"])); | ||
}); | ||
}); | ||
}); |
@@ -18,8 +18,22 @@ import { AddressPointer, DecodeResult, EventPointer, ProfilePointer } from "nostr-tools/nip19"; | ||
export declare function encodeDecodeResult(result: DecodeResult): "" | `nprofile1${string}` | `nevent1${string}` | `naddr1${string}` | `nsec1${string}` | `npub1${string}` | `note1${string}`; | ||
/** @throws */ | ||
export declare function getEventPointerFromTag(tag: string[]): EventPointer; | ||
/** @throws */ | ||
export declare function getAddressPointerFromTag(tag: string[]): AddressPointer; | ||
/** @throws */ | ||
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 */ | ||
@@ -31,7 +45,13 @@ export declare function getPointerFromTag(tag: string[]): DecodeResult | null; | ||
export declare function getCoordinateFromAddressPointer(pointer: AddressPointer): string; | ||
/** Returns a tag for an address pointer */ | ||
export declare function getATagFromAddressPointer(pointer: AddressPointer): ["a", ...string[]]; | ||
/** Returns a tag for an event pointer */ | ||
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; |
@@ -5,2 +5,3 @@ import { naddrEncode, neventEncode, noteEncode, nprofileEncode, npubEncode, nsecEncode, } from "nostr-tools/nip19"; | ||
import { getTagValue } from "./index.js"; | ||
import { isParameterizedReplaceableKind } from "nostr-tools/kinds"; | ||
export function parseCoordinate(a, requireD = false, silent = true) { | ||
@@ -69,4 +70,7 @@ const parts = a.split(":"); | ||
} | ||
/** @throws */ | ||
export function getEventPointerFromTag(tag) { | ||
/** | ||
* Gets an EventPointer form a common "e" tag | ||
* @throws | ||
*/ | ||
export function getEventPointerFromETag(tag) { | ||
if (!tag[1]) | ||
@@ -77,10 +81,23 @@ throw new Error("Missing event id in tag"); | ||
pointer.relays = safeRelayUrls([tag[2]]); | ||
// get author from NIP-18 quote tags and nip-22 comments | ||
if ((tag[0] === "q" || tag[0] === "e" || tag[0] === "E") && tag[3] && tag[3].length === 64) { | ||
return pointer; | ||
} | ||
/** | ||
* 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; | ||
} | ||
/** @throws */ | ||
export function getAddressPointerFromTag(tag) { | ||
/** | ||
* Get an AddressPointer from an "a" tag | ||
* @throws | ||
*/ | ||
export function getAddressPointerFromATag(tag) { | ||
if (!tag[1]) | ||
@@ -93,4 +110,7 @@ throw new Error("Missing coordinate in tag"); | ||
} | ||
/** @throws */ | ||
export function getProfilePointerFromTag(tag) { | ||
/** | ||
* Gets a ProfilePointer from a "p" tag | ||
* @throws | ||
*/ | ||
export function getProfilePointerFromPTag(tag) { | ||
if (!tag[1]) | ||
@@ -108,13 +128,13 @@ throw new Error("Missing pubkey in tag"); | ||
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: getEventPointerFromTag(tag) }; | ||
return { type: "nevent", data: getEventPointerFromETag(tag) }; | ||
} | ||
@@ -138,11 +158,30 @@ } | ||
} | ||
/** Returns a tag for an address pointer */ | ||
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, | ||
}; | ||
} | ||
/** Returns a tag for an event pointer */ | ||
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, | ||
}; | ||
} | ||
@@ -149,0 +188,0 @@ /** Returns a pointer for a given event */ |
@@ -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 @@ } |
@@ -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"; | ||
@@ -38,3 +38,3 @@ export const ZapRequestSymbol = Symbol.for("zap-request"); | ||
const a = zap.tags.find(isATag); | ||
return a ? getAddressPointerFromTag(a) : null; | ||
return a ? getAddressPointerFromATag(a) : null; | ||
}); | ||
@@ -46,3 +46,3 @@ } | ||
const e = zap.tags.find(isETag); | ||
return e ? getEventPointerFromTag(e) : null; | ||
return e ? getEventPointerFromETag(e) : null; | ||
}); | ||
@@ -49,0 +49,0 @@ } |
@@ -24,3 +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 replies for an event */ | ||
/** 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[]>; |
@@ -7,2 +7,3 @@ import { kinds } from "nostr-tools"; | ||
import { getEventUID, getReplaceableUID, getTagValue, isEvent } from "../helpers/event.js"; | ||
import { COMMENT_KIND } from "../helpers/comment.js"; | ||
const defaultOptions = { | ||
@@ -69,3 +70,3 @@ kinds: [kinds.ShortTextNote], | ||
} | ||
/** A query that gets all legacy and NIP-10 replies for an event */ | ||
/** A query that gets all legacy and NIP-10, and NIP-22 replies for an event */ | ||
export function RepliesQuery(event, overrideKinds) { | ||
@@ -75,3 +76,3 @@ return { | ||
run: (events) => { | ||
const kinds = overrideKinds || event.kind === 1 ? [1, 1111] : [1111]; | ||
const kinds = overrideKinds || event.kind === 1 ? [1, COMMENT_KIND] : [COMMENT_KIND]; | ||
const filter = { kinds }; | ||
@@ -88,3 +89,3 @@ if (isEvent(parent) || isEventPointer(event)) | ||
return events.filter((e) => { | ||
const refs = interpretThreadTags(e); | ||
const refs = interpretThreadTags(e.tags); | ||
return refs.reply?.e?.[1] === event.id || refs.reply?.a?.[1] === address; | ||
@@ -91,0 +92,0 @@ }); |
{ | ||
"name": "applesauce-core", | ||
"version": "0.0.0-next-20241211183908", | ||
"version": "0.0.0-next-20241212192616", | ||
"description": "", | ||
@@ -65,18 +65,7 @@ "type": "module", | ||
"devDependencies": { | ||
"@jest/globals": "^29.7.0", | ||
"@types/debug": "^4.1.12", | ||
"@types/hash-sum": "^1.0.2", | ||
"@types/jest": "^29.5.13", | ||
"jest": "^29.7.0", | ||
"jest-extended": "^4.0.2", | ||
"typescript": "^5.6.3" | ||
"typescript": "^5.6.3", | ||
"vitest": "^2.1.8" | ||
}, | ||
"jest": { | ||
"roots": [ | ||
"dist" | ||
], | ||
"setupFilesAfterEnv": [ | ||
"jest-extended/all" | ||
] | ||
}, | ||
"funding": { | ||
@@ -89,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 |
128944
4
101
3288