Compile intersections

This commit is contained in:
Jon Staab
2024-04-29 12:51:47 -07:00
parent b88c074d86
commit 00b142243b
8 changed files with 71 additions and 12 deletions
+3 -3
View File
@@ -3064,7 +3064,7 @@
"version": "0.0.2", "version": "0.0.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@welshman/util": "0.0.2" "@welshman/util": "0.0.3"
}, },
"devDependencies": { "devDependencies": {
"gts": "^5.0.1", "gts": "^5.0.1",
@@ -3102,7 +3102,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@welshman/lib": "0.0.2", "@welshman/lib": "0.0.2",
"@welshman/util": "0.0.2", "@welshman/util": "0.0.3",
"isomorphic-ws": "^5.0.0", "isomorphic-ws": "^5.0.0",
"ws": "^8.16.0" "ws": "^8.16.0"
}, },
@@ -3115,7 +3115,7 @@
}, },
"packages/util": { "packages/util": {
"name": "@welshman/util", "name": "@welshman/util",
"version": "0.0.2", "version": "0.0.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@welshman/lib": "0.0.2", "@welshman/lib": "0.0.2",
+25 -2
View File
@@ -1,6 +1,6 @@
import {uniq, tryCatch, now, isNil} from '@welshman/lib' import {uniq, tryCatch, now, isNil} from '@welshman/lib'
import type {Rumor, Filter} from '@welshman/util' import type {Rumor, Filter} from '@welshman/util'
import {Tags, getIdFilters, mergeFilters} from '@welshman/util' import {Tags, intersectFilters, BOGUS_RELAY_URL, getIdFilters, unionFilters} from '@welshman/util'
import type {RequestItem, DVMItem, Scope, Feed, DynamicFilter, FeedOptions} from './core' import type {RequestItem, DVMItem, Scope, Feed, DynamicFilter, FeedOptions} from './core'
import {FeedType, getSubFeeds} from './core' import {FeedType, getSubFeeds} from './core'
@@ -19,6 +19,7 @@ export class FeedCompiler<E extends Rumor> {
switch(type) { switch(type) {
case FeedType.Relay: case FeedType.Relay:
case FeedType.Union: case FeedType.Union:
case FeedType.Intersection:
return getSubFeeds([type, ...feed] as Feed).every(this.canCompile) return getSubFeeds([type, ...feed] as Feed).every(this.canCompile)
case FeedType.Filter: case FeedType.Filter:
case FeedType.List: case FeedType.List:
@@ -33,6 +34,8 @@ export class FeedCompiler<E extends Rumor> {
switch(type) { switch(type) {
case FeedType.Union: case FeedType.Union:
return await this._compileUnion(feed as Feed[]) return await this._compileUnion(feed as Feed[])
case FeedType.Intersection:
return await this._compileIntersection(feed as Feed[])
case FeedType.Relay: case FeedType.Relay:
/* eslint no-case-declarations: 0 */ /* eslint no-case-declarations: 0 */
const {relays, filters} = await this._compileUnion(feed.slice(1) as Feed[]) const {relays, filters} = await this._compileUnion(feed.slice(1) as Feed[])
@@ -72,10 +75,30 @@ export class FeedCompiler<E extends Rumor> {
return { return {
relays: uniq(relays), relays: uniq(relays),
filters: mergeFilters(filters), filters: unionFilters(filters),
} }
} }
async _compileIntersection(feeds: Feed[]): Promise<RequestItem> {
const items = await Promise.all(feeds.map(this.compile))
const filters = intersectFilters(items.map(item => item.filters))
let relays = uniq(items.flatMap(item => item.relays))
let hasRelays = relays.length > 0
items.forEach((item, i) => {
if (item.relays.length > 0) {
relays = relays.filter(relay => item.relays.includes(relay))
}
})
if (hasRelays && relays.length === 0) {
relays.push(BOGUS_RELAY_URL)
}
return {relays, filters}
}
async _compileLists(addresses: string[]): Promise<RequestItem> { async _compileLists(addresses: string[]): Promise<RequestItem> {
const events: E[] = [] const events: E[] = []
+1 -1
View File
@@ -31,6 +31,6 @@
"typescript": "~5.1.6" "typescript": "~5.1.6"
}, },
"dependencies": { "dependencies": {
"@welshman/util": "0.0.2" "@welshman/util": "0.0.3"
} }
} }
+2 -2
View File
@@ -1,7 +1,7 @@
import type {Event} from 'nostr-tools' import type {Event} from 'nostr-tools'
import {Emitter, randomId, groupBy, batch, defer, uniq, uniqBy} from '@welshman/lib' import {Emitter, randomId, groupBy, batch, defer, uniq, uniqBy} from '@welshman/lib'
import type {Deferred} from '@welshman/lib' import type {Deferred} from '@welshman/lib'
import {matchFilters, mergeFilters} from '@welshman/util' import {matchFilters, unionFilters} from '@welshman/util'
import type {Filter} from '@welshman/util' import type {Filter} from '@welshman/util'
import {Tracker} from "./Tracker" import {Tracker} from "./Tracker"
import {Connection} from './Connection' import {Connection} from './Connection'
@@ -83,7 +83,7 @@ export const mergeSubscriptions = (subs: Subscription[]) => {
const mergedSub = makeSubscription({ const mergedSub = makeSubscription({
relays: [relay], relays: [relay],
timeout: callerSubs[0].request.timeout, timeout: callerSubs[0].request.timeout,
filters: mergeFilters(callerSubs.flatMap((sub: Subscription) => sub.request.filters)), filters: unionFilters(callerSubs.flatMap((sub: Subscription) => sub.request.filters)),
}) })
for (const {id, emitter} of callerSubs) { for (const {id, emitter} of callerSubs) {
+1 -1
View File
@@ -33,7 +33,7 @@
}, },
"dependencies": { "dependencies": {
"@welshman/lib": "0.0.2", "@welshman/lib": "0.0.2",
"@welshman/util": "0.0.2", "@welshman/util": "0.0.3",
"isomorphic-ws": "^5.0.0", "isomorphic-ws": "^5.0.0",
"ws": "^8.16.0" "ws": "^8.16.0"
} }
+34 -2
View File
@@ -76,7 +76,7 @@ export const calculateFilterGroup = ({since, until, limit, search, ...filter}: F
return group.sort().join("-") return group.sort().join("-")
} }
export const mergeFilters = (filters: Filter[]) => { export const unionFilters = (filters: Filter[]) => {
const result = [] const result = []
for (const group of Object.values(groupBy(calculateFilterGroup, filters))) { for (const group of Object.values(groupBy(calculateFilterGroup, filters))) {
@@ -96,6 +96,38 @@ export const mergeFilters = (filters: Filter[]) => {
return result return result
} }
export const intersectFilters = (groups: Filter[][]) => {
let result = groups[0]
for (const filters of groups.slice(1)) {
result = result.flatMap(f1 => {
return filters.map(f2 => {
const f3: Filter = {}
for (const k of uniq([...Object.keys(f1), ...Object.keys(f2)]) as (keyof Filter)[]) {
if (k === 'since' || k === 'limit') {
f3[k] = Math.max(f1[k] || 0, f2[k] || 0)
} else if (k === 'until') {
f3[k] = Math.min(f1[k] || f2[k] || 0, f2[k] || f1[k] || 0)
} else if (k === 'search') {
if (f1[k] && f2[k] && f1[k] !== f2[k]) {
f3[k] = [f1[k], f2[k]].join(' ')
} else {
f3[k] = f1[k] || f2[k]
}
} else {
f3[k] = uniq([...(f1[k] || []), ...(f2[k] || [])]) as any[]
}
}
return f3
})
})
}
return unionFilters(result)
}
export const getIdFilters = (idsOrAddresses: Iterable<string>) => { export const getIdFilters = (idsOrAddresses: Iterable<string>) => {
const ids = [] const ids = []
const aFilters = [] const aFilters = []
@@ -114,7 +146,7 @@ export const getIdFilters = (idsOrAddresses: Iterable<string>) => {
} }
} }
const filters = mergeFilters(aFilters) const filters = unionFilters(aFilters)
if (ids.length > 0) { if (ids.length > 0) {
filters.push({ids}) filters.push({ids})
+4
View File
@@ -1,5 +1,9 @@
import {normalizeUrl, stripProtocol} from '@welshman/lib' import {normalizeUrl, stripProtocol} from '@welshman/lib'
export const LOCAL_RELAY_URL = "local://welshman.relay"
export const BOGUS_RELAY_URL = "bogus://welshman.relay"
export const isShareableRelayUrl = (url: string) => export const isShareableRelayUrl = (url: string) =>
Boolean( Boolean(
typeof url === 'string' && typeof url === 'string' &&
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@welshman/util", "name": "@welshman/util",
"version": "0.0.2", "version": "0.0.3",
"author": "hodlbod", "author": "hodlbod",
"license": "MIT", "license": "MIT",
"description": "A collection of nostr-related utilities.", "description": "A collection of nostr-related utilities.",