Remove Fluent, Tags
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import {uniq, identity, flatten, pushToMapKey, intersection, tryCatch, now} from '@welshman/lib'
|
import {uniq, identity, flatten, pushToMapKey, intersection, tryCatch, now} from '@welshman/lib'
|
||||||
import type {TrustedEvent, Filter} from '@welshman/util'
|
import type {TrustedEvent, Filter} from '@welshman/util'
|
||||||
import {Tags, intersectFilters, matchFilter, getAddress, getIdFilters, unionFilters} from '@welshman/util'
|
import {intersectFilters, matchFilter, getAddress, getIdFilters, unionFilters} from '@welshman/util'
|
||||||
import type {CreatedAtItem, RequestItem, ListItem, LabelItem, WOTItem, DVMItem, Scope, Feed, FeedOptions} from './core'
|
import type {CreatedAtItem, RequestItem, ListItem, LabelItem, WOTItem, DVMItem, Scope, Feed, FeedOptions} from './core'
|
||||||
import {getFeedArgs, feedsFromTags} from './utils'
|
import {getFeedArgs, feedsFromTags} from './utils'
|
||||||
import {FeedType} from './core'
|
import {FeedType} from './core'
|
||||||
@@ -110,7 +110,7 @@ export class FeedCompiler {
|
|||||||
this.options.requestDVM({
|
this.options.requestDVM({
|
||||||
...request,
|
...request,
|
||||||
onEvent: async (e: TrustedEvent) => {
|
onEvent: async (e: TrustedEvent) => {
|
||||||
const tags = Tags.wrap(await tryCatch(() => JSON.parse(e.content)) || [])
|
const tags = await tryCatch(() => JSON.parse(e.content)) || []
|
||||||
|
|
||||||
for (const feed of feedsFromTags(tags, mappings)) {
|
for (const feed of feedsFromTags(tags, mappings)) {
|
||||||
feeds.push(feed)
|
feeds.push(feed)
|
||||||
@@ -231,7 +231,7 @@ export class FeedCompiler {
|
|||||||
const event = eventsByAddress.get(address)
|
const event = eventsByAddress.get(address)
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
for (const feed of feedsFromTags(Tags.fromEvent(event), mappings)) {
|
for (const feed of feedsFromTags(event.tags, mappings)) {
|
||||||
feeds.push(feed)
|
feeds.push(feed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,7 +271,7 @@ export class FeedCompiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return feedsFromTags(Tags.wrap(tags), mappings)
|
return feedsFromTags(tags, mappings)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {ensureNumber} from '@welshman/lib'
|
import {ensureNumber} from '@welshman/lib'
|
||||||
import type {Filter} from '@welshman/util'
|
import type {Filter} from '@welshman/util'
|
||||||
import {Tags} from '@welshman/util'
|
import {getTagValues} from '@welshman/util'
|
||||||
import {
|
import {
|
||||||
FeedType,
|
FeedType,
|
||||||
Feed,
|
Feed,
|
||||||
@@ -110,15 +110,13 @@ export const defaultTagFeedMappings: TagFeedMapping[] = [
|
|||||||
['t', [FeedType.Tag, '#t']],
|
['t', [FeedType.Tag, '#t']],
|
||||||
]
|
]
|
||||||
|
|
||||||
export const feedsFromTags = (tags: Tags, mappings?: TagFeedMapping[]) => {
|
export const feedsFromTags = (tags: string[][], mappings?: TagFeedMapping[]) => {
|
||||||
const feeds = []
|
const feeds = []
|
||||||
|
|
||||||
for (const [tagName, templateFeed] of mappings || defaultTagFeedMappings) {
|
for (const [tagName, templateFeed] of mappings || defaultTagFeedMappings) {
|
||||||
const filterTags = tags.whereKey(tagName)
|
let values: any[] = getTagValues(tagName, tags)
|
||||||
|
|
||||||
if (filterTags.exists()) {
|
|
||||||
let values: string[] | number[] = filterTags.values().valueOf()
|
|
||||||
|
|
||||||
|
if (values.length > 0) {
|
||||||
if (isKindFeed(templateFeed)) {
|
if (isKindFeed(templateFeed)) {
|
||||||
values = values.map(ensureNumber) as number[]
|
values = values.map(ensureNumber) as number[]
|
||||||
}
|
}
|
||||||
@@ -130,7 +128,7 @@ export const feedsFromTags = (tags: Tags, mappings?: TagFeedMapping[]) => {
|
|||||||
return feeds
|
return feeds
|
||||||
}
|
}
|
||||||
|
|
||||||
export const feedFromTags = (tags: Tags, mappings?: TagFeedMapping[]) =>
|
export const feedFromTags = (tags: string[][], mappings?: TagFeedMapping[]) =>
|
||||||
makeIntersectionFeed(...feedsFromTags(tags, mappings))
|
makeIntersectionFeed(...feedsFromTags(tags, mappings))
|
||||||
|
|
||||||
export const feedsFromFilter = ({since, until, ...filter}: Filter) => {
|
export const feedsFromFilter = ({since, until, ...filter}: Filter) => {
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
import {last} from './Tools'
|
|
||||||
|
|
||||||
export class Fluent<T> {
|
|
||||||
constructor(readonly xs: T[]) {}
|
|
||||||
|
|
||||||
static create() {
|
|
||||||
return this.from([])
|
|
||||||
}
|
|
||||||
|
|
||||||
static from<T>(xs: Iterable<T>) {
|
|
||||||
return new Fluent<T>(Array.from(xs))
|
|
||||||
}
|
|
||||||
|
|
||||||
clone<K extends Fluent<T>>(this: K, xs: T[]): K {
|
|
||||||
return new (this.constructor as { new (xs: T[]): K })(xs)
|
|
||||||
}
|
|
||||||
|
|
||||||
valueOf = () => this.xs
|
|
||||||
|
|
||||||
first = () => this.xs[0]
|
|
||||||
|
|
||||||
nth = (i: number) => this.xs[i]
|
|
||||||
|
|
||||||
last = () => last(this.xs)
|
|
||||||
|
|
||||||
count = () => this.xs.length
|
|
||||||
|
|
||||||
exists = () => this.xs.length > 0
|
|
||||||
|
|
||||||
has = (v: T) => this.xs.includes(v)
|
|
||||||
|
|
||||||
every = (f: (t: T) => boolean) => this.xs.every(f)
|
|
||||||
|
|
||||||
some = (f: (t: T) => boolean) => this.xs.some(f)
|
|
||||||
|
|
||||||
find = (f: (t: T) => boolean) => this.xs.find(f)
|
|
||||||
|
|
||||||
uniq = () => this.clone(Array.from(new Set(this.xs)))
|
|
||||||
|
|
||||||
slice = (a: number, b?: number) => this.clone(this.xs.slice(a, b))
|
|
||||||
|
|
||||||
take = (n: number) => this.slice(0, n)
|
|
||||||
|
|
||||||
drop = (n: number) => this.slice(n)
|
|
||||||
|
|
||||||
filter = (f: (t: T) => boolean) => this.clone(this.xs.filter(f))
|
|
||||||
|
|
||||||
reject = (f: (t: T) => boolean) => this.clone(this.xs.filter(t => !f(t)))
|
|
||||||
|
|
||||||
keep = (xs: T[]) => this.filter(x => xs.includes(x))
|
|
||||||
|
|
||||||
without = (xs: T[]) => this.reject(x => xs.includes(x))
|
|
||||||
|
|
||||||
map = (f: (t: T) => T) => this.clone(this.xs.map(f))
|
|
||||||
|
|
||||||
mapTo = <U>(f: (t: T) => U) => Fluent.from(this.xs.map(f))
|
|
||||||
|
|
||||||
flatMap = <U>(f: (t: T) => U[]) => Fluent.from(this.xs.flatMap(f))
|
|
||||||
|
|
||||||
forEach = (f: (t: T, i: number) => void) => this.xs.forEach(f)
|
|
||||||
|
|
||||||
join = (s: string) => this.valueOf().join(s)
|
|
||||||
|
|
||||||
set = (i: number, x: T) => this.clone([...this.xs.slice(0, i), x, ...this.xs.slice(i + 1)])
|
|
||||||
|
|
||||||
concat = (xs: T[]) => this.clone(this.xs.concat(xs))
|
|
||||||
|
|
||||||
append = (x: T) => this.concat([x])
|
|
||||||
|
|
||||||
prepend = (x: T) => this.clone([x].concat(this.xs))
|
|
||||||
}
|
|
||||||
@@ -1088,10 +1088,3 @@ export const hexToBech32 = (prefix: string, hex: string) =>
|
|||||||
*/
|
*/
|
||||||
export const bech32ToHex = (b32: string) =>
|
export const bech32ToHex = (b32: string) =>
|
||||||
utf8.encode(bech32.fromWords(bech32.decode(b32, false).words))
|
utf8.encode(bech32.fromWords(bech32.decode(b32, false).words))
|
||||||
|
|
||||||
/** Extracts non-function property names from type */
|
|
||||||
// https://github.com/microsoft/TypeScript/issues/4628#issuecomment-1147905253
|
|
||||||
export type OmitStatics<T, S extends string> =
|
|
||||||
T extends {new(...args: infer A): infer R} ?
|
|
||||||
{new(...args: A): R}&Omit<T, S> :
|
|
||||||
Omit<T, S>;
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export * from './Context'
|
export * from './Context'
|
||||||
export * from './Deferred'
|
export * from './Deferred'
|
||||||
export * from './Emitter'
|
export * from './Emitter'
|
||||||
export * from './Fluent'
|
|
||||||
export * from './LRUCache'
|
export * from './LRUCache'
|
||||||
export * from './Tools'
|
export * from './Tools'
|
||||||
export * from './Worker'
|
export * from './Worker'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {verifiedSymbol, getEventHash, verifyEvent} from 'nostr-tools'
|
import {verifiedSymbol, getEventHash, verifyEvent} from 'nostr-tools'
|
||||||
import {cached, pick, now} from '@welshman/lib'
|
import {cached, pick, now} from '@welshman/lib'
|
||||||
import {Tags} from './Tags'
|
import {getAncestorTagValues} from './Tags'
|
||||||
import {getAddress} from './Address'
|
import {getAddress} from './Address'
|
||||||
import {isEphemeralKind, isReplaceableKind, isPlainReplaceableKind, isParameterizedReplaceableKind} from './Kinds'
|
import {isEphemeralKind, isReplaceableKind, isPlainReplaceableKind, isParameterizedReplaceableKind} from './Kinds'
|
||||||
|
|
||||||
@@ -128,8 +128,8 @@ export const isPlainReplaceable = (e: EventTemplate) => isPlainReplaceableKind(e
|
|||||||
export const isParameterizedReplaceable = (e: EventTemplate) => isParameterizedReplaceableKind(e.kind)
|
export const isParameterizedReplaceable = (e: EventTemplate) => isParameterizedReplaceableKind(e.kind)
|
||||||
|
|
||||||
export const isChildOf = (child: EventContent, parent: HashedEvent) => {
|
export const isChildOf = (child: EventContent, parent: HashedEvent) => {
|
||||||
const {roots, replies} = Tags.fromEvent(child).ancestors()
|
const {roots, replies} = getAncestorTagValues(child.tags)
|
||||||
const parentIds = (replies.exists() ? replies : roots).values().valueOf()
|
const parentIds = replies.length > 0 ? replies : roots
|
||||||
|
|
||||||
return getIdAndAddress(parent).some(x => parentIds.includes(x))
|
return getIdAndAddress(parent).some(x => parentIds.includes(x))
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-165
@@ -1,170 +1,7 @@
|
|||||||
import type {OmitStatics} from "@welshman/lib"
|
import {uniq, uniqBy, mapVals, nth, nthEq, ensurePlural} from "@welshman/lib"
|
||||||
import {Fluent, uniq, uniqBy, mapVals, nth, nthEq, ensurePlural} from "@welshman/lib"
|
import {isRelayUrl, isShareableRelayUrl} from "./Relay"
|
||||||
import {isRelayUrl, isShareableRelayUrl, normalizeRelayUrl} from "./Relay"
|
|
||||||
import {Address} from "./Address"
|
import {Address} from "./Address"
|
||||||
|
|
||||||
export class Tag extends (Fluent<string> as OmitStatics<typeof Fluent<string>, "from">) {
|
|
||||||
static from = (xs: Iterable<string>) => new Tag(Array.from(xs))
|
|
||||||
|
|
||||||
static fromId = (id: string) => new Tag(["e", id])
|
|
||||||
|
|
||||||
static fromIdentifier = (identifier: string) => new Tag(["d", identifier])
|
|
||||||
|
|
||||||
static fromTopic = (topic: string) => new Tag(["t", topic])
|
|
||||||
|
|
||||||
static fromPubkey = (pubkey: string) => new Tag(["p", pubkey])
|
|
||||||
|
|
||||||
static fromAddress = (address: string, relay = "") => new Tag(["a", address, relay])
|
|
||||||
|
|
||||||
key = () => this.xs[0]
|
|
||||||
|
|
||||||
value = () => this.xs[1]
|
|
||||||
|
|
||||||
entry = () => this.xs.slice(0, 2)
|
|
||||||
|
|
||||||
setKey = (k: string) => this.set(0, k)
|
|
||||||
|
|
||||||
setValue = (v: string) => this.set(1, v)
|
|
||||||
|
|
||||||
isAddress = (kind?: number) => this.key() === "a" && this.value()?.startsWith(`${kind}:`)
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Tags extends (Fluent<Tag> as OmitStatics<typeof Fluent<Tag>, "from">) {
|
|
||||||
static from = (p: Iterable<Tag>) => new Tags(Array.from(p))
|
|
||||||
|
|
||||||
static wrap = (p: Iterable<string[]>) => new Tags(Array.from(p).map(Tag.from))
|
|
||||||
|
|
||||||
static fromEvent = (event: {tags: string[][]}) => Tags.wrap(event?.tags || [])
|
|
||||||
|
|
||||||
static fromEvents = (events: {tags: string[][]}[]) => Tags.wrap(events.flatMap(e => e.tags || []))
|
|
||||||
|
|
||||||
static fromIMeta = (imeta: string[]) => Tags.wrap(imeta.map((m: string) => m.split(" ")))
|
|
||||||
|
|
||||||
unwrap = () => this.xs.map(tag => tag.valueOf())
|
|
||||||
|
|
||||||
whereKey = (key: string) => this.filter(t => t.key() === key)
|
|
||||||
|
|
||||||
whereValue = (value: string) => this.filter(t => t.value() === value)
|
|
||||||
|
|
||||||
filterByKey = (keys: string[]) => this.filter(t => keys.includes(t.key()))
|
|
||||||
|
|
||||||
filterByValue = (values: string[]) => this.filter(t => values.includes(t.value()))
|
|
||||||
|
|
||||||
rejectByKey = (keys: string[]) => this.reject(t => keys.includes(t.key()))
|
|
||||||
|
|
||||||
rejectByValue = (values: string[]) => this.reject(t => values.includes(t.value()))
|
|
||||||
|
|
||||||
get = (key: string) => this.whereKey(key).first()
|
|
||||||
|
|
||||||
keys = () => this.mapTo(t => t.key())
|
|
||||||
|
|
||||||
values = (key?: string | string[]) =>
|
|
||||||
(key ? this.filterByKey(ensurePlural(key)) : this).mapTo(t => t.value())
|
|
||||||
|
|
||||||
entries = () => this.mapTo(t => t.entry())
|
|
||||||
|
|
||||||
relays = () =>
|
|
||||||
this.flatMap((t: Tag) =>
|
|
||||||
t
|
|
||||||
.valueOf()
|
|
||||||
.filter(isRelayUrl)
|
|
||||||
.map(url => normalizeRelayUrl(url))
|
|
||||||
).uniq()
|
|
||||||
|
|
||||||
topics = () =>
|
|
||||||
this.whereKey("t")
|
|
||||||
.values()
|
|
||||||
.map((t: string) => t.replace(/^#/, ""))
|
|
||||||
|
|
||||||
ancestors = (x?: boolean) => {
|
|
||||||
const {roots, replies, mentions} = getAncestorTags(this.unwrap())
|
|
||||||
|
|
||||||
return {
|
|
||||||
roots: Tags.wrap(roots),
|
|
||||||
replies: Tags.wrap(replies),
|
|
||||||
mentions: Tags.wrap(mentions),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
roots = () => this.ancestors().roots
|
|
||||||
|
|
||||||
replies = () => this.ancestors().replies
|
|
||||||
|
|
||||||
mentions = () => this.ancestors().mentions
|
|
||||||
|
|
||||||
root = () => {
|
|
||||||
const roots = this.roots()
|
|
||||||
|
|
||||||
return roots.get("e") || roots.get("a")
|
|
||||||
}
|
|
||||||
|
|
||||||
reply = () => {
|
|
||||||
const replies = this.replies()
|
|
||||||
|
|
||||||
return replies.get("e") || replies.get("a")
|
|
||||||
}
|
|
||||||
|
|
||||||
parents = () => {
|
|
||||||
const {roots, replies} = this.ancestors()
|
|
||||||
|
|
||||||
return replies.exists() ? replies : roots
|
|
||||||
}
|
|
||||||
|
|
||||||
parent = () => {
|
|
||||||
const parents = this.parents()
|
|
||||||
|
|
||||||
return parents.get("e") || parents.get("a")
|
|
||||||
}
|
|
||||||
|
|
||||||
asObject = () => {
|
|
||||||
const result: Record<string, string> = {}
|
|
||||||
|
|
||||||
for (const t of this.xs) {
|
|
||||||
result[t.key()] = t.value()
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
imeta = (url: string) => {
|
|
||||||
for (const tag of this.whereKey("imeta").xs) {
|
|
||||||
const tags = Tags.fromIMeta(tag.drop(1).valueOf())
|
|
||||||
|
|
||||||
if (tags.get("url")?.value() === url) {
|
|
||||||
return tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic setters
|
|
||||||
|
|
||||||
addTag = (...args: string[]) => this.append(Tag.from(args))
|
|
||||||
|
|
||||||
setTag = (k: string, ...args: string[]) => this.rejectByKey([k]).addTag(k, ...args)
|
|
||||||
|
|
||||||
// Images
|
|
||||||
|
|
||||||
addImages = (imeta: Tags[]) =>
|
|
||||||
this.concat(imeta.map(tags => Tag.from(["image", tags.get("url").value()])))
|
|
||||||
|
|
||||||
removeImages = () => this.rejectByKey(["image"])
|
|
||||||
|
|
||||||
setImages = (imeta: Tags[]) => this.removeImages().addImages(imeta)
|
|
||||||
|
|
||||||
// IMeta
|
|
||||||
|
|
||||||
addIMeta = (imeta: Tags[]) =>
|
|
||||||
this.concat(imeta.map(tags => Tag.from(["imeta", ...tags.valueOf().map(xs => xs.join(" "))])))
|
|
||||||
|
|
||||||
removeIMeta = () => this.rejectByKey(["imeta"])
|
|
||||||
|
|
||||||
setIMeta = (imeta: Tags[]) => this.removeIMeta().addIMeta(imeta)
|
|
||||||
}
|
|
||||||
|
|
||||||
// New, simpler version
|
|
||||||
|
|
||||||
export const getTags = (types: string | string[], tags: string[][]) => {
|
export const getTags = (types: string | string[], tags: string[][]) => {
|
||||||
types = ensurePlural(types)
|
types = ensurePlural(types)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user