Remove tsc-multi, re-install gts, apply autoformatting and linting

This commit is contained in:
Jon Staab
2024-12-17 10:59:27 -08:00
parent 0b86613161
commit f33e03740e
122 changed files with 2243 additions and 2178 deletions
+3 -8
View File
@@ -15,21 +15,16 @@
"exports": {
".": {
"types": "./build/src/index.d.ts",
"import": "./build/src/index.mjs",
"require": "./build/src/index.cjs"
"import": "./build/src/index.js",
"require": "./build/src/index.js"
}
},
"scripts": {
"pub": "npm run lint && npm run build && npm publish",
"build": "gts clean && tsc-multi",
"build": "gts clean && tsc",
"lint": "gts lint",
"fix": "gts fix"
},
"devDependencies": {
"gts": "^5.0.1",
"tsc-multi": "^1.1.0",
"typescript": "~5.1.6"
},
"dependencies": {
"@types/throttle-debounce": "^5.0.2",
"@welshman/dvm": "~0.0.11",
+4 -4
View File
@@ -1,7 +1,7 @@
import {readable, derived, type Readable} from 'svelte/store'
import {indexBy, type Maybe, now} from '@welshman/lib'
import {withGetter} from '@welshman/store'
import {getFreshness, setFreshnessThrottled} from './freshness'
import {readable, derived, type Readable} from "svelte/store"
import {indexBy, type Maybe, now} from "@welshman/lib"
import {withGetter} from "@welshman/store"
import {getFreshness, setFreshnessThrottled} from "./freshness.js"
export const collection = <T, LoadArgs extends any[]>({
name,
+6 -7
View File
@@ -1,9 +1,9 @@
import {get} from 'svelte/store'
import {ctx} from '@welshman/lib'
import {addToListPublicly, removeFromList, makeList, FOLLOWS, MUTES} from '@welshman/util'
import {userFollows, userMutes} from './user'
import {nip44EncryptToSelf} from './session'
import {publishThunk} from './thunk'
import {get} from "svelte/store"
import {ctx} from "@welshman/lib"
import {addToListPublicly, removeFromList, makeList, FOLLOWS, MUTES} from "@welshman/util"
import {userFollows, userMutes} from "./user.js"
import {nip44EncryptToSelf} from "./session.js"
import {publishThunk} from "./thunk.js"
export const unfollow = async (value: string) => {
const list = get(userFollows) || makeList({kind: FOLLOWS})
@@ -32,4 +32,3 @@ export const mute = async (tag: string[]) => {
return publishThunk({event, relays: ctx.app.router.FromUser().getUrls()})
}
+9 -7
View File
@@ -1,12 +1,15 @@
import {partition} from "@welshman/lib"
import {defaultOptimizeSubscriptions, getDefaultNetContext as originalGetDefaultNetContext} from "@welshman/net"
import {
defaultOptimizeSubscriptions,
getDefaultNetContext as originalGetDefaultNetContext,
} from "@welshman/net"
import type {Subscription, RelaysAndFilters, NetContext} from "@welshman/net"
import {LOCAL_RELAY_URL, isEphemeralKind, isDVMKind, unionFilters} from "@welshman/util"
import {LOCAL_RELAY_URL, isEphemeralKind, isDVMKind, unionFilters} from "@welshman/util"
import type {TrustedEvent, StampedEvent} from "@welshman/util"
import {tracker, repository} from './core'
import {makeRouter, getFilterSelections} from './router'
import {signer} from './session'
import type {Router} from './router'
import {tracker, repository} from "./core.js"
import {makeRouter, getFilterSelections} from "./router.js"
import {signer} from "./session.js"
import type {Router} from "./router.js"
export type AppContext = {
router: Router
@@ -52,4 +55,3 @@ export const getDefaultAppContext = (overrides: Partial<AppContext> = {}) => ({
requestTimeout: 3000,
...overrides,
})
+29 -23
View File
@@ -1,4 +1,4 @@
import {throttle} from '@welshman/lib'
import {throttle} from "@welshman/lib"
import {Repository, Relay} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {Tracker} from "@welshman/net"
@@ -13,33 +13,39 @@ export const tracker = new Tracker()
// Adapt above objects to stores
export const makeRepositoryStore = ({throttle: t = 300}: {throttle?: number} = {}) =>
custom(setter => {
let onUpdate = () => setter(repository)
custom(
setter => {
let onUpdate = () => setter(repository)
if (t) {
onUpdate = throttle(t, onUpdate)
}
if (t) {
onUpdate = throttle(t, onUpdate)
}
onUpdate()
repository.on('update', onUpdate)
onUpdate()
repository.on("update", onUpdate)
return () => repository.off('update', onUpdate)
}, {
set: (other: Repository) => repository.load(other.dump()),
})
return () => repository.off("update", onUpdate)
},
{
set: (other: Repository) => repository.load(other.dump()),
},
)
export const makeTrackerStore = ({throttle: t = 300}: {throttle?: number} = {}) =>
custom(setter => {
let onUpdate = () => setter(tracker)
custom(
setter => {
let onUpdate = () => setter(tracker)
if (t) {
onUpdate = throttle(t, onUpdate)
}
if (t) {
onUpdate = throttle(t, onUpdate)
}
onUpdate()
tracker.on('update', onUpdate)
onUpdate()
tracker.on("update", onUpdate)
return () => tracker.off('update', onUpdate)
}, {
set: (other: Tracker) => tracker.load(other.relaysById),
})
return () => tracker.off("update", onUpdate)
},
{
set: (other: Tracker) => tracker.load(other.relaysById),
},
)
+30 -29
View File
@@ -1,23 +1,20 @@
import {ctx, nthEq, now} from '@welshman/lib'
import {createEvent, getPubkeyTagValues} from '@welshman/util'
import {Scope, FeedController} from '@welshman/feeds'
import type {RequestOpts, FeedOptions, DVMOpts, Feed} from '@welshman/feeds'
import {makeDvmRequest, DVMEvent} from '@welshman/dvm'
import {makeSecret, Nip01Signer} from '@welshman/signer'
import {pubkey, signer} from './session'
import {getFilterSelections} from './router'
import {loadRelaySelections} from './relaySelections'
import {wotGraph, maxWot, getFollows, getNetwork, getFollowers} from './wot'
import {load} from './subscribe'
import {ctx, nthEq, now} from "@welshman/lib"
import {createEvent, getPubkeyTagValues} from "@welshman/util"
import {Scope, FeedController} from "@welshman/feeds"
import type {RequestOpts, FeedOptions, DVMOpts, Feed} from "@welshman/feeds"
import {makeDvmRequest, DVMEvent} from "@welshman/dvm"
import {makeSecret, Nip01Signer} from "@welshman/signer"
import {pubkey, signer} from "./session.js"
import {getFilterSelections} from "./router.js"
import {loadRelaySelections} from "./relaySelections.js"
import {wotGraph, maxWot, getFollows, getNetwork, getFollowers} from "./wot.js"
import {load} from "./subscribe.js"
export const request = async ({filters = [{}], relays = [], onEvent}: RequestOpts) => {
if (relays.length > 0) {
await load({onEvent, filters, relays})
} else {
await Promise.all(
getFilterSelections(filters)
.map(opts => load({onEvent, ...opts}))
)
await Promise.all(getFilterSelections(filters).map(opts => load({onEvent, ...opts})))
}
}
@@ -32,24 +29,23 @@ export const requestDVM = async ({kind, onEvent, ...request}: DVMOpts) => {
const tags = request.tags || []
const $signer = signer.get() || new Nip01Signer(makeSecret())
const pubkey = await $signer.getPubkey()
const relays =
request.relays
? ctx.app.router.FromRelays(request.relays).getUrls()
: ctx.app.router.FromPubkeys(getPubkeyTagValues(tags)).getUrls()
const relays = request.relays
? ctx.app.router.FromRelays(request.relays).getUrls()
: ctx.app.router.FromPubkeys(getPubkeyTagValues(tags)).getUrls()
if (!tags.some(nthEq(0, 'expiration'))) {
if (!tags.some(nthEq(0, "expiration"))) {
tags.push(["expiration", String(now() + 60)])
}
if (!tags.some(nthEq(0, 'relays'))) {
if (!tags.some(nthEq(0, "relays"))) {
tags.push(["relays", ...relays])
}
if (!tags.some(nthEq(1, 'user'))) {
if (!tags.some(nthEq(1, "user"))) {
tags.push(["param", "user", pubkey])
}
if (!tags.some(nthEq(1, 'max_results'))) {
if (!tags.some(nthEq(1, "max_results"))) {
tags.push(["param", "max_results", "200"])
}
@@ -72,11 +68,16 @@ export const getPubkeysForScope = (scope: string) => {
}
switch (scope) {
case Scope.Self: return [$pubkey]
case Scope.Follows: return getFollows($pubkey)
case Scope.Network: return getNetwork($pubkey)
case Scope.Followers: return getFollowers($pubkey)
default: return []
case Scope.Self:
return [$pubkey]
case Scope.Follows:
return getFollows($pubkey)
case Scope.Network:
return getNetwork($pubkey)
case Scope.Followers:
return getFollowers($pubkey)
default:
return []
}
}
@@ -94,7 +95,7 @@ export const getPubkeysForWOTRange = (min: number, max: number) => {
return pubkeys
}
type _FeedOptions = Partial<Omit<FeedOptions, 'feed'>> & {feed: Feed}
type _FeedOptions = Partial<Omit<FeedOptions, "feed">> & {feed: Feed}
export const createFeedController = (options: _FeedOptions) =>
new FeedController({
+8 -9
View File
@@ -1,17 +1,16 @@
import {FOLLOWS, asDecryptedEvent, readList} from '@welshman/util'
import {type TrustedEvent, type PublishedList} from '@welshman/util'
import {FOLLOWS, asDecryptedEvent, readList} from "@welshman/util"
import {type TrustedEvent, type PublishedList} from "@welshman/util"
import {type SubscribeRequestWithHandlers} from "@welshman/net"
import {deriveEventsMapped} from '@welshman/store'
import {repository} from './core'
import {load} from './subscribe'
import {collection} from './collection'
import {loadRelaySelections} from './relaySelections'
import {deriveEventsMapped} from "@welshman/store"
import {repository} from "./core.js"
import {load} from "./subscribe.js"
import {collection} from "./collection.js"
import {loadRelaySelections} from "./relaySelections.js"
export const follows = deriveEventsMapped<PublishedList>(repository, {
filters: [{kinds: [FOLLOWS]}],
itemToEvent: item => item.event,
eventToItem: (event: TrustedEvent) =>
readList(asDecryptedEvent(event)),
eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)),
})
export const {
+4 -4
View File
@@ -1,6 +1,6 @@
import {writable} from 'svelte/store'
import {assoc, batch} from '@welshman/lib'
import {withGetter} from '@welshman/store'
import {writable} from "svelte/store"
import {assoc, batch} from "@welshman/lib"
import {withGetter} from "@welshman/store"
export type FreshnessUpdate = {
ns: string
@@ -25,5 +25,5 @@ export const setFreshnessThrottled = batch(100, (updates: FreshnessUpdate[]) =>
}
return $freshness
})
}),
)
+30 -21
View File
@@ -1,8 +1,8 @@
import {writable, derived} from 'svelte/store'
import {writable, derived} from "svelte/store"
import {type SubscribeRequestWithHandlers} from "@welshman/net"
import {ctx, tryCatch, fetchJson, uniq, batcher, postJson, last} from '@welshman/lib'
import {collection} from './collection'
import {deriveProfile} from './profiles'
import {ctx, tryCatch, fetchJson, uniq, batcher, postJson, last} from "@welshman/lib"
import {collection} from "./collection.js"
import {deriveProfile} from "./profiles.js"
export type Handle = {
nip05: string
@@ -18,10 +18,14 @@ export async function queryProfile(nip05: string) {
if (!match) return undefined
const [_, name = '_', domain] = match
const [_, name = "_", domain] = match
try {
const {names, relays = {}, nip46 = {}} = await fetchJson(`https://${domain}/.well-known/nostr.json?name=${name}`)
const {
names,
relays = {},
nip46 = {},
} = await fetchJson(`https://${domain}/.well-known/nostr.json?name=${name}`)
const pubkey = names[name]
@@ -48,14 +52,19 @@ export const fetchHandles = async (nip05s: string[]) => {
// Use dufflepud if we it's set up to protect user privacy, otherwise fetch directly
if (base) {
const res: any = await tryCatch(async () => await postJson(`${base}/handle/info`, {handles: nip05s}))
const res: any = await tryCatch(
async () => await postJson(`${base}/handle/info`, {handles: nip05s}),
)
for (const {handle: nip05, info} of res?.data || []) {
handlesByNip05.set(nip05, info)
}
} else {
const results = await Promise.all(
nip05s.map(async nip05 => ({nip05, info: await tryCatch(async () => await queryProfile(nip05))}))
nip05s.map(async nip05 => ({
nip05,
info: await tryCatch(async () => await queryProfile(nip05)),
})),
)
for (const {nip05, info} of results) {
@@ -94,21 +103,21 @@ export const {
}),
})
export const deriveHandleForPubkey = (pubkey: string, request: Partial<SubscribeRequestWithHandlers> = {}) =>
derived(
[handlesByNip05, deriveProfile(pubkey, request)],
([$handlesByNip05, $profile]) => {
if (!$profile?.nip05) {
return undefined
}
loadHandle($profile.nip05)
return $handlesByNip05.get($profile.nip05)
export const deriveHandleForPubkey = (
pubkey: string,
request: Partial<SubscribeRequestWithHandlers> = {},
) =>
derived([handlesByNip05, deriveProfile(pubkey, request)], ([$handlesByNip05, $profile]) => {
if (!$profile?.nip05) {
return undefined
}
)
loadHandle($profile.nip05)
return $handlesByNip05.get($profile.nip05)
})
export const displayNip05 = (nip05: string) =>
(nip05?.startsWith("_@") ? last(nip05.split("@")) : nip05)
nip05?.startsWith("_@") ? last(nip05.split("@")) : nip05
export const displayHandle = (handle: Handle) => displayNip05(handle.nip05)
+36 -26
View File
@@ -1,26 +1,36 @@
export * from './context'
export * from './core'
export * from './collection'
export * from './commands'
export * from './feeds'
export * from './freshness'
export * from './follows'
export * from './handles'
export * from './mutes'
export * from './plaintext'
export * from './profiles'
export * from './relays'
export * from './relaySelections'
export * from './router'
export * from './search'
export * from './session'
export * from './storage'
export * from './subscribe'
export * from './sync'
export * from './tags'
export * from './thunk'
export * from './topics'
export * from './user'
export * from './util'
export * from './wot'
export * from './zappers'
export * from "./context.js"
export * from "./core.js"
export * from "./collection.js"
export * from "./commands.js"
export * from "./feeds.js"
export * from "./freshness.js"
export * from "./follows.js"
export * from "./handles.js"
export * from "./mutes.js"
export * from "./plaintext.js"
export * from "./profiles.js"
export * from "./relays.js"
export * from "./relaySelections.js"
export * from "./router.js"
export * from "./search.js"
export * from "./session.js"
export * from "./storage.js"
export * from "./subscribe.js"
export * from "./sync.js"
export * from "./tags.js"
export * from "./thunk.js"
export * from "./topics.js"
export * from "./user.js"
export * from "./util.js"
export * from "./wot.js"
export * from "./zappers.js"
import type {NetContext} from "@welshman/net"
import type {AppContext} from "./context.js"
declare module "@welshman/lib" {
interface Context {
net: NetContext
app: AppContext
}
}
+8 -9
View File
@@ -1,12 +1,12 @@
import {MUTES, asDecryptedEvent, readList} from '@welshman/util'
import {type TrustedEvent, type PublishedList} from '@welshman/util'
import {MUTES, asDecryptedEvent, readList} from "@welshman/util"
import {type TrustedEvent, type PublishedList} from "@welshman/util"
import {type SubscribeRequestWithHandlers} from "@welshman/net"
import {deriveEventsMapped} from '@welshman/store'
import {repository} from './core'
import {load} from './subscribe'
import {collection} from './collection'
import {ensurePlaintext} from './plaintext'
import {loadRelaySelections} from './relaySelections'
import {deriveEventsMapped} from "@welshman/store"
import {repository} from "./core.js"
import {load} from "./subscribe.js"
import {collection} from "./collection.js"
import {ensurePlaintext} from "./plaintext.js"
import {loadRelaySelections} from "./relaySelections.js"
export const mutes = deriveEventsMapped<PublishedList>(repository, {
filters: [{kinds: [MUTES]}],
@@ -32,4 +32,3 @@ export const {
await load({...request, filters: [{kinds: [MUTES], authors: [pubkey]}]})
},
})
+5 -5
View File
@@ -1,9 +1,9 @@
import {writable} from 'svelte/store'
import {assoc} from '@welshman/lib'
import type {TrustedEvent} from '@welshman/util'
import {withGetter} from '@welshman/store'
import {writable} from "svelte/store"
import {assoc} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {withGetter} from "@welshman/store"
import {decrypt} from "@welshman/signer"
import {getSigner, getSession} from './session'
import {getSigner, getSession} from "./session.js"
export const plaintext = withGetter(writable<Record<string, string>>({}))
+9 -11
View File
@@ -1,19 +1,19 @@
import {derived, readable} from 'svelte/store'
import {readProfile, displayProfile, displayPubkey, PROFILE} from '@welshman/util'
import {derived, readable} from "svelte/store"
import {readProfile, displayProfile, displayPubkey, PROFILE} from "@welshman/util"
import type {SubscribeRequestWithHandlers} from "@welshman/net"
import type {PublishedProfile} from "@welshman/util"
import {deriveEventsMapped, withGetter} from '@welshman/store'
import {repository} from './core'
import {load} from './subscribe'
import {collection} from './collection'
import {loadRelaySelections} from './relaySelections'
import {deriveEventsMapped, withGetter} from "@welshman/store"
import {repository} from "./core.js"
import {load} from "./subscribe.js"
import {collection} from "./collection.js"
import {loadRelaySelections} from "./relaySelections.js"
export const profiles = withGetter(
deriveEventsMapped<PublishedProfile>(repository, {
filters: [{kinds: [PROFILE]}],
eventToItem: readProfile,
itemToEvent: item => item.event,
})
}),
)
export const {
@@ -45,9 +45,7 @@ export const {
})
export const displayProfileByPubkey = (pubkey: string | undefined) =>
pubkey
? displayProfile(profilesByPubkey.get().get(pubkey), displayPubkey(pubkey))
: ""
pubkey ? displayProfile(profilesByPubkey.get().get(pubkey), displayPubkey(pubkey)) : ""
export const deriveProfileDisplay = (pubkey: string | undefined) =>
pubkey
+20 -13
View File
@@ -1,11 +1,20 @@
import {uniq} from '@welshman/lib'
import {INBOX_RELAYS, RELAYS, normalizeRelayUrl, asDecryptedEvent, readList, getListTags, getRelayTags, getRelayTagValues} from '@welshman/util'
import type {TrustedEvent, PublishedList, List} from '@welshman/util'
import {uniq} from "@welshman/lib"
import {
INBOX_RELAYS,
RELAYS,
normalizeRelayUrl,
asDecryptedEvent,
readList,
getListTags,
getRelayTags,
getRelayTagValues,
} from "@welshman/util"
import type {TrustedEvent, PublishedList, List} from "@welshman/util"
import type {SubscribeRequestWithHandlers} from "@welshman/net"
import {deriveEventsMapped} from '@welshman/store'
import {repository} from './core'
import {load} from './subscribe'
import {collection} from './collection'
import {deriveEventsMapped} from "@welshman/store"
import {repository} from "./core.js"
import {load} from "./subscribe.js"
import {collection} from "./collection.js"
export const getRelayUrls = (list?: List): string[] =>
uniq(getRelayTagValues(getListTags(list)).map(normalizeRelayUrl))
@@ -14,21 +23,20 @@ export const getReadRelayUrls = (list?: List): string[] =>
uniq(
getRelayTags(getListTags(list))
.filter((t: string[]) => !t[2] || t[2] === "read")
.map((t: string[]) => normalizeRelayUrl(t[1]))
.map((t: string[]) => normalizeRelayUrl(t[1])),
)
export const getWriteRelayUrls = (list?: List): string[] =>
uniq(
getRelayTags(getListTags(list))
.filter((t: string[]) => !t[2] || t[2] === "write")
.map((t: string[]) => normalizeRelayUrl(t[1]))
.map((t: string[]) => normalizeRelayUrl(t[1])),
)
export const relaySelections = deriveEventsMapped<PublishedList>(repository, {
filters: [{kinds: [RELAYS]}],
itemToEvent: item => item.event,
eventToItem: (event: TrustedEvent) =>
readList(asDecryptedEvent(event)),
eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)),
})
export const {
@@ -46,8 +54,7 @@ export const {
export const inboxRelaySelections = deriveEventsMapped<PublishedList>(repository, {
filters: [{kinds: [INBOX_RELAYS]}],
itemToEvent: item => item.event,
eventToItem: (event: TrustedEvent) =>
readList(asDecryptedEvent(event)),
eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)),
})
export const {
+87 -57
View File
@@ -1,11 +1,11 @@
import {writable, derived} from 'svelte/store'
import {withGetter} from '@welshman/store'
import {ctx, groupBy, indexBy, batch, now, ago, uniq, batcher, postJson} from '@welshman/lib'
import {writable, derived} from "svelte/store"
import {withGetter} from "@welshman/store"
import {ctx, groupBy, indexBy, batch, now, ago, uniq, batcher, postJson} from "@welshman/lib"
import type {RelayProfile} from "@welshman/util"
import {normalizeRelayUrl, displayRelayUrl, displayRelayProfile} from "@welshman/util"
import {ConnectionEvent} from '@welshman/net'
import type {Connection, Message} from '@welshman/net'
import {collection} from './collection'
import {ConnectionEvent} from "@welshman/net"
import type {Connection, Message} from "@welshman/net"
import {collection} from "./collection.js"
export type RelayStats = {
first_seen: number
@@ -151,78 +151,108 @@ const updateRelayStats = batch(500, (updates: RelayStatsUpdate[]) => {
})
const onConnectionOpen = ({url}: Connection) =>
updateRelayStats([url, stats => {
stats.last_open = now()
stats.open_count++
}])
updateRelayStats([
url,
stats => {
stats.last_open = now()
stats.open_count++
},
])
const onConnectionClose = ({url}: Connection) =>
updateRelayStats([url, stats => {
stats.last_close = now()
stats.close_count++
}])
updateRelayStats([
url,
stats => {
stats.last_close = now()
stats.close_count++
},
])
const onConnectionSend = ({url}: Connection, [verb]: Message) => {
if (verb === 'REQ') {
updateRelayStats([url, stats => {
stats.request_count++
stats.last_request = now()
}])
} else if (verb === 'EVENT') {
updateRelayStats([url, stats => {
stats.publish_count++
stats.last_publish = now()
}])
if (verb === "REQ") {
updateRelayStats([
url,
stats => {
stats.request_count++
stats.last_request = now()
},
])
} else if (verb === "EVENT") {
updateRelayStats([
url,
stats => {
stats.publish_count++
stats.last_publish = now()
},
])
}
}
const onConnectionReceive = ({url, state}: Connection, [verb, ...extra]: Message) => {
if (verb === 'OK') {
if (verb === "OK") {
const [eventId, ok] = extra
const pub = state.pendingPublishes.get(eventId)
updateRelayStats([url, stats => {
if (pub) {
stats.publish_timer += ago(pub.sent)
}
updateRelayStats([
url,
stats => {
if (pub) {
stats.publish_timer += ago(pub.sent)
}
if (ok) {
stats.publish_success_count++
} else {
stats.publish_failure_count++
}
}])
} else if (verb === 'AUTH') {
updateRelayStats([url, stats => {
stats.last_auth = now()
}])
} else if (verb === 'EVENT') {
updateRelayStats([url, stats => {
stats.event_count++
stats.last_event = now()
}])
} else if (verb === 'EOSE') {
if (ok) {
stats.publish_success_count++
} else {
stats.publish_failure_count++
}
},
])
} else if (verb === "AUTH") {
updateRelayStats([
url,
stats => {
stats.last_auth = now()
},
])
} else if (verb === "EVENT") {
updateRelayStats([
url,
stats => {
stats.event_count++
stats.last_event = now()
},
])
} else if (verb === "EOSE") {
const request = state.pendingRequests.get(extra[0])
// Only count the first eose
if (request && !request.eose) {
updateRelayStats([url, stats => {
stats.eose_count++
stats.eose_timer += now() - request.sent
}])
updateRelayStats([
url,
stats => {
stats.eose_count++
stats.eose_timer += now() - request.sent
},
])
}
} else if (verb === 'NOTICE') {
updateRelayStats([url, stats => {
stats.notice_count++
}])
} else if (verb === "NOTICE") {
updateRelayStats([
url,
stats => {
stats.notice_count++
},
])
}
}
const onConnectionError = ({url}: Connection) =>
updateRelayStats([url, stats => {
stats.last_error = now()
stats.recent_errors = uniq(stats.recent_errors.concat(now())).slice(-10)
}])
updateRelayStats([
url,
stats => {
stats.last_error = now()
stats.recent_errors = uniq(stats.recent_errors.concat(now())).slice(-10)
},
])
export const trackRelayStats = (connection: Connection) => {
connection.on(ConnectionEvent.Open, onConnectionOpen)
+70 -70
View File
@@ -32,15 +32,15 @@ import {
} from "@welshman/util"
import type {TrustedEvent, Filter} from "@welshman/util"
import type {RelaysAndFilters} from "@welshman/net"
import {pubkey} from "./session"
import {pubkey} from "./session.js"
import {
relaySelectionsByPubkey,
inboxRelaySelectionsByPubkey,
getReadRelayUrls,
getWriteRelayUrls,
getRelayUrls,
} from "./relaySelections"
import {relays, relaysByUrl} from "./relays"
} from "./relaySelections.js"
import {relays, relaysByUrl} from "./relays.js"
export const INDEXED_KINDS = [PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS]
@@ -99,12 +99,14 @@ export type RouterOptions = {
}
export type Selection = {
weight: number,
relays: string[],
weight: number
relays: string[]
}
const makeSelection = (relays: string[], weight = 1): Selection =>
({relays: relays.map(normalizeRelayUrl), weight})
const makeSelection = (relays: string[], weight = 1): Selection => ({
relays: relays.map(normalizeRelayUrl),
weight,
})
// Fallback policies
@@ -112,7 +114,7 @@ export type FallbackPolicy = (count: number, limit: number) => number
export const addNoFallbacks = (count: number, limit: number) => 0
export const addMinimalFallbacks = (count: number, limit: number) => count > 0 ? 0 : 1
export const addMinimalFallbacks = (count: number, limit: number) => (count > 0 ? 0 : 1)
export const addMaximalFallbacks = (count: number, limit: number) => limit - count
@@ -142,35 +144,26 @@ export class Router {
// Routing scenarios
FromRelays = (relays: string[]) =>
this.scenario([makeSelection(relays)])
FromRelays = (relays: string[]) => this.scenario([makeSelection(relays)])
ForUser = () =>
this.FromRelays(this.getRelaysForUser(RelayMode.Read))
ForUser = () => this.FromRelays(this.getRelaysForUser(RelayMode.Read))
FromUser = () =>
this.FromRelays(this.getRelaysForUser(RelayMode.Write))
FromUser = () => this.FromRelays(this.getRelaysForUser(RelayMode.Write))
UserInbox = () =>
this.FromRelays(this.getRelaysForUser(RelayMode.Inbox)).policy(addNoFallbacks)
UserInbox = () => this.FromRelays(this.getRelaysForUser(RelayMode.Inbox)).policy(addNoFallbacks)
ForPubkey = (pubkey: string) =>
this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Read))
ForPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Read))
FromPubkey = (pubkey: string) =>
this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Write))
FromPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Write))
PubkeyInbox = (pubkey: string) =>
this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Inbox)).policy(addNoFallbacks)
ForPubkeys = (pubkeys: string[]) =>
this.merge(pubkeys.map(pubkey => this.ForPubkey(pubkey)))
ForPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.ForPubkey(pubkey)))
FromPubkeys = (pubkeys: string[]) =>
this.merge(pubkeys.map(pubkey => this.FromPubkey(pubkey)))
FromPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.FromPubkey(pubkey)))
PubkeyInboxes = (pubkeys: string[]) =>
this.merge(pubkeys.map(pubkey => this.PubkeyInbox(pubkey)))
PubkeyInboxes = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.PubkeyInbox(pubkey)))
Event = (event: TrustedEvent) =>
this.FromRelays(this.getRelaysForPubkey(event.pubkey, RelayMode.Write))
@@ -180,10 +173,7 @@ export class Router {
Quote = (event: TrustedEvent, value: string, relays: string[] = []) => {
const tag = event.tags.find(t => t[1] === value)
const scenarios = [
this.ForPubkey(event.pubkey),
this.FromPubkey(event.pubkey),
]
const scenarios = [this.ForPubkey(event.pubkey), this.FromPubkey(event.pubkey)]
if (tag?.[2] && isShareableRelayUrl(tag[2])) {
scenarios.push(this.FromRelays([tag[2]]))
@@ -198,21 +188,19 @@ export class Router {
EventAncestors = (event: TrustedEvent, type: "mentions" | "replies" | "roots") => {
return this.scenario(
getAncestorTags(event.tags)[type].flatMap(
([_, value, relay, pubkey]) => {
const selections = [makeSelection(this.ForUser().getUrls(), 0.5)]
getAncestorTags(event.tags)[type].flatMap(([_, value, relay, pubkey]) => {
const selections = [makeSelection(this.ForUser().getUrls(), 0.5)]
if (pubkey) {
selections.push(makeSelection(this.FromPubkey(pubkey).getUrls()))
}
if (relay) {
selections.push(makeSelection([relay], 0.9))
}
return selections
if (pubkey) {
selections.push(makeSelection(this.FromPubkey(pubkey).getUrls()))
}
)
if (relay) {
selections.push(makeSelection([relay], 0.9))
}
return selections
}),
)
}
@@ -240,16 +228,28 @@ export type RouterScenarioOptions = {
}
export class RouterScenario {
constructor(readonly router: Router, readonly selections: Selection[], readonly options: RouterScenarioOptions = {}) {}
constructor(
readonly router: Router,
readonly selections: Selection[],
readonly options: RouterScenarioOptions = {},
) {}
clone = (options: RouterScenarioOptions) =>
new RouterScenario(this.router, this.selections, {...this.options, ...options})
filter = (f: (selection: Selection) => boolean) =>
new RouterScenario(this.router, this.selections.filter(selection => f(selection)), this.options)
new RouterScenario(
this.router,
this.selections.filter(selection => f(selection)),
this.options,
)
update = (f: (selection: Selection) => Selection) =>
new RouterScenario(this.router, this.selections.map(selection => f(selection)), this.options)
new RouterScenario(
this.router,
this.selections.map(selection => f(selection)),
this.options,
)
policy = (policy: FallbackPolicy) => this.clone({policy})
@@ -290,11 +290,7 @@ export class RouterScenario {
const relays = take(
limit,
sortBy(
scoreRelay,
Array.from(relayWeights.keys())
.filter(scoreRelay)
)
sortBy(scoreRelay, Array.from(relayWeights.keys()).filter(scoreRelay)),
)
const fallbacksNeeded = fallbackPolicy(relays.length, limit)
@@ -348,14 +344,14 @@ export const getIndexerRelays = () => ctx.app.indexerRelays || getFallbackRelays
export const getFallbackRelays = throttleWithValue(300, () =>
sortBy(r => -getRelayQuality(r.url), relays.get())
.slice(0, 30)
.map(r => r.url)
.map(r => r.url),
)
export const getSearchRelays = throttleWithValue(300, () =>
sortBy(r => -getRelayQuality(r.url), relays.get())
.filter(r => r.profile?.supported_nips?.includes(50))
.slice(0, 30)
.map(r => r.url)
.map(r => r.url),
)
export const makeRouter = (options: Partial<RouterOptions> = {}) =>
@@ -372,7 +368,7 @@ export const makeRouter = (options: Partial<RouterOptions> = {}) =>
// Infer relay selections from filters
type FilterScenario = {filter: Filter, scenario: RouterScenario}
type FilterScenario = {filter: Filter; scenario: RouterScenario}
type FilterSelectionRule = (filter: Filter) => FilterScenario[]
@@ -387,10 +383,12 @@ export const getFilterSelectionsForSearch = (filter: Filter) => {
export const getFilterSelectionsForWraps = (filter: Filter) => {
if (!filter.kinds?.includes(WRAP) || filter.authors) return []
return [{
filter: {...filter, kinds: [WRAP]},
scenario: ctx.app.router.UserInbox(),
}]
return [
{
filter: {...filter, kinds: [WRAP]},
scenario: ctx.app.router.UserInbox(),
},
]
}
export const getFilterSelectionsForIndexedKinds = (filter: Filter) => {
@@ -400,10 +398,12 @@ export const getFilterSelectionsForIndexedKinds = (filter: Filter) => {
const relays = ctx.app.router.options.getIndexerRelays?.() || []
return [{
filter: {...filter, kinds},
scenario: ctx.app.router.FromRelays(relays),
}]
return [
{
filter: {...filter, kinds},
scenario: ctx.app.router.FromRelays(relays),
},
]
}
export const getFilterSelectionsForAuthors = (filter: Filter) => {
@@ -411,15 +411,15 @@ export const getFilterSelectionsForAuthors = (filter: Filter) => {
const chunkCount = clamp([1, 30], Math.round(filter.authors.length / 30))
return chunks(chunkCount, filter.authors)
.map(authors => ({
filter: {...filter, authors},
scenario: ctx.app.router.FromPubkeys(authors),
}))
return chunks(chunkCount, filter.authors).map(authors => ({
filter: {...filter, authors},
scenario: ctx.app.router.FromPubkeys(authors),
}))
}
export const getFilterSelectionsForUser = (filter: Filter) =>
[{filter, scenario: ctx.app.router.ForUser().weight(0.2)}]
export const getFilterSelectionsForUser = (filter: Filter) => [
{filter, scenario: ctx.app.router.ForUser().weight(0.2)},
]
export const defaultFilterSelectionRules = [
getFilterSelectionsForSearch,
@@ -431,7 +431,7 @@ export const defaultFilterSelectionRules = [
export const getFilterSelections = (
filters: Filter[],
rules: FilterSelectionRule[] = defaultFilterSelectionRules
rules: FilterSelectionRule[] = defaultFilterSelectionRules,
): RelaysAndFilters[] => {
const filtersById = new Map<string, Filter>()
const scenariosById = new Map<string, RouterScenario[]>()
+18 -19
View File
@@ -1,19 +1,19 @@
import Fuse from "fuse.js"
import type {IFuseOptions, FuseResult} from "fuse.js"
import {debounce} from 'throttle-debounce'
import {derived} from 'svelte/store'
import {dec, sortBy} from '@welshman/lib'
import {PROFILE} from '@welshman/util'
import {throttled} from '@welshman/store'
import {debounce} from "throttle-debounce"
import {derived} from "svelte/store"
import {dec, sortBy} from "@welshman/lib"
import {PROFILE} from "@welshman/util"
import {throttled} from "@welshman/store"
import type {PublishedProfile} from "@welshman/util"
import {load} from './subscribe'
import {wotGraph} from './wot'
import {profiles} from './profiles'
import {topics} from './topics'
import type {Topic} from './topics'
import {relays} from './relays'
import type {Relay} from './relays'
import {handlesByNip05} from './handles'
import {load} from "./subscribe.js"
import {wotGraph} from "./wot.js"
import {profiles} from "./profiles.js"
import {topics} from "./topics.js"
import type {Topic} from "./topics.js"
import {relays} from "./relays.js"
import type {Relay} from "./relays.js"
import {handlesByNip05} from "./handles.js"
export type SearchOptions<V, T> = {
getValue: (item: T) => V
@@ -65,12 +65,11 @@ export const profileSearch = derived(
[throttled(800, profiles), throttled(800, handlesByNip05)],
([$profiles, $handlesByNip05]) => {
// Remove invalid nip05's from profiles
const options = $profiles
.map(p => {
const isNip05Valid = !p.nip05 || $handlesByNip05.get(p.nip05)?.pubkey === p.event.pubkey
const options = $profiles.map(p => {
const isNip05Valid = !p.nip05 || $handlesByNip05.get(p.nip05)?.pubkey === p.event.pubkey
return isNip05Valid ? p : {...p, nip05: ""}
})
return isNip05Valid ? p : {...p, nip05: ""}
})
return createSearch(options, {
onSearch: searchProfiles,
@@ -93,7 +92,7 @@ export const profileSearch = derived(
shouldSort: false,
},
})
}
},
)
export const topicSearch = derived(topics, $topics =>
+12 -12
View File
@@ -4,18 +4,18 @@ import {withGetter, synced} from "@welshman/store"
import {Nip46Broker, Nip46Signer, Nip07Signer, Nip01Signer, Nip55Signer} from "@welshman/signer"
export type SessionNip01 = {
method: 'nip01'
method: "nip01"
pubkey: string
secret: string
}
export type SessionNip07 = {
method: 'nip07'
method: "nip07"
pubkey: string
}
export type SessionNip46 = {
method: 'nip46'
method: "nip46"
pubkey: string
secret: string
handler: {
@@ -25,22 +25,22 @@ export type SessionNip46 = {
}
export type SessionNip55 = {
method: 'nip55'
method: "nip55"
pubkey: string
signer: string
}
export type SessionPubkey = {
method: 'pubkey'
method: "pubkey"
pubkey: string
}
export type SessionAnyMethod =
SessionNip01 |
SessionNip07 |
SessionNip46 |
SessionNip55 |
SessionPubkey
| SessionNip01
| SessionNip07
| SessionNip46
| SessionNip55
| SessionPubkey
export type Session = SessionAnyMethod & Record<string, any>
@@ -88,14 +88,14 @@ export const getSigner = cached({
clientSecret: session.secret!,
relays: session.handler!.relays,
signerPubkey: session.handler!.pubkey,
})
}),
)
case "nip55":
return new Nip55Signer(session.signer!)
default:
return null
}
}
},
})
export const signer = withGetter(derived(session, getSigner))
+117 -96
View File
@@ -49,35 +49,37 @@ export const initIndexedDbAdapter = async (name: string, adapter: IndexedDbAdapt
adapter.store.set(prevRecords)
adapter.store.subscribe(
async (currentRecords: any[]) => {
if (dead.get()) {
return
}
adapter.store.subscribe(async (currentRecords: any[]) => {
if (dead.get()) {
return
}
const currentIds = new Set(currentRecords.map(item => item[adapter.keyPath]))
const removedRecords = prevRecords.filter(r => !currentIds.has(r[adapter.keyPath]))
const currentIds = new Set(currentRecords.map(item => item[adapter.keyPath]))
const removedRecords = prevRecords.filter(r => !currentIds.has(r[adapter.keyPath]))
const prevRecordsById = indexBy(item => item[adapter.keyPath], prevRecords)
const updatedRecords = currentRecords.filter(r => r !== prevRecordsById.get(r[adapter.keyPath]))
const prevRecordsById = indexBy(item => item[adapter.keyPath], prevRecords)
const updatedRecords = currentRecords.filter(r => r !== prevRecordsById.get(r[adapter.keyPath]))
prevRecords = currentRecords
prevRecords = currentRecords
if (updatedRecords.length > 0) {
await bulkPut(name, updatedRecords)
}
if (updatedRecords.length > 0) {
await bulkPut(name, updatedRecords)
}
if (removedRecords.length > 0) {
await bulkDelete(
name,
removedRecords.map(item => item[adapter.keyPath]),
)
}
},
)
if (removedRecords.length > 0) {
await bulkDelete(
name,
removedRecords.map(item => item[adapter.keyPath]),
)
}
})
}
export const initStorage = async (name: string, version: number, adapters: Record<string, IndexedDbAdapter>) => {
export const initStorage = async (
name: string,
version: number,
adapters: Record<string, IndexedDbAdapter>,
) => {
if (!window.indexedDB) return
window.addEventListener("beforeunload", () => closeStorage())
@@ -131,14 +133,20 @@ const migrate = (data: any[], options: StorageAdapterOptions) =>
options.migrate ? options.migrate(data) : data
export const storageAdapters = {
fromObjectStore: <T>(store: Writable<Record<string, T>>, options: StorageAdapterOptions = {}) => ({
fromObjectStore: <T>(
store: Writable<Record<string, T>>,
options: StorageAdapterOptions = {},
) => ({
options,
keyPath: "key",
store: adapter({
store: throttled(options.throttle || 0, store),
forward: (data: Record<string, T>) =>
migrate(Object.entries(data).map(([key, value]) => ({key, value})), options),
backward: (data: {key: string, value: T}[]) =>
migrate(
Object.entries(data).map(([key, value]) => ({key, value})),
options,
),
backward: (data: {key: string; value: T}[]) =>
fromPairs(data.map(({key, value}) => [key, value])),
}),
}),
@@ -148,100 +156,113 @@ export const storageAdapters = {
store: adapter({
store: throttled(options.throttle || 0, store),
forward: (data: Map<string, T>) =>
migrate(Array.from(data.entries()).map(([key, value]) => ({key, value})), options),
backward: (data: {key: string, value: T}[]) =>
migrate(
Array.from(data.entries()).map(([key, value]) => ({key, value})),
options,
),
backward: (data: {key: string; value: T}[]) =>
new Map(data.map(({key, value}) => [key, value])),
}),
}),
fromTracker: (tracker: Tracker, options: StorageAdapterOptions = {}) => ({
options,
keyPath: 'key',
store: custom(setter => {
let onUpdate = () =>
setter(
migrate(
Array.from(tracker.relaysById.entries())
.map(([key, urls]) => ({key, value: Array.from(urls)})),
options
keyPath: "key",
store: custom(
setter => {
let onUpdate = () =>
setter(
migrate(
Array.from(tracker.relaysById.entries()).map(([key, urls]) => ({
key,
value: Array.from(urls),
})),
options,
),
)
)
if (options.throttle) {
onUpdate = throttle(options.throttle, onUpdate)
}
if (options.throttle) {
onUpdate = throttle(options.throttle, onUpdate)
}
onUpdate()
tracker.on('update', onUpdate)
onUpdate()
tracker.on("update", onUpdate)
return () => tracker.off('update', onUpdate)
}, {
set: (data: {key: string, value: string[]}[]) =>
tracker.load(new Map(data.map(({key, value}) => [key, new Set(value)]))),
}),
return () => tracker.off("update", onUpdate)
},
{
set: (data: {key: string; value: string[]}[]) =>
tracker.load(new Map(data.map(({key, value}) => [key, new Set(value)]))),
},
),
}),
fromRepository: (repository: Repository, options: StorageAdapterOptions = {}) => ({
options,
keyPath: 'id',
store: custom(setter => {
let onUpdate = () => setter(migrate(repository.dump(), options))
keyPath: "id",
store: custom(
setter => {
let onUpdate = () => setter(migrate(repository.dump(), options))
if (options.throttle) {
onUpdate = throttle(options.throttle, onUpdate)
}
if (options.throttle) {
onUpdate = throttle(options.throttle, onUpdate)
}
onUpdate()
repository.on('update', onUpdate)
onUpdate()
repository.on("update", onUpdate)
return () => repository.off('update', onUpdate)
}, {
set: (events: TrustedEvent[]) => repository.load(events),
}),
return () => repository.off("update", onUpdate)
},
{
set: (events: TrustedEvent[]) => repository.load(events),
},
),
}),
fromRepositoryAndTracker: (
repository: Repository,
tracker: Tracker,
options: StorageAdapterOptions = {}
options: StorageAdapterOptions = {},
) => ({
options,
keyPath: 'id',
store: custom(setter => {
let onUpdate = () => {
const events = migrate(repository.dump(), options)
keyPath: "id",
store: custom(
setter => {
let onUpdate = () => {
const events = migrate(repository.dump(), options)
setter(
events.map(event => {
const relays = Array.from(tracker.getRelays(event.id))
setter(
events.map(event => {
const relays = Array.from(tracker.getRelays(event.id))
return {id: event.id, event, relays}
})
)
}
if (options.throttle) {
onUpdate = throttle(options.throttle, onUpdate)
}
onUpdate()
tracker.on('update', onUpdate)
repository.on('update', onUpdate)
return () => {
tracker.off('update', onUpdate)
}
}, {
set: (items: {event: TrustedEvent, relays: string[]}[]) => {
const events: TrustedEvent[] = []
const relaysById = new Map<string, Set<string>>()
for (const {event, relays} of items) {
events.push(event)
relaysById.set(event.id, new Set(relays))
return {id: event.id, event, relays}
}),
)
}
repository.load(events)
tracker.load(relaysById)
},
}),
})
}
if (options.throttle) {
onUpdate = throttle(options.throttle, onUpdate)
}
onUpdate()
tracker.on("update", onUpdate)
repository.on("update", onUpdate)
return () => {
tracker.off("update", onUpdate)
}
},
{
set: (items: {event: TrustedEvent; relays: string[]}[]) => {
const events: TrustedEvent[] = []
const relaysById = new Map<string, Set<string>>()
for (const {event, relays} of items) {
events.push(event)
relaysById.set(event.id, new Set(relays))
}
repository.load(events)
tracker.load(relaysById)
},
},
),
}),
}
+1 -1
View File
@@ -3,7 +3,7 @@ import {LOCAL_RELAY_URL, getFilterResultCardinality} from "@welshman/util"
import type {TrustedEvent, Filter} from "@welshman/util"
import {subscribe as baseSubscribe, SubscriptionEvent} from "@welshman/net"
import type {SubscribeRequestWithHandlers} from "@welshman/net"
import {repository} from './core'
import {repository} from "./core.js"
export type PartialSubscribeRequest = Partial<SubscribeRequestWithHandlers> & {filters: Filter[]}
+25 -25
View File
@@ -1,14 +1,21 @@
import type {Filter} from '@welshman/util'
import {isSignedEvent} from '@welshman/util'
import {push as basePush, pull as basePull, sync as baseSync, pushWithoutNegentropy, pullWithoutNegentropy, syncWithoutNegentropy} from "@welshman/net"
import {repository} from './core'
import {relaysByUrl} from './relays'
import type {Filter} from "@welshman/util"
import {isSignedEvent} from "@welshman/util"
import {
push as basePush,
pull as basePull,
sync as baseSync,
pushWithoutNegentropy,
pullWithoutNegentropy,
syncWithoutNegentropy,
} from "@welshman/net"
import {repository} from "./core.js"
import {relaysByUrl} from "./relays.js"
export const hasNegentropy = (url: string) => {
const p = relaysByUrl.get().get(url)?.profile
if (p?.supported_nips?.includes(77)) return true
if (p?.software?.includes('strfry') && !p?.version?.match(/^0\./)) return true
if (p?.software?.includes("strfry") && !p?.version?.match(/^0\./)) return true
return false
}
@@ -23,12 +30,10 @@ export const pull = async ({relays, filters}: AppSyncOpts) => {
await Promise.all(
relays.map(async relay => {
await (
hasNegentropy(relay)
? basePull({filters, events, relays: [relay]})
: pullWithoutNegentropy({filters, relays: [relay]})
)
})
await (hasNegentropy(relay)
? basePull({filters, events, relays: [relay]})
: pullWithoutNegentropy({filters, relays: [relay]}))
}),
)
}
@@ -37,12 +42,10 @@ export const push = async ({relays, filters}: AppSyncOpts) => {
await Promise.all(
relays.map(async relay => {
await (
hasNegentropy(relay)
? basePush({filters, events, relays: [relay]})
: pushWithoutNegentropy({events, relays: [relay]})
)
})
await (hasNegentropy(relay)
? basePush({filters, events, relays: [relay]})
: pushWithoutNegentropy({events, relays: [relay]}))
}),
)
}
@@ -51,12 +54,9 @@ export const sync = async ({relays, filters}: AppSyncOpts) => {
await Promise.all(
relays.map(async relay => {
await (
hasNegentropy(relay)
? baseSync({filters, events, relays: [relay]})
: syncWithoutNegentropy({filters, events, relays: [relay]})
)
})
await (hasNegentropy(relay)
? baseSync({filters, events, relays: [relay]})
: syncWithoutNegentropy({filters, events, relays: [relay]}))
}),
)
}
+11 -8
View File
@@ -1,8 +1,14 @@
import {ctx} from '@welshman/lib'
import {getAddress, isReplaceable, getAncestorTags, getPubkeyTagValues, getIdAndAddress} from '@welshman/util'
import type {TrustedEvent} from '@welshman/util'
import {displayProfileByPubkey} from './profiles'
import {pubkey} from './session'
import {ctx} from "@welshman/lib"
import {
getAddress,
isReplaceable,
getAncestorTags,
getPubkeyTagValues,
getIdAndAddress,
} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {displayProfileByPubkey} from "./profiles.js"
import {pubkey} from "./session.js"
export const tagZapSplit = (pubkey: string, split = 1) => [
"zap",
@@ -102,6 +108,3 @@ export const tagReactionTo = (event: TrustedEvent) => {
return tags
}
+28 -16
View File
@@ -1,13 +1,26 @@
import {writable, derived, get} from 'svelte/store'
import type {Writable, Readable} from 'svelte/store'
import {Worker, identity, uniq, defer, sleep, assoc} from '@welshman/lib'
import type {Deferred} from '@welshman/lib'
import {writable, derived, get} from "svelte/store"
import type {Writable, Readable} from "svelte/store"
import {Worker, identity, uniq, defer, sleep, assoc} from "@welshman/lib"
import type {Deferred} from "@welshman/lib"
import {stamp, own, hash} from "@welshman/signer"
import type {TrustedEvent, HashedEvent, EventTemplate, SignedEvent, StampedEvent, OwnedEvent} from '@welshman/util'
import {isStampedEvent, isOwnedEvent, isHashedEvent, isUnwrappedEvent, isSignedEvent} from '@welshman/util'
import type {
TrustedEvent,
HashedEvent,
EventTemplate,
SignedEvent,
StampedEvent,
OwnedEvent,
} from "@welshman/util"
import {
isStampedEvent,
isOwnedEvent,
isHashedEvent,
isUnwrappedEvent,
isSignedEvent,
} from "@welshman/util"
import {publish, PublishStatus} from "@welshman/net"
import {repository, tracker} from './core'
import {pubkey, getSession, getSigner} from './session'
import {repository, tracker} from "./core.js"
import {pubkey, getSession, getSigner} from "./session.js"
const {Pending, Success, Failure, Timeout, Aborted} = PublishStatus
@@ -53,8 +66,8 @@ export const prepEvent = (event: ThunkEvent) => {
export const makeThunk = (request: ThunkRequest) => {
const event = prepEvent(request.event)
const controller = new AbortController()
const result: Thunk['result'] = defer()
const status: Thunk['status'] = writable({})
const result: Thunk["result"] = defer()
const status: Thunk["status"] = writable({})
return {event, request, controller, result, status}
}
@@ -72,7 +85,7 @@ export const isMergedThunk = (thunk: Thunk | MergedThunk): thunk is MergedThunk
export const mergeThunks = (thunks: Thunk[]) => {
const controller = new AbortController()
controller.signal.addEventListener('abort', () => {
controller.signal.addEventListener("abort", () => {
for (const thunk of thunks) {
thunk.controller.abort()
}
@@ -99,8 +112,8 @@ export const mergeThunks = (thunks: Thunk[]) => {
}
return mergedStatus
}
)
},
),
}
}
@@ -125,7 +138,7 @@ export const publishThunk = (request: ThunkRequest) => {
thunks.update(assoc(thunk.event.id, thunk))
thunk.controller.signal.addEventListener('abort', () => {
thunk.controller.signal.addEventListener("abort", () => {
repository.removeEvent(thunk.event.id)
})
@@ -143,7 +156,7 @@ export const publishThunks = (requests: ThunkRequest[]) => {
thunks.update(assoc(thunk.event.id, mergedThunk))
thunk.controller.signal.addEventListener('abort', () => {
thunk.controller.signal.addEventListener("abort", () => {
repository.removeEvent(thunk.event.id)
})
}
@@ -221,4 +234,3 @@ thunkWorker.addGlobalHandler((thunk: Thunk) => {
})
})
})
+3 -3
View File
@@ -1,6 +1,6 @@
import {inc, throttle} from '@welshman/lib'
import {custom} from '@welshman/store'
import {repository} from './core'
import {inc, throttle} from "@welshman/lib"
import {custom} from "@welshman/store"
import {repository} from "./core.js"
export type Topic = {
name: string
-9
View File
@@ -1,9 +0,0 @@
import type {NetContext} from '@welshman/net'
import type {AppContext} from './context'
declare module "@welshman/lib" {
interface Context {
net: NetContext
app: AppContext
}
}
+29 -33
View File
@@ -1,43 +1,39 @@
import {derived} from 'svelte/store'
import {pubkey} from './session'
import {profilesByPubkey, loadProfile} from './profiles'
import {followsByPubkey, loadFollows} from './follows'
import {mutesByPubkey, loadMutes} from './mutes'
import {relaySelectionsByPubkey, inboxRelaySelectionsByPubkey, loadRelaySelections, loadInboxRelaySelections} from './relaySelections'
import {wotGraph} from './wot'
import {derived} from "svelte/store"
import {pubkey} from "./session.js"
import {profilesByPubkey, loadProfile} from "./profiles.js"
import {followsByPubkey, loadFollows} from "./follows.js"
import {mutesByPubkey, loadMutes} from "./mutes.js"
import {
relaySelectionsByPubkey,
inboxRelaySelectionsByPubkey,
loadRelaySelections,
loadInboxRelaySelections,
} from "./relaySelections.js"
import {wotGraph} from "./wot.js"
export const userProfile = derived(
[profilesByPubkey, pubkey],
([$profilesByPubkey, $pubkey]) => {
if (!$pubkey) return undefined
export const userProfile = derived([profilesByPubkey, pubkey], ([$profilesByPubkey, $pubkey]) => {
if (!$pubkey) return undefined
loadProfile($pubkey)
loadProfile($pubkey)
return $profilesByPubkey.get($pubkey)
}
)
return $profilesByPubkey.get($pubkey)
})
export const userFollows = derived(
[followsByPubkey, pubkey],
([$followsByPubkey, $pubkey]) => {
if (!$pubkey) return undefined
export const userFollows = derived([followsByPubkey, pubkey], ([$followsByPubkey, $pubkey]) => {
if (!$pubkey) return undefined
loadFollows($pubkey)
loadFollows($pubkey)
return $followsByPubkey.get($pubkey)
}
)
return $followsByPubkey.get($pubkey)
})
export const userMutes = derived(
[mutesByPubkey, pubkey],
([$mutesByPubkey, $pubkey]) => {
if (!$pubkey) return undefined
export const userMutes = derived([mutesByPubkey, pubkey], ([$mutesByPubkey, $pubkey]) => {
if (!$pubkey) return undefined
loadMutes($pubkey)
loadMutes($pubkey)
return $mutesByPubkey.get($pubkey)
}
)
return $mutesByPubkey.get($pubkey)
})
export const userRelaySelections = derived(
[relaySelectionsByPubkey, pubkey],
@@ -47,7 +43,7 @@ export const userRelaySelections = derived(
loadRelaySelections($pubkey)
return $relaySelectionsByPubkey.get($pubkey)
}
},
)
export const userInboxRelaySelections = derived(
@@ -58,7 +54,7 @@ export const userInboxRelaySelections = derived(
loadInboxRelaySelections($pubkey)
return $inboxRelaySelectionsByPubkey.get($pubkey)
}
},
)
export const getUserWotScore = (tpk: string) => wotGraph.get().get(tpk) || 0
+24 -32
View File
@@ -1,10 +1,10 @@
import {derived, writable} from 'svelte/store'
import {max, throttle, addToMapKey, inc, dec} from '@welshman/lib'
import {getListTags, getPubkeyTagValues} from '@welshman/util'
import {throttled, withGetter} from '@welshman/store'
import {pubkey} from './session'
import {follows, followsByPubkey} from './follows'
import {mutes, mutesByPubkey} from './mutes'
import {derived, writable} from "svelte/store"
import {max, throttle, addToMapKey, inc, dec} from "@welshman/lib"
import {getListTags, getPubkeyTagValues} from "@welshman/util"
import {throttled, withGetter} from "@welshman/store"
import {pubkey} from "./session.js"
import {follows, followsByPubkey} from "./follows.js"
import {mutes, mutesByPubkey} from "./mutes.js"
export const getFollows = (pubkey: string) =>
getPubkeyTagValues(getListTags(followsByPubkey.get().get(pubkey)))
@@ -27,46 +27,38 @@ export const getNetwork = (pubkey: string) => {
return Array.from(network)
}
export const followersByPubkey = withGetter(
derived(
throttled(1000, follows),
lists => {
const $followersByPubkey = new Map<string, Set<string>>()
derived(throttled(1000, follows), lists => {
const $followersByPubkey = new Map<string, Set<string>>()
for (const list of lists) {
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
addToMapKey($followersByPubkey, pubkey, list.event.pubkey)
}
for (const list of lists) {
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
addToMapKey($followersByPubkey, pubkey, list.event.pubkey)
}
return $followersByPubkey
}
)
return $followersByPubkey
}),
)
export const mutersByPubkey = withGetter(
derived(
throttled(1000, mutes),
lists => {
const $mutersByPubkey = new Map<string, Set<string>>()
derived(throttled(1000, mutes), lists => {
const $mutersByPubkey = new Map<string, Set<string>>()
for (const list of lists) {
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
addToMapKey($mutersByPubkey, pubkey, list.event.pubkey)
}
for (const list of lists) {
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
addToMapKey($mutersByPubkey, pubkey, list.event.pubkey)
}
return $mutersByPubkey
}
)
return $mutersByPubkey
}),
)
export const getFollowers = (pubkey: string) =>
Array.from(followersByPubkey.get().get(pubkey) || [])
export const getMuters = (pubkey: string) =>
Array.from(mutersByPubkey.get().get(pubkey) || [])
export const getMuters = (pubkey: string) => Array.from(mutersByPubkey.get().get(pubkey) || [])
export const getFollowsWhoFollow = (pubkey: string, target: string) =>
getFollows(pubkey).filter(other => getFollows(other).includes(target))
+30 -19
View File
@@ -1,9 +1,19 @@
import {writable, derived} from 'svelte/store'
import {type Zapper} from '@welshman/util'
import {writable, derived} from "svelte/store"
import {type Zapper} from "@welshman/util"
import {type SubscribeRequestWithHandlers} from "@welshman/net"
import {ctx, identity, fetchJson, uniq, bech32ToHex, hexToBech32, tryCatch, batcher, postJson} from '@welshman/lib'
import {collection} from './collection'
import {deriveProfile} from './profiles'
import {
ctx,
identity,
fetchJson,
uniq,
bech32ToHex,
hexToBech32,
tryCatch,
batcher,
postJson,
} from "@welshman/lib"
import {collection} from "./collection.js"
import {deriveProfile} from "./profiles.js"
export const zappers = writable<Zapper[]>([])
@@ -16,7 +26,9 @@ export const fetchZappers = async (lnurls: string[]) => {
const hexUrls = lnurls.map(lnurl => tryCatch(() => bech32ToHex(lnurl))).filter(identity)
if (hexUrls.length > 0) {
const res: any = await tryCatch(async () => await postJson(`${base}/zapper/info`, {lnurls: hexUrls}))
const res: any = await tryCatch(
async () => await postJson(`${base}/zapper/info`, {lnurls: hexUrls}),
)
for (const {lnurl, info} of res?.data || []) {
tryCatch(() => zappersByLnurl.set(hexToBech32("lnurl", lnurl), info))
@@ -29,7 +41,7 @@ export const fetchZappers = async (lnurls: string[]) => {
const info = hexUrl ? await tryCatch(async () => await fetchJson(hexUrl)) : undefined
return {lnurl, hexUrl, info}
})
}),
)
for (const {lnurl, info} of results) {
@@ -68,17 +80,16 @@ export const {
}),
})
export const deriveZapperForPubkey = (pubkey: string, request: Partial<SubscribeRequestWithHandlers> = {}) =>
derived(
[zappersByLnurl, deriveProfile(pubkey, request)],
([$zappersByLnurl, $profile]) => {
if (!$profile?.lnurl) {
return undefined
}
loadZapper($profile.lnurl)
return $zappersByLnurl.get($profile.lnurl)
export const deriveZapperForPubkey = (
pubkey: string,
request: Partial<SubscribeRequestWithHandlers> = {},
) =>
derived([zappersByLnurl, deriveProfile(pubkey, request)], ([$zappersByLnurl, $profile]) => {
if (!$profile?.lnurl) {
return undefined
}
)
loadZapper($profile.lnurl)
return $zappersByLnurl.get($profile.lnurl)
})
-7
View File
@@ -1,7 +0,0 @@
{
"targets": [
{"extname": ".cjs", "module": "commonjs"},
{"extname": ".mjs", "module": "esnext", "moduleResolution": "node"}
],
"projects": ["tsconfig.json"]
}
+7 -4
View File
@@ -3,9 +3,12 @@
"compilerOptions": {
"rootDir": ".",
"outDir": "build",
"esModuleInterop": true,
"skipLibCheck": true,
"lib": ["esnext", "dom", "dom.iterable"]
"module": "nodenext",
"moduleResolution": "nodenext",
"lib": ["esnext", "dom"]
},
"include": ["src/**/*.ts"]
"include": [
"src/**/*.ts",
"test/**/*.ts"
]
}