From 94f6e676a7eef654120bba96f644c52aa7a80319 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Wed, 3 Apr 2024 10:49:54 -0700 Subject: [PATCH] Back to lists --- packages/lib/Tools.ts | 26 +++++++++++++++++++++++++- packages/util/Filters.ts | 18 +++++++++++++++++- packages/util/Router.ts | 35 ++++++++++++++++------------------- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/packages/lib/Tools.ts b/packages/lib/Tools.ts index 4107316..30723a6 100644 --- a/packages/lib/Tools.ts +++ b/packages/lib/Tools.ts @@ -1,6 +1,8 @@ import {throttle} from 'throttle-debounce' import {bech32, utf8} from "@scure/base" +export type Nil = null | undefined + export const now = () => Math.round(Date.now() / 1000) export const nth = (i: number) => (xs: T[]) => xs[i] @@ -13,6 +15,10 @@ export const prop = (k: string) => (x: Record) => x[k] export const identity = (x: T) => x +export const inc = (x: number | Nil) => (x || 0) + 1 + +export const dec = (x: number | Nil) => (x || 0) - 1 + export const max = (xs: number[]) => xs.reduce((a, b) => Math.max(a, b), 0) export const min = (xs: number[]) => xs.reduce((a, b) => Math.min(a, b), 0) @@ -41,6 +47,9 @@ export const stripProtocol = (url: string) => url.replace(/.*:\/\//, "") export const ensurePlural = (x: T | T[]) => (x instanceof Array ? x : [x]) +export const hash = (s: string) => + Math.abs(s.split("").reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0)).toString() + export const sortBy = (f: (x: T) => number, xs: T[]) => xs.sort((a: T, b: T) => f(a) - f(b)) @@ -62,8 +71,9 @@ export const groupBy = (f: (x: T) => string, xs: T[]) => { export const sample = (n: number, xs: T[]) => { const result: T[] = [] + const limit = Math.min(n, xs.length) - for (let i = 0; i < n; i++) { + for (let i = 0; i < limit; i++) { result.push(xs.splice(Math.floor(xs.length * Math.random()), 1)[0]) } @@ -119,6 +129,20 @@ export const batch = (t: number, f: (xs: T[]) => void) => { } } +export const addToMapKey = (m: Map>, k: string, v: T) => { + const a = m.get(k) || new Set() + + a.add(v) + m.set(k, a) +} + +export const pushToMapKey = (m: Map, k: string, v: T) => { + const a = m.get(k) || [] + + a.push(v) + m.set(k, a) +} + export const hexToBech32 = (prefix: string, url: string) => bech32.encode(prefix, bech32.toWords(utf8.decode(url)), false) diff --git a/packages/util/Filters.ts b/packages/util/Filters.ts index 4803c72..61401ba 100644 --- a/packages/util/Filters.ts +++ b/packages/util/Filters.ts @@ -1,6 +1,6 @@ import type {Event} from 'nostr-tools' import {matchFilter as nostrToolsMatchFilter} from 'nostr-tools' -import {prop, groupBy, randomId, uniq} from '@coracle.social/lib' +import {prop, hash, identity, groupBy, randomId, uniq} from '@coracle.social/lib' import type {Rumor} from './Events' import {decodeAddress, addressFromEvent, encodeAddress} from './Address' import {isReplaceableKind} from './Kinds' @@ -47,6 +47,22 @@ export const matchFilters = (filters: Filter[], event: Event) => { return false } +export const getFilterId = (filter: Filter) => { + const keys = Object.keys(filter) + + keys.sort() + + const parts = [] + for (const k of keys) { + const v = filter[k as keyof Filter] + const s = Array.isArray(v) ? v.join(',') : v + + parts.push([k, v].join(':')) + } + + return hash(parts.join('|')) +} + export const calculateFilterGroup = ({since, until, limit, search, ...filter}: Filter) => { const group = Object.keys(filter) diff --git a/packages/util/Router.ts b/packages/util/Router.ts index 2aa63c2..a4e80fe 100644 --- a/packages/util/Router.ts +++ b/packages/util/Router.ts @@ -1,11 +1,11 @@ import type {EventTemplate} from 'nostr-tools' -import {first, identity, sortBy, uniq, shuffle} from '@coracle.social/lib' +import {first, identity, sortBy, uniq, shuffle, pushToMapKey} from '@coracle.social/lib' import {Tags, Tag} from '@coracle.social/util' import type {Rumor} from './Events' import {getAddress, isReplaceable} from './Events' import {isShareableRelayUrl} from './Relays' import {GROUP_DEFINITION, COMMUNITY_DEFINITION} from './Kinds' -import {addressFromEvent, decodeAddress, isCommunityAddress, isGroupAddress} from './Address' +import {addressFromEvent, decodeAddress, isContextAddress, isCommunityAddress, isGroupAddress} from './Address' export enum RelayMode { Read = "read", @@ -19,20 +19,21 @@ export type RouterOptions = { getPubkeyRelays: (pubkey: string, mode?: RelayMode) => string[] getStaticRelays: () => string[] getIndexerRelays: () => string[] + getSearchRelays: () => string[] getRelayQuality: (url: string) => number getRedundancy: () => number } -export type ValuesByRelay = Map> +export type ValuesByRelay = Map export type RelayValues = { relay: string - values: Set + values: string[] } export type ValueRelays = { value: string - relays: Set + relays: string[] } export type FallbackPolicy = (count: number, limit: number) => number @@ -66,7 +67,7 @@ export class Router { // Utilities for creating ValueRelays - selection = (value: string, relays: Iterable) => ({value, relays: new Set(relays)}) + selection = (value: string, relays: Iterable) => ({value, relays: Array.from(relays)}) selections = (values: string[], relays: string[]) => values.map(value => this.selection(value, relays)) @@ -77,10 +78,10 @@ export class Router { // Utilities for processing hints relaySelectionsFromMap = (valuesByRelay: ValuesByRelay) => - Array.from(valuesByRelay).map(([relay, values]: [string, Set]) => ({relay, values})) + Array.from(valuesByRelay).map(([relay, values]: [string, string[]]) => ({relay, values: uniq(values)})) scoreRelaySelection = ({values, relay}: RelayValues) => - values.size * this.options.getRelayQuality(relay) + values.length * this.options.getRelayQuality(relay) sortRelaySelections = (relaySelections: RelayValues[]) => { const scores = new Map() @@ -207,6 +208,9 @@ export class Router { WithinMultipleContexts = (addresses: string[]) => this.merge(addresses.map(this.WithinContext)) + Search = (term: string) => + this.product([term], this.options.getSearchRelays()) + // Fallback policies addNoFallbacks = (count: number, redundancy: number) => count @@ -267,13 +271,13 @@ export class RouterScenario { getPolicy = () => this.options.policy || this.router.addMaximalFallbacks - getLimit = () => this.options.limit + getLimit = () => this.options.limit || Infinity getSelections = () => { const valuesByRelay: ValuesByRelay = new Map() for (const {value, relays} of this.selections) { for (const relay of relays) { - addToKey(valuesByRelay, relay, value) + pushToMapKey(valuesByRelay, relay, value) } } @@ -293,7 +297,7 @@ export class RouterScenario { } if (values.size > 0) { - result.set(relay, values) + result.set(relay, Array.from(values)) } } @@ -305,7 +309,7 @@ export class RouterScenario { if (fallbacksNeeded > 0) { for (const relay of fallbacks.slice(0, fallbacksNeeded)) { - addToKey(result, relay, value) + pushToMapKey(result, relay, value) } } } @@ -321,10 +325,3 @@ export class RouterScenario { getUrl = () => first(this.getUrls()) } - -const addToKey = (m: Map>, k: string, v: T) => { - const a = m.get(k) || new Set() - - a.add(v) - m.set(k, a) -}