diff --git a/packages/app/src/feeds.ts b/packages/app/src/feeds.ts index b07aa97..564e2f6 100644 --- a/packages/app/src/feeds.ts +++ b/packages/app/src/feeds.ts @@ -1,6 +1,6 @@ import {Scope, FeedController, FeedControllerOptions, Feed} from "@welshman/feeds" import {pubkey, signer} from "./session.js" -import {wotGraph, maxWot, getFollows, getNetwork, getFollowers} from "./wot.js" +import {getWotGraph, getMaxWot, getFollows, getNetwork, getFollowers} from "./wot.js" export const getPubkeysForScope = (scope: string) => { const $pubkey = pubkey.get() @@ -25,10 +25,11 @@ export const getPubkeysForScope = (scope: string) => { export const getPubkeysForWOTRange = (min: number, max: number) => { const pubkeys = [] - const thresholdMin = maxWot.get() * min - const thresholdMax = maxWot.get() * max + const $maxWot = getMaxWot() + const thresholdMin = $maxWot * min + const thresholdMax = $maxWot * max - for (const [tpk, score] of wotGraph.get().entries()) { + for (const [tpk, score] of getWotGraph().entries()) { if (score >= thresholdMin && score <= thresholdMax) { pubkeys.push(tpk) } diff --git a/packages/app/src/search.ts b/packages/app/src/search.ts index d629e6b..7dd7050 100644 --- a/packages/app/src/search.ts +++ b/packages/app/src/search.ts @@ -6,7 +6,7 @@ import {PROFILE, PublishedProfile, RelayProfile} from "@welshman/util" import {load} from "@welshman/net" import {throttled} from "@welshman/store" import {Router} from "@welshman/router" -import {wotGraph, maxWot} from "./wot.js" +import {getWotGraph, getMaxWot} from "./wot.js" import {profiles} from "./profiles.js" import {topics, Topic} from "./topics.js" import {relays} from "./relays.js" @@ -75,9 +75,9 @@ export const profileSearch = derived( onSearch: searchProfiles, getValue: (profile: PublishedProfile) => profile.event.pubkey, sortFn: ({score = 1, item}) => { - const wotScore = wotGraph.get().get(item.event.pubkey) || 0 + const wotScore = getWotGraph().get(item.event.pubkey) || 0 - return dec(score) * inc(wotScore / maxWot.get()) + return dec(score) * inc(wotScore / getMaxWot()) }, fuseOptions: { keys: [ diff --git a/packages/app/src/user.ts b/packages/app/src/user.ts index c4e3368..ad52525 100644 --- a/packages/app/src/user.ts +++ b/packages/app/src/user.ts @@ -1,5 +1,5 @@ import {derived, Readable} from "svelte/store" -import {withGetter, memoized} from "@welshman/store" +import {ItemsByKey} from "@welshman/store" import {pubkey} from "./session.js" import {profilesByPubkey, forceLoadProfile, loadProfile} from "./profiles.js" import {followListsByPubkey, forceLoadFollowList, loadFollowList} from "./follows.js" @@ -16,94 +16,61 @@ import { forceLoadMessagingRelayList, loadMessagingRelayList, } from "./messagingRelayLists.js" -import {wotGraph} from "./wot.js" +import {wotGraph, getWotGraph} from "./wot.js" -export type UserDataLoader = (pubkey: string, relays?: string[], force?: boolean) => unknown +export const makeUserData = ( + itemsByKey: Readable>, + onDerive?: (key: string, ...args: any[]) => void, +) => + derived([itemsByKey, pubkey], ([$itemsByKey, $pubkey]) => { + if (!$pubkey) return undefined -export type MakeUserDataOptions = { - mapStore: Readable> - loadItem: UserDataLoader -} + onDerive?.($pubkey) -export const makeUserData = ({mapStore, loadItem}: MakeUserDataOptions) => - withGetter( - memoized( - derived([mapStore, pubkey], ([$mapStore, $pubkey]) => { - if (!$pubkey) return undefined - - loadItem($pubkey) - - return $mapStore.get($pubkey) - }), - ), - ) + return $itemsByKey.get($pubkey) + }) export const makeUserLoader = - (loadItem: UserDataLoader) => - async (relays: string[] = [], force = false) => { + (loadItem: (key: string, ...args: any[]) => void) => + async (...args: any[]) => { const $pubkey = pubkey.get() if ($pubkey) { - await loadItem($pubkey, relays, force) + await loadItem($pubkey, ...args) } } -export const userProfile = makeUserData({ - mapStore: profilesByPubkey, - loadItem: loadProfile, -}) - +export const userProfile = makeUserData(profilesByPubkey, loadProfile) export const forceLoadUserProfile = makeUserLoader(forceLoadProfile) export const loadUserProfile = makeUserLoader(loadProfile) -export const userFollowList = makeUserData({ - mapStore: followListsByPubkey, - loadItem: loadFollowList, -}) - +export const userFollowList = makeUserData(followListsByPubkey, loadFollowList) export const forceLoadUserFollowList = makeUserLoader(forceLoadFollowList) export const loadUserFollowList = makeUserLoader(loadFollowList) -export const userMuteList = makeUserData({ - mapStore: muteListsByPubkey, - loadItem: loadMuteList, -}) - +export const userMuteList = makeUserData(muteListsByPubkey, loadMuteList) export const forceLoadUserMuteList = makeUserLoader(forceLoadMuteList) export const loadUserMuteList = makeUserLoader(loadMuteList) -export const userPinList = makeUserData({ - mapStore: pinListsByPubkey, - loadItem: loadPinList, -}) - +export const userPinList = makeUserData(pinListsByPubkey, loadPinList) export const forceLoadUserPinList = makeUserLoader(forceLoadPinList) export const loadUserPinList = makeUserLoader(loadPinList) -export const userRelayList = makeUserData({ - mapStore: relayListsByPubkey, - loadItem: loadRelayList, -}) - +export const userRelayList = makeUserData(relayListsByPubkey, loadRelayList) export const forceLoadUserRelayList = makeUserLoader(forceLoadRelayList) export const loadUserRelayList = makeUserLoader(loadRelayList) -export const userMessagingRelayList = makeUserData({ - mapStore: messagingRelayListsByPubkey, - loadItem: loadMessagingRelayList, -}) - +export const userMessagingRelayList = makeUserData( + messagingRelayListsByPubkey, + loadMessagingRelayList, +) export const forceLoadUserMessagingRelayList = makeUserLoader(forceLoadMessagingRelayList) export const loadUserMessagingRelayList = makeUserLoader(loadMessagingRelayList) -export const userBlossomServerList = makeUserData({ - mapStore: blossomServerListsByPubkey, - loadItem: loadBlossomServerList, -}) - +export const userBlossomServerList = makeUserData(blossomServerListsByPubkey, loadBlossomServerList) export const forceLoadUserBlossomServerList = makeUserLoader(forceLoadBlossomServerList) export const loadUserBlossomServerList = makeUserLoader(loadBlossomServerList) -export const getUserWotScore = (tpk: string) => wotGraph.get().get(tpk) || 0 +export const getUserWotScore = (tpk: string) => getWotGraph().get(tpk) || 0 export const deriveUserWotScore = (tpk: string) => derived(wotGraph, $g => $g.get(tpk) || 0) diff --git a/packages/app/src/wot.ts b/packages/app/src/wot.ts index c455cd0..c50e2d1 100644 --- a/packages/app/src/wot.ts +++ b/packages/app/src/wot.ts @@ -1,7 +1,7 @@ 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 {throttled, getter} from "@welshman/store" import {pubkey} from "./session.js" import {followLists, getFollowListsByPubkey, getFollowList} from "./follows.js" import {muteLists, getMuteList} from "./mutes.js" @@ -25,38 +25,37 @@ export const getNetwork = (pubkey: string) => { return Array.from(network) } -export const followersByPubkey = withGetter( - derived(throttled(1000, followLists), lists => { - const $followersByPubkey = new Map>() +export const followersByPubkey = derived(throttled(1000, followLists), lists => { + const $followersByPubkey = new Map>() - 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, muteLists), lists => { - const $mutersByPubkey = new Map>() +export const getFollowersByPubkey = getter(followersByPubkey) - for (const list of lists) { - for (const pubkey of getPubkeyTagValues(getListTags(list))) { - addToMapKey($mutersByPubkey, pubkey, list.event.pubkey) - } +export const mutersByPubkey = derived(throttled(1000, muteLists), lists => { + const $mutersByPubkey = new Map>() + + 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 getMutersByPubkey = getter(mutersByPubkey) -export const getMuters = (pubkey: string) => Array.from(mutersByPubkey.get().get(pubkey) || []) +export const getFollowers = (pubkey: string) => Array.from(getFollowersByPubkey().get(pubkey) || []) + +export const getMuters = (pubkey: string) => Array.from(getMutersByPubkey().get(pubkey) || []) export const getFollowsWhoFollow = (pubkey: string, target: string) => getFollows(pubkey).filter(other => getFollows(other).includes(target)) @@ -64,9 +63,13 @@ export const getFollowsWhoFollow = (pubkey: string, target: string) => export const getFollowsWhoMute = (pubkey: string, target: string) => getFollows(pubkey).filter(other => getMutes(other).includes(target)) -export const wotGraph = withGetter(writable(new Map())) +export const wotGraph = writable(new Map()) -export const maxWot = withGetter(derived(wotGraph, $g => max(Array.from($g.values())))) +export const getWotGraph = getter(wotGraph) + +export const maxWot = derived(wotGraph, $g => max(Array.from($g.values()))) + +export const getMaxWot = getter(maxWot) const buildGraph = throttle(1000, () => { const $pubkey = pubkey.get() diff --git a/packages/net/src/repository.ts b/packages/net/src/repository.ts index c5f9346..86fb0e1 100644 --- a/packages/net/src/repository.ts +++ b/packages/net/src/repository.ts @@ -110,7 +110,6 @@ export class Repository extends Emitter { removed.add(id) } - console.log("UPDATE") this.emit("update", {added, removed}) } diff --git a/packages/store/__tests__/index.test.ts b/packages/store/__tests__/index.test.ts index eddb9a8..7ffdf2a 100644 --- a/packages/store/__tests__/index.test.ts +++ b/packages/store/__tests__/index.test.ts @@ -1,16 +1,6 @@ -import {TrustedEvent} from "@welshman/util" -import {Repository} from "@welshman/net" import {get} from "svelte/store" import {afterEach, beforeEach, describe, expect, it, vi} from "vitest" -import { - deriveEvents, - deriveIsDeleted, - getter, - synced, - localStorageProvider, - throttled, - withGetter, -} from "../src/index" +import {getter, synced, localStorageProvider, throttled, withGetter} from "../src/index" // Mock localStorage const localStorageMock = (() => { @@ -139,138 +129,4 @@ describe("Store utilities", () => { expect(mockFn).toHaveBeenLastCalledWith(3) }) }) - - describe("custom", () => { - it("should handle updates correctly", () => { - const mockFn = vi.fn() - const store = custom(set => { - set(0) - return () => {} - }) - - store.subscribe(mockFn) - store.set(1) - store.update(n => n + 1) - - expect(mockFn).toHaveBeenCalledTimes(3) // Initial + set + update - expect(store.get()).toBe(2) - }) - }) - - describe("Event-related stores", () => { - const mockRepository = { - query: vi.fn(), - isDeleted: vi.fn(), - isDeletedByAddress: vi.fn(), - on: vi.fn(), - off: vi.fn(), - } satisfies Partial - - beforeEach(() => { - vi.clearAllMocks() - }) - - describe("deriveEvents", () => { - it("should derive events from repository", () => { - const mockEvent = {id: "1", content: "test"} as TrustedEvent - mockRepository.query.mockReturnValue([mockEvent]) - - const store = deriveEvents(mockRepository as any, { - filters: [], - }) - - const mockFn = vi.fn() - store.subscribe(mockFn) - - expect(mockRepository.query).toHaveBeenCalled() - expect(mockFn).toHaveBeenCalledWith([mockEvent]) - }) - }) - - describe("deriveEventsMapped", () => { - it("should map events to items", async () => { - const mockEvent = {id: "1", content: "test"} as TrustedEvent - mockRepository.query.mockReturnValue([mockEvent]) - - const store = deriveEventsMapped(mockRepository as any, { - filters: [], - eventToItem: event => ({id: event.id, mapped: true}), - itemToEvent: item => ({id: item.id, content: ""}) as TrustedEvent, - }) - - const mockFn = vi.fn() - store.subscribe(mockFn) - - expect(mockRepository.query).toHaveBeenCalled() - expect(mockFn).toHaveBeenCalledWith([{id: "1", mapped: true}]) - }) - - it("should handle async eventToItem mapping", async () => { - const mockEvent = {id: "1", content: "test"} as TrustedEvent - mockRepository.query.mockReturnValue([mockEvent]) - - const store = deriveEventsMapped(mockRepository as any, { - filters: [], - eventToItem: async event => ({id: event.id, mapped: true}), - itemToEvent: item => ({id: item.id, content: ""}) as TrustedEvent, - }) - - const mockFn = vi.fn() - store.subscribe(mockFn) - - // Wait for async operations to complete - await vi.runAllTimersAsync() - - expect(mockRepository.query).toHaveBeenCalled() - expect(mockFn).toHaveBeenCalledWith([{id: "1", mapped: true}]) - }) - - it("should handle repository updates", () => { - const mockEvent = {id: "1", content: "test"} as TrustedEvent - mockRepository.query.mockReturnValue([mockEvent]) - - const store = deriveEventsMapped(mockRepository as any, { - filters: [{}], - eventToItem: event => ({id: event.id, mapped: true}), - itemToEvent: item => ({id: item.id, content: ""}) as TrustedEvent, - }) - - const mockFn = vi.fn() - store.subscribe(mockFn) - - const [[_, callback]] = mockRepository.on.mock.calls - - callback({ - added: [{id: "2"} as TrustedEvent], - removed: new Set([mockEvent.id]), - }) - - vi.advanceTimersByTime(300) // Wait for batch delay - - expect(mockFn).toHaveBeenLastCalledWith([{id: "2", mapped: true}]) - }) - }) - - describe("deriveIsDeleted", () => { - it("should track deletion status", () => { - const mockEvent = {id: "1"} as TrustedEvent - - mockRepository.isDeleted.mockReturnValue(false) - - const store = deriveIsDeleted(mockRepository as any, mockEvent) - const mockFn = vi.fn() - store.subscribe(mockFn) - - expect(mockRepository.isDeleted).toHaveBeenCalledWith(mockEvent) - expect(mockFn).toHaveBeenCalledWith(false) - - const [[_, callback]] = mockRepository.on.mock.calls - - callback() - - expect(mockRepository.isDeleted).toHaveBeenCalledWith(mockEvent) - expect(mockFn).toHaveBeenCalledWith(false) - }) - }) - }) })