Query events through welshman's Events plugin and delete the repository.ts wrappers around it

This commit is contained in:
Jon Staab 2026-09-25 16:11:59 -07:00
parent f10dc96c43
commit 964a1bb6fa
36 changed files with 123 additions and 193 deletions

View file

@ -324,7 +324,7 @@ card, and `NoteContentClassified` renders a listing wherever `NoteContent` is us
**Routes.** `src/routes/spaces/[relay]/classifieds/+page.svelte` loads listings and their comments **Routes.** `src/routes/spaces/[relay]/classifieds/+page.svelte` loads listings and their comments
with `makeFeed` and filters them with the `classifieds.ts` helpers. `[address]/+page.svelte` reads with `makeFeed` and filters them with the `classifieds.ts` helpers. `[address]/+page.svelte` reads
one listing with `deriveEvent(address, [url])`. one listing with `$events.one(address, [url]).$`.
Articles are composed on a full page (`spaces/[relay]/articles/create`, built by Articles are composed on a full page (`spaces/[relay]/articles/create`, built by
`makeArticleCreatePath`) instead of in a modal. `makeArticleCreatePath`) instead of in a modal.

View file

@ -54,8 +54,8 @@ members and pins, the space member list, and roles. Anyone can publish events of
readers must check the author. The welshman collections do: `Rooms` and every readers must check the author. The welshman collections do: `Rooms` and every
`RelaySignedDerivedPlugin` (`RelayMemberLists`, `RelayRoles`, `RoomPinLists`) drop events whose `RelaySignedDerivedPlugin` (`RelayMemberLists`, `RelayRoles`, `RoomPinLists`) drop events whose
author isn't the relay's `self`, and re-check when NIP-11 loads. For a relay-authored kind with no author isn't the relay's `self`, and re-check when NIP-11 loads. For a relay-authored kind with no
plugin, filter `deriveEventsForUrl(url, filters)` on that `self` key yourself rather than reading a plugin, read `$events.relaySignedForUrl(url, filters)`, which keeps only events signed by that
bare `deriveEventsForUrl`. `self` key, rather than a bare `$events.forUrl`.
The app signs everything it publishes with the user's own key. Space-wide content with no author of The app signs everything it publishes with the user's own key. Space-wide content with no author of
its own belongs to the space's owner, the pubkey NIP-11 names: featured content its own belongs to the space's owner, the pubkey NIP-11 names: featured content
@ -235,7 +235,7 @@ space it lists or used to list, so each relay hears about joins and leaves. Kind
so `RoomChat` and `publishRoomQuote` publish raw templates through `Thunks`. so `RoomChat` and `publishRoomQuote` publish raw templates through `Thunks`.
Reads are scoped the same way. Request from the space relay (`relays: [url]`, plus `"#h": [h]` for Reads are scoped the same way. Request from the space relay (`relays: [url]`, plus `"#h": [h]` for
a room) and read results with `deriveEventsForUrl(url, filters)`, which uses the tracker to keep a room) and read results with `$events.forUrl(url, filters)`, which uses the tracker to keep
only events seen on that relay. A repository-wide query would mix rooms that share an `h` across only events seen on that relay. A repository-wide query would mix rooms that share an `h` across
relays. `src/app/sync.ts` pulls each space's room state, membership and recent content in the relays. `src/app/sync.ts` pulls each space's room state, membership and recent content in the
background; see flotilla-state. background; see flotilla-state.

View file

@ -36,7 +36,7 @@ The app is therefore stable for as long as anything under the login gate is moun
| `session` | the persisted `Session` | `undefined` | | `session` | the persisted `Session` | `undefined` |
| `user` | `User.require($app)`, derived | subscribing or `.get()` throws | | `user` | `User.require($app)`, derived | subscribing or `.get()` throws |
| `usePlugin(Plugin)` | a store holding `$app.use(Plugin)` for the current app | safe | | `usePlugin(Plugin)` | a store holding `$app.use(Plugin)` for the current app | safe |
| `profiles`, `rooms`, `relays`, `thunks`, … | `usePlugin` for 27 welshman plugins | safe | | `profiles`, `rooms`, `relays`, `events`, `thunks`, … | `usePlugin` for 28 welshman plugins | safe |
| `fromApp(read)` | a store that re-reads `read($app)` when the app changes | safe | | `fromApp(read)` | a store that re-reads `read($app)` when the app changes | safe |
| `deriveUserItem(Plugin)` | the signed-in user's entry in a keyed plugin | `undefined` | | `deriveUserItem(Plugin)` | the signed-in user's entry in a keyed plugin | `undefined` |
| `userSearchRelayUrls` | the user's search relays, or `DEFAULT_SEARCH_RELAYS` | the defaults | | `userSearchRelayUrls` | the user's search relays, or `DEFAULT_SEARCH_RELAYS` | the defaults |
@ -175,16 +175,19 @@ Events enter `app.repository` from `ingestPolicy` (which calls `tracker.track`,
optimistically. The tracker records which relays each event was seen on, which is what lets optimistically. The tracker records which relays each event was seen on, which is what lets
space content be keyed by relay. space content be keyed by relay.
`src/app/repository.ts` wraps the `@welshman/store` derivations in `fromApp`: Raw event queries go through welshman's `Events` plugin, exported from `core.ts` as `events`.
Every method returns a projection over the current app's repository:
- `deriveEvent`, `deriveEvents`, `deriveEventsById`, `deriveIsDeleted` - `one(idOrAddress, relays)` (loads when nothing local matches, keeps deleted events), `byId`,
- relay-scoped: `deriveEventsForUrl`, `deriveEventsByIdForUrl`, `deriveEventsByIdByUrl`, `all`, `asc`, `desc`, `isDeleted`
`getEventsForUrl` - relay-scoped through the tracker: `forUrl`, `byIdForUrl`, `byIdByUrl`, and
- `deriveLatestEvent` `relaySignedForUrl`, which keeps only events signed by the relay's NIP-11 `self`
Use these for raw event queries. Welshman's `Events` plugin has the same surface returning Components bind `$events.forUrl(url, filters).$`. A `derive*` function called from gated code
projections, and flotilla does not use it. Plugin reads (`get`, `one`, `load`, `index`) are binds `events.get().forUrl(...).$` at call time, and a module-scope store wraps it in
documented in welshman-app. `fromApp($app => $app.use(Events).byIdByUrl(filters).$)`, as `latestActivityByPath` does.
`src/app/repository.ts` holds only `deriveLatestEvent`. Plugin reads (`get`, `one`, `load`,
`index`) are documented in welshman-app.
### Free functions in an app module ### Free functions in an app module
@ -196,7 +199,7 @@ projections and repository derivations:
export const deriveSpaceActionItems = (url: string) => export const deriveSpaceActionItems = (url: string) =>
derived( derived(
[ [
deriveEventsForUrl(url, [{kinds: [REPORT]}]), events.get().forUrl(url, [{kinds: [REPORT]}]).$,
rooms.get().pendingJoins(url).$, rooms.get().pendingJoins(url).$,
deriveSpacePermissions(url), deriveSpacePermissions(url),
], ],
@ -424,7 +427,7 @@ store, consumed in components with `$store`.
Take the first answer that fits: Take the first answer that fits:
1. **It is an event, or derived from events.** It is already in the repository, or should be. 1. **It is an event, or derived from events.** It is already in the repository, or should be.
Read it with a plugin or an `@app/repository` derivation, and put the domain logic in a Read it with a plugin or the `events` projections, and put the domain logic in a
`derive*` function in the owning app module (`deriveUserRooms`). Don't copy it into a `derive*` function in the owning app module (`deriveUserRooms`). Don't copy it into a
writable. writable.
2. **It is a keyed collection of one kind, loaded by key.** Write a plugin. A generic kind goes 2. **It is a keyed collection of one kind, loaded by key.** Write a plugin. A generic kind goes

View file

@ -254,7 +254,7 @@ stores, so a component takes the key and looks up the rest: `RoomName` takes `{u
`$rooms.forRoom(url, h)`, `ProfileName` takes `pubkey` and reads `$profiles.display`. `$rooms.forRoom(url, h)`, `ProfileName` takes `pubkey` and reads `$profiles.display`.
Events are the exception, and 66 components take `event: TrustedEvent`, since the parent already Events are the exception, and 66 components take `event: TrustedEvent`, since the parent already
holds the event from a feed or a `deriveEvent`. An event prop usually travels with `url`, the holds the event from a feed or `$events.one`. An event prop usually travels with `url`, the
relay it lives on, which routes its replies and reactions, and with `context: FeedContext`, the relay it lives on, which routes its replies and reactions, and with `context: FeedContext`, the
shared loader below. A component takes a pointer instead only when it has to load the event shared loader below. A component takes a pointer instead only when it has to load the event
itself, as `ContentQuote` does. Anything with neither a store nor an event is passed as its itself, as `ContentQuote` does. Anything with neither a store nor an event is passed as its
@ -281,7 +281,7 @@ param change and lists key by id. A prop that does change while mounted needs `$
## Reading state in a component ## Reading state in a component
Components read plugins through the `usePlugin` stores exported from `src/app/core.ts` Components read plugins through the `usePlugin` stores exported from `src/app/core.ts`
(`$profiles`, `$relays`, `$rooms`, `$roomLists`, `$network`, `$thunks`, `$deletes`, (`$profiles`, `$relays`, `$rooms`, `$roomLists`, `$events`, `$network`, `$thunks`, `$deletes`,
`$relayManagement`, …). `$app.use(X)` covers plugins with no export, such as `Zappers`, `$relayManagement`, …). `$app.use(X)` covers plugins with no export, such as `Zappers`,
`Pinboards` and `Feeds`; flotilla-state has the rules about which to use where. `Pinboards` and `Feeds`; flotilla-state has the rules about which to use where.
@ -291,6 +291,7 @@ How you bind depends on what the method returns:
<script lang="ts"> <script lang="ts">
const room = $rooms.forRoom(url, h) // Readable: one, forRoom const room = $rooms.forRoom(url, h) // Readable: one, forRoom
const display = $profiles.display(pubkey, removeUndefined([url])).$ // Projection: take .$ const display = $profiles.display(pubkey, removeUndefined([url])).$ // Projection: take .$
const replies = $events.asc(filters).$ // raw repository queries
const shouldProtect = $relays.hasNip(url, 70) // Promise: load, hasNip const shouldProtect = $relays.hasNip(url, 70) // Promise: load, hasNip
</script> </script>
@ -298,7 +299,7 @@ How you bind depends on what the method returns:
``` ```
In a handler, call `.get()` on a projection instead of subscribing. The app modules add `derive*` In a handler, call `.get()` on a projection instead of subscribing. The app modules add `derive*`
factories over the same data: `deriveEvent` and `deriveEventsById` in `src/app/repository.ts`, factories over the same data: `deriveSpaceActionItems` in `src/app/actionItems.ts`,
`deriveSpaceSupportedMethods` in `src/app/management.ts`, `deriveRelayAuthError` in `deriveSpaceSupportedMethods` in `src/app/management.ts`, `deriveRelayAuthError` in
`src/app/access.ts`. Call them at the top of the script with fixed arguments, and wrap one in `src/app/access.ts`. Call them at the top of the script with fixed arguments, and wrap one in
`$derived` only when its arguments change, as the thread page does for filters that wait on the `$derived` only when its arguments change, as the thread page does for filters that wait on the
@ -316,7 +317,7 @@ data every page needs lives in `src/app/sync.ts`.
`people/[npub]/+page.svelte` does for the profile, relay list, follow, pin, room and messaging `people/[npub]/+page.svelte` does for the profile, relay list, follow, pin, room and messaging
lists before loading the author's outbox with `$network.loadLenient`. lists before loading the author's outbox with `$network.loadLenient`.
**A detail page:** `deriveEvent(address, [url])` loads when nothing local matches. While it is **A detail page:** `$events.one(address, [url]).$` loads when nothing local matches. While it is
empty, seven pages show a spinner that turns into a failure message: empty, seven pages show a spinner that turns into a failure message:
```svelte ```svelte
@ -328,7 +329,7 @@ empty, seven pages show a spinner that turns into a failure message:
``` ```
**Related events:** request them, abort on teardown, and read them back from the repository with **Related events:** request them, abort on teardown, and read them back from the repository with
`deriveEventsAsc(deriveEventsById(filters))`. `$events.asc(filters).$`.
```typescript ```typescript
onMount(() => { onMount(() => {
@ -460,8 +461,8 @@ Rich text uses `makeEditor` from `src/app/editor` with `EditorContent`.
- `flotilla-architecture`: layer rules and exceptions, the `src/app` modules, boot, placement - `flotilla-architecture`: layer rules and exceptions, the `src/app` modules, boot, placement
- `flotilla-state`: plugin stores, `derive*` stores, drafts, settings, thunks and commands - `flotilla-state`: plugin stores, `derive*` stores, drafts, settings, thunks and commands
- `flotilla-model`: spaces, rooms, NIP-43/29/86, content kinds, and where events are published - `flotilla-model`: spaces, rooms, NIP-43/29/86, content kinds, and where events are published
- `welshman-app`: plugins, projections, `Command`, thunks, `Feeds` - `welshman-app`: plugins, projections, `Command`, thunks, `Events`, `Feeds`
- `welshman-store`: `deriveEventsById`, `deriveEventsAsc`, the other repository stores - `welshman-store`: `deriveEventsById`, `deriveEventsAsc`, the repository stores under `Events`
- `welshman-domain`: the readers and writers behind `reader` and `writer` - `welshman-domain`: the readers and writers behind `reader` and `writer`
- `welshman-feeds`: `FeedController`, still used by `ProfileFeed` - `welshman-feeds`: `FeedController`, still used by `ProfileFeed`
- `welshman-editor`: the composer behind `makeEditor` - `welshman-editor`: the composer behind `makeEditor`

View file

@ -1,8 +1,7 @@
import {derived} from "svelte/store" import {derived} from "svelte/store"
import {REPORT, sortEventsDesc} from "@welshman/util" import {REPORT, sortEventsDesc} from "@welshman/util"
import {rooms} from "@app/core" import {events, rooms} from "@app/core"
import {deriveSpacePermissions} from "@app/management" import {deriveSpacePermissions} from "@app/management"
import {deriveEventsForUrl} from "@app/repository"
// Action items (admin review queue) // Action items (admin review queue)
@ -10,7 +9,7 @@ import {deriveEventsForUrl} from "@app/repository"
export const deriveSpaceActionItems = (url: string) => export const deriveSpaceActionItems = (url: string) =>
derived( derived(
[ [
deriveEventsForUrl(url, [{kinds: [REPORT]}]), events.get().forUrl(url, [{kinds: [REPORT]}]).$,
rooms.get().pendingJoins(url).$, rooms.get().pendingJoins(url).$,
deriveSpacePermissions(url), deriveSpacePermissions(url),
], ],

View file

@ -14,8 +14,7 @@ import type {TrustedEvent} from "@welshman/util"
import {CalendarRsvp, TimeEvent} from "@welshman/domain" import {CalendarRsvp, TimeEvent} from "@welshman/domain"
import type {RsvpStatus} from "@welshman/domain" import type {RsvpStatus} from "@welshman/domain"
import {synced} from "@welshman/store" import {synced} from "@welshman/store"
import {deletes, reader, relays, thunks, writer} from "@app/core" import {deletes, events, reader, relays, thunks, writer} from "@app/core"
import {deriveEvents} from "@app/repository"
import {toSpace} from "@app/rooms" import {toSpace} from "@app/rooms"
import {kv} from "@app/storage" import {kv} from "@app/storage"
@ -237,7 +236,7 @@ export const makeRsvpFilter = (event: TrustedEvent) => ({
"#a": [getAddress(event)], "#a": [getAddress(event)],
}) })
export const deriveRsvps = (event: TrustedEvent) => deriveEvents([makeRsvpFilter(event)]) export const deriveRsvps = (event: TrustedEvent) => events.get().desc([makeRsvpFilter(event)]).$
export const isAttending = (status?: RsvpStatus) => status === "accepted" || status === "tentative" export const isAttending = (status?: RsvpStatus) => status === "accepted" || status === "tentative"

View file

@ -5,8 +5,7 @@ import {deriveDeduplicated} from "@welshman/store"
import {makeRoomKey} from "@welshman/app" import {makeRoomKey} from "@welshman/app"
import type {Room} from "@welshman/app" import type {Room} from "@welshman/app"
import type {Room as LiveKitRoom} from "livekit-client" import type {Room as LiveKitRoom} from "livekit-client"
import {network} from "@app/core" import {events, network} from "@app/core"
import {deriveEventsForUrl} from "@app/repository"
export const LIVEKIT_PARTICIPANTS = 39004 export const LIVEKIT_PARTICIPANTS = 39004
@ -304,7 +303,7 @@ export const deriveCallParticipants = (url: string, h: string) =>
participantMediaState, participantMediaState,
callTargetRoom, callTargetRoom,
deriveDeduplicated( deriveDeduplicated(
deriveEventsForUrl(url, [{kinds: [LIVEKIT_PARTICIPANTS], "#d": [h]}]), events.get().forUrl(url, [{kinds: [LIVEKIT_PARTICIPANTS], "#d": [h]}]).$,
events => first(sortEventsDesc(events)), events => first(sortEventsDesc(events)),
), ),
], ],

View file

@ -11,8 +11,7 @@
import EventActivity from "@app/components/EventActivity.svelte" import EventActivity from "@app/components/EventActivity.svelte"
import EventActions from "@app/components/EventActions.svelte" import EventActions from "@app/components/EventActions.svelte"
import EventActionBar from "@app/components/EventActionBar.svelte" import EventActionBar from "@app/components/EventActionBar.svelte"
import {reader} from "@app/core" import {events, reader} from "@app/core"
import {deriveIsDeleted} from "@app/repository"
import {makeSpacePath} from "@app/routes" import {makeSpacePath} from "@app/routes"
type Props = { type Props = {
@ -30,7 +29,7 @@
const article = $derived(reader(Article)(event)) const article = $derived(reader(Article)(event))
const h = $derived(article.room()) const h = $derived(article.room())
const topics = $derived(article.topics()) const topics = $derived(article.topics())
const deleted = $derived(deriveIsDeleted(event)) const deleted = $derived($events.isDeleted(event).$)
const deleteReaction = (reaction: TrustedEvent) => retractReaction(reaction, {url, h}) const deleteReaction = (reaction: TrustedEvent) => retractReaction(reaction, {url, h})

View file

@ -9,8 +9,7 @@
import Profile from "@app/components/Profile.svelte" import Profile from "@app/components/Profile.svelte"
import ProfileName from "@app/components/ProfileName.svelte" import ProfileName from "@app/components/ProfileName.svelte"
import ProfileAbout from "@app/components/ProfileAbout.svelte" import ProfileAbout from "@app/components/ProfileAbout.svelte"
import {network, reader} from "@app/core" import {events, network, reader} from "@app/core"
import {deriveEventsForUrl} from "@app/repository"
import {displayReadingTime} from "@app/articles" import {displayReadingTime} from "@app/articles"
import {makeArticlePath} from "@app/routes" import {makeArticlePath} from "@app/routes"
@ -24,15 +23,15 @@
const MAX_ARTICLES = 6 const MAX_ARTICLES = 6
const filters = [{kinds: [LONG_FORM], authors: [event.pubkey], limit: 20}] const filters = [{kinds: [LONG_FORM], authors: [event.pubkey], limit: 20}]
const events = deriveEventsForUrl(url, filters) const articles = $events.forUrl(url, filters).$
const address = getAddress(event) const address = getAddress(event)
const articleCount = $derived($events.length) const articleCount = $derived($articles.length)
const others = $derived( const others = $derived(
sortBy( sortBy(
e => -reader(Article)(e).publishedAt(), e => -reader(Article)(e).publishedAt(),
$events.filter(e => getAddress(e) !== address), $articles.filter(e => getAddress(e) !== address),
).slice(0, MAX_ARTICLES), ).slice(0, MAX_ARTICLES),
) )

View file

@ -17,8 +17,7 @@
import NoteCard from "@app/components/NoteCard.svelte" import NoteCard from "@app/components/NoteCard.svelte"
import NoteContent from "@app/components/NoteContent.svelte" import NoteContent from "@app/components/NoteContent.svelte"
import NoteContentMinimal from "@app/components/NoteContentMinimal.svelte" import NoteContentMinimal from "@app/components/NoteContentMinimal.svelte"
import {network, router} from "@app/core" import {events, network, router} from "@app/core"
import {deriveEvent} from "@app/repository"
import {entityLink} from "@app/env" import {entityLink} from "@app/env"
import {goToEvent} from "@app/routes" import {goToEvent} from "@app/routes"
@ -39,7 +38,7 @@
const mergedRelays = writable(hints) const mergedRelays = writable(hints)
const quote = deriveEvent(idOrAddress, hints) const quote = $events.one(idOrAddress, hints).$
// Whoever quoted the event must have seen it, so the quoting event's relays are asked too. // Whoever quoted the event must have seen it, so the quoting event's relays are asked too.
$router.resolver $router.resolver

View file

@ -3,15 +3,13 @@
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {getCommentFiltersForRoot} from "@welshman/util" import {getCommentFiltersForRoot} from "@welshman/util"
import {deriveEventsAsc} from "@welshman/store"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import CommentTree from "@app/components/CommentTree.svelte" import CommentTree from "@app/components/CommentTree.svelte"
import type {FeedContext} from "@app/feeds" import type {FeedContext} from "@app/feeds"
import CommentCompose from "@app/components/CommentCompose.svelte" import CommentCompose from "@app/components/CommentCompose.svelte"
import {network} from "@app/core" import {events, network} from "@app/core"
import {deriveEventsById} from "@app/repository"
import {buildCommentTree} from "@app/social" import {buildCommentTree} from "@app/social"
type Props = { type Props = {
@ -24,7 +22,7 @@
const relays = removeUndefined([url]) const relays = removeUndefined([url])
const filters = getCommentFiltersForRoot([event]) const filters = getCommentFiltersForRoot([event])
const comments = deriveEventsAsc(deriveEventsById(filters)) const comments = $events.asc(filters).$
const nodes = $derived(buildCommentTree(event, $comments)) const nodes = $derived(buildCommentTree(event, $comments))

View file

@ -3,12 +3,11 @@
import {POLL_RESPONSE} from "@welshman/util" import {POLL_RESPONSE} from "@welshman/util"
import {Poll} from "@welshman/domain" import {Poll} from "@welshman/domain"
import ContentMinimal from "@app/components/ContentMinimal.svelte" import ContentMinimal from "@app/components/ContentMinimal.svelte"
import {reader} from "@app/core" import {events, reader} from "@app/core"
import {deriveEvents} from "@app/repository"
const props: ComponentProps<typeof ContentMinimal> = $props() const props: ComponentProps<typeof ContentMinimal> = $props()
const responses = deriveEvents([{kinds: [POLL_RESPONSE], "#e": [props.event.id]}]) const responses = $events.desc([{kinds: [POLL_RESPONSE], "#e": [props.event.id]}]).$
const voters = $derived(reader(Poll)(props.event).results($responses).voters) const voters = $derived(reader(Poll)(props.event).results($responses).voters)
</script> </script>

View file

@ -2,8 +2,8 @@
import {onDestroy} from "svelte" import {onDestroy} from "svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import NoteItem from "@app/components/NoteItem.svelte" import NoteItem from "@app/components/NoteItem.svelte"
import {events} from "@app/core"
import {makeFeedContext} from "@app/feeds" import {makeFeedContext} from "@app/feeds"
import {deriveEvent} from "@app/repository"
type Props = { type Props = {
url?: string url?: string
@ -13,7 +13,7 @@
const {url, value, relays}: Props = $props() const {url, value, relays}: Props = $props()
const event = deriveEvent(value, relays) const event = $events.one(value, relays).$
const context = makeFeedContext({relays}) const context = makeFeedContext({relays})
onDestroy(context.cleanup) onDestroy(context.cleanup)

View file

@ -15,9 +15,8 @@
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte" import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
import RelayName from "@app/components/RelayName.svelte" import RelayName from "@app/components/RelayName.svelte"
import PinAdd from "@app/components/PinAdd.svelte" import PinAdd from "@app/components/PinAdd.svelte"
import {reader} from "@app/core" import {events, reader} from "@app/core"
import {eventToReference} from "@app/pinboards" import {eventToReference} from "@app/pinboards"
import {deriveEventsForUrl} from "@app/repository"
import {makeSpacePath} from "@app/routes" import {makeSpacePath} from "@app/routes"
import {pushModal} from "@app/modal" import {pushModal} from "@app/modal"
@ -28,8 +27,8 @@
const {url, event}: Props = $props() const {url, event}: Props = $props()
const boards = derived(deriveEventsForUrl(url, [{kinds: [PINBOARD]}]), $events => const boards = derived($events.forUrl(url, [{kinds: [PINBOARD]}]).$, $pinboards =>
$events.map(reader(Pinboard)), $pinboards.map(reader(Pinboard)),
) )
const reference = eventToReference(event) const reference = eventToReference(event)

View file

@ -6,8 +6,7 @@
import {Poll, PollResponse} from "@welshman/domain" import {Poll, PollResponse} from "@welshman/domain"
import type {Thunk} from "@welshman/app" import type {Thunk} from "@welshman/app"
import PollOption from "@app/components/PollOption.svelte" import PollOption from "@app/components/PollOption.svelte"
import {command, reader, thunks, user, writer} from "@app/core" import {command, events, reader, thunks, user, writer} from "@app/core"
import {deriveEvents} from "@app/repository"
type Props = { type Props = {
url: string url: string
@ -16,7 +15,7 @@
const {url, event}: Props = $props() const {url, event}: Props = $props()
const responses = deriveEvents([{kinds: [POLL_RESPONSE], "#e": [event.id]}]) const responses = $events.desc([{kinds: [POLL_RESPONSE], "#e": [event.id]}]).$
const getOwnResponse = (responses: TrustedEvent[]) => { const getOwnResponse = (responses: TrustedEvent[]) => {
let latest: TrustedEvent | undefined let latest: TrustedEvent | undefined

View file

@ -8,8 +8,8 @@
import ModalBody from "@lib/components/ModalBody.svelte" import ModalBody from "@lib/components/ModalBody.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import {deriveEventsById} from "@app/repository"
import ReportItem from "@app/components/ReportItem.svelte" import ReportItem from "@app/components/ReportItem.svelte"
import {events} from "@app/core"
type Props = { type Props = {
url: string url: string
@ -18,7 +18,7 @@
const {url, event}: Props = $props() const {url, event}: Props = $props()
const reports = deriveEventsById([{kinds: [REPORT], "#e": [event.id]}]) const reports = $events.byId([{kinds: [REPORT], "#e": [event.id]}]).$
const back = () => history.back() const back = () => history.back()

View file

@ -32,12 +32,11 @@
import RoomItemMenuButton from "@app/components/RoomItemMenuButton.svelte" import RoomItemMenuButton from "@app/components/RoomItemMenuButton.svelte"
import RoomItemMenuMobile from "@app/components/RoomItemMenuMobile.svelte" import RoomItemMenuMobile from "@app/components/RoomItemMenuMobile.svelte"
import RoomItemContent from "@app/components/RoomItemContent.svelte" import RoomItemContent from "@app/components/RoomItemContent.svelte"
import {profiles, thunks, user} from "@app/core" import {events, profiles, thunks, user} from "@app/core"
import {noThunks, thunksByEventId} from "@app/thunks" import {noThunks, thunksByEventId} from "@app/thunks"
import {colorFor} from "@app/theme" import {colorFor} from "@app/theme"
import {ENABLE_ZAPS} from "@app/env" import {ENABLE_ZAPS} from "@app/env"
import type {FeedContext} from "@app/feeds" import type {FeedContext} from "@app/feeds"
import {deriveEvent, deriveEventsForUrl} from "@app/repository"
import {highlightedEvent, makeContentPath, makeThreadPath} from "@app/routes" import {highlightedEvent, makeContentPath, makeThreadPath} from "@app/routes"
import {pushModal} from "@app/modal" import {pushModal} from "@app/modal"
@ -70,16 +69,16 @@
// A message that is nothing but a reference renders as the event it quotes. // A message that is nothing but a reference renders as the event it quotes.
const isQuoteOnly = event.content.trim().split(/\s/).length === 1 const isQuoteOnly = event.content.trim().split(/\s/).length === 1
const quote = isQuoteOnly ? getContentQuote(event.content) : undefined const quote = isQuoteOnly ? getContentQuote(event.content) : undefined
const innerEvent = quote ? deriveEvent(quote.value, [url]) : readable(undefined) const innerEvent = quote ? $events.one(quote.value, [url]).$ : readable(undefined)
const innerComments = $derived( const innerComments = $derived(
$innerEvent ? deriveEventsForUrl(url, getCommentFiltersForParent([$innerEvent])) : readable([]), $innerEvent ? $events.forUrl(url, getCommentFiltersForParent([$innerEvent])).$ : readable([]),
) )
const path = $derived( const path = $derived(
$innerEvent && makeContentPath(url, $innerEvent.kind, getIdOrAddress($innerEvent)), $innerEvent && makeContentPath(url, $innerEvent.kind, getIdOrAddress($innerEvent)),
) )
const threads = deriveEventsForUrl(url, [{kinds: [THREAD], "#q": [event.id]}]) const threads = $events.forUrl(url, [{kinds: [THREAD], "#q": [event.id]}]).$
const commenterDisplays = $derived( const commenterDisplays = $derived(
deriveDisplaysByPubkey(uniq($innerComments.map(e => e.pubkey)), url), deriveDisplaysByPubkey(uniq($innerComments.map(e => e.pubkey)), url),

View file

@ -14,8 +14,7 @@
import PeopleItem from "@app/components/PeopleItem.svelte" import PeopleItem from "@app/components/PeopleItem.svelte"
import NoteCard from "@app/components/NoteCard.svelte" import NoteCard from "@app/components/NoteCard.svelte"
import NoteContentMinimal from "@app/components/NoteContentMinimal.svelte" import NoteContentMinimal from "@app/components/NoteContentMinimal.svelte"
import {getEventsForUrl} from "@app/repository" import {events, network, profiles} from "@app/core"
import {network, profiles} from "@app/core"
import {popModal} from "@app/modal" import {popModal} from "@app/modal"
import {pushToast} from "@app/toast" import {pushToast} from "@app/toast"
import {goToEvent} from "@app/routes" import {goToEvent} from "@app/routes"
@ -52,7 +51,7 @@
results = sortEventsDesc( results = sortEventsDesc(
uniqBy( uniqBy(
(e: TrustedEvent) => e.id, (e: TrustedEvent) => e.id,
relays.flatMap(relay => Array.from(getEventsForUrl(relay, filters))), relays.flatMap(relay => events.get().forUrl(relay, filters).get()),
), ),
) )

View file

@ -6,8 +6,7 @@
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import Profile from "@app/components/Profile.svelte" import Profile from "@app/components/Profile.svelte"
import {relayMemberLists, relays} from "@app/core" import {events, relayMemberLists, relays} from "@app/core"
import {deriveEventsForUrl} from "@app/repository"
import {makeSpacePath} from "@app/routes" import {makeSpacePath} from "@app/routes"
type Props = { type Props = {
@ -18,7 +17,7 @@
const relay = $relays.one(url) const relay = $relays.one(url)
const members = $relayMemberLists.forUrl(url) const members = $relayMemberLists.forUrl(url)
const memberEvents = deriveEventsForUrl(url, [{kinds: [RELAY_ADD_MEMBER, RELAY_JOIN]}]) const memberEvents = $events.forUrl(url, [{kinds: [RELAY_ADD_MEMBER, RELAY_JOIN]}]).$
const admins = $derived($relay?.pubkey ? [$relay.pubkey] : []) const admins = $derived($relay?.pubkey ? [$relay.pubkey] : [])

View file

@ -17,10 +17,9 @@
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte" import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
import SpaceSearch from "@app/components/SpaceSearch.svelte" import SpaceSearch from "@app/components/SpaceSearch.svelte"
import {relays} from "@app/core" import {events, relays} from "@app/core"
import {ENABLE_ZAPS} from "@app/env" import {ENABLE_ZAPS} from "@app/env"
import {CONTENT_KINDS} from "@app/content" import {CONTENT_KINDS} from "@app/content"
import {deriveEventsForUrl} from "@app/repository"
import {makeSpacePath} from "@app/routes" import {makeSpacePath} from "@app/routes"
import {allNotifications, notifications} from "@app/notifications" import {allNotifications, notifications} from "@app/notifications"
import {pushModal} from "@app/modal" import {pushModal} from "@app/modal"
@ -46,8 +45,8 @@
const cachedKinds: number[] = getJson(spaceKindsKey) ?? [] const cachedKinds: number[] = getJson(spaceKindsKey) ?? []
const spaceKinds = deriveDeduplicatedByValue( const spaceKinds = deriveDeduplicatedByValue(
deriveEventsForUrl(url, [{kinds: CONTENT_KINDS}]), $events.forUrl(url, [{kinds: CONTENT_KINDS}]).$,
$events => new Set([...cachedKinds, ...$events.map(e => e.kind)]), $content => new Set([...cachedKinds, ...$content.map(e => e.kind)]),
) )
// A comment can name content this space doesn't have, and it counts toward the space either way. // A comment can name content this space doesn't have, and it counts toward the space either way.

View file

@ -11,7 +11,7 @@
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import ReportItem from "@app/components/ReportItem.svelte" import ReportItem from "@app/components/ReportItem.svelte"
import RelayName from "@app/components/RelayName.svelte" import RelayName from "@app/components/RelayName.svelte"
import {deriveEventsForUrl} from "@app/repository" import {events} from "@app/core"
interface Props { interface Props {
url: string url: string
@ -19,7 +19,7 @@
const {url}: Props = $props() const {url}: Props = $props()
const reports = deriveEventsForUrl(url, [{kinds: [REPORT]}]) const reports = $events.forUrl(url, [{kinds: [REPORT]}]).$
const back = () => history.back() const back = () => history.back()

View file

@ -10,6 +10,7 @@ import {
BlossomServerLists, BlossomServerLists,
Deletes, Deletes,
Domain, Domain,
Events,
FollowLists, FollowLists,
Handles, Handles,
Logger, Logger,
@ -130,6 +131,7 @@ export const blockedRelayLists = usePlugin(BlockedRelayLists)
export const blossomServerLists = usePlugin(BlossomServerLists) export const blossomServerLists = usePlugin(BlossomServerLists)
export const deletes = usePlugin(Deletes) export const deletes = usePlugin(Deletes)
export const domain = usePlugin(Domain) export const domain = usePlugin(Domain)
export const events = usePlugin(Events)
export const followLists = usePlugin(FollowLists) export const followLists = usePlugin(FollowLists)
export const handles = usePlugin(Handles) export const handles = usePlugin(Handles)
export const logger = usePlugin(Logger) export const logger = usePlugin(Logger)

View file

@ -2,8 +2,7 @@ import {derived} from "svelte/store"
import {APP_DATA, sortEventsDesc, tagSpec, tagValues} from "@welshman/util" import {APP_DATA, sortEventsDesc, tagSpec, tagValues} from "@welshman/util"
import {AppData} from "@welshman/domain" import {AppData} from "@welshman/domain"
import {Relays, publish} from "@welshman/app" import {Relays, publish} from "@welshman/app"
import {command, fromApp, writer} from "@app/core" import {command, events, fromApp, writer} from "@app/core"
import {deriveEventsForUrl} from "@app/repository"
import {toSpace} from "@app/rooms" import {toSpace} from "@app/rooms"
// NIP-78 app data from the pubkey the space's NIP-11 names, one ["content", <value>] tag per entry. // NIP-78 app data from the pubkey the space's NIP-11 names, one ["content", <value>] tag per entry.
@ -13,7 +12,7 @@ export const deriveFeaturedContent = (url: string) =>
derived( derived(
[ [
fromApp($app => $app.use(Relays).one(url)), fromApp($app => $app.use(Relays).one(url)),
deriveEventsForUrl(url, [{kinds: [APP_DATA], "#d": [FEATURED_CONTENT_D]}]), events.get().forUrl(url, [{kinds: [APP_DATA], "#d": [FEATURED_CONTENT_D]}]).$,
], ],
([$relay, $events]) => { ([$relay, $events]) => {
const [event] = sortEventsDesc($events.filter(e => e.pubkey === $relay?.pubkey)) const [event] = sortEventsDesc($events.filter(e => e.pubkey === $relay?.pubkey))

View file

@ -30,6 +30,7 @@ import {
tagValues, tagValues,
} from "@welshman/util" } from "@welshman/util"
import {Comment, TimeEvent} from "@welshman/domain" import {Comment, TimeEvent} from "@welshman/domain"
import {Events} from "@welshman/app"
import type {Maybe} from "@welshman/lib" import type {Maybe} from "@welshman/lib"
import type {Filter, TrustedEvent} from "@welshman/util" import type {Filter, TrustedEvent} from "@welshman/util"
import {mergeRepositoryUpdates} from "@welshman/net" import {mergeRepositoryUpdates} from "@welshman/net"
@ -39,7 +40,6 @@ import type {ScrollerOpts} from "@lib/html"
import {daysBetween} from "@lib/util" import {daysBetween} from "@lib/util"
import {EVENT_CONTEXT_KINDS, REACTION_KINDS} from "@app/content" import {EVENT_CONTEXT_KINDS, REACTION_KINDS} from "@app/content"
import {app, network, reader} from "@app/core" import {app, network, reader} from "@app/core"
import {getEventsForUrl} from "@app/repository"
const noEvents: TrustedEvent[] = [] const noEvents: TrustedEvent[] = []
@ -661,7 +661,7 @@ export const makeFeed = ({
// What the repository already holds is an answer too, so a page of it renders before any relay replies. // What the repository already holds is an answer too, so a page of it renders before any relay replies.
const stored = relays.map(url => const stored = relays.map(url =>
Array.from(getEventsForUrl(url, filters)).sort(compareEventsAsc).slice(-PAGE_SIZE), app.get().use(Events).forUrl(url, filters).get().sort(compareEventsAsc).slice(-PAGE_SIZE),
) )
if (stored.every(page => page.length > 0)) { if (stored.every(page => page.length > 0)) {
@ -712,7 +712,7 @@ export const makeCalendarFeed = ({
const events = writable( const events = writable(
uniqBy( uniqBy(
e => e.id, e => e.id,
relays.flatMap(url => Array.from(getEventsForUrl(url, filters))), relays.flatMap(url => app.get().use(Events).forUrl(url, filters).get()),
).sort(compareByStart), ).sort(compareByStart),
) )

View file

@ -3,8 +3,7 @@ import {ago, MINUTE, now, simpleCache} from "@welshman/lib"
import {ROOM_CREATE_PERMISSION, hexTags, tagValues} from "@welshman/util" import {ROOM_CREATE_PERMISSION, hexTags, tagValues} from "@welshman/util"
import {RelayManagement, Relays} from "@welshman/app" import {RelayManagement, Relays} from "@welshman/app"
import type {IApp} from "@welshman/app" import type {IApp} from "@welshman/app"
import {fromApp, usePlugin, user} from "@app/core" import {events, fromApp, usePlugin, user} from "@app/core"
import {deriveEventsForUrl} from "@app/repository"
// `supportedmethods` answers for the pubkey that signed the request, so this catalog is the client's. // `supportedmethods` answers for the pubkey that signed the request, so this catalog is the client's.
export const MANAGEMENT_METHOD_GROUPS = [ export const MANAGEMENT_METHOD_GROUPS = [
@ -221,7 +220,7 @@ export const deriveUserCanCreateRoom = (url: string) =>
derived( derived(
[ [
user, user,
deriveEventsForUrl(url, [{kinds: [ROOM_CREATE_PERMISSION]}]), events.get().forUrl(url, [{kinds: [ROOM_CREATE_PERMISSION]}]).$,
deriveUserIsSpaceStaff(url), deriveUserIsSpaceStaff(url),
], ],
([$user, $events, $isStaff]) => ([$user, $events, $isStaff]) =>

View file

@ -15,8 +15,7 @@ import {
} from "@welshman/util" } from "@welshman/util"
import {Comment} from "@welshman/domain" import {Comment} from "@welshman/domain"
import {synced, throttled, withGetter} from "@welshman/store" import {synced, throttled, withGetter} from "@welshman/store"
import {Relays, RoomLists} from "@welshman/app" import {Events, Relays, RoomLists} from "@welshman/app"
import {deriveEventsByIdByUrl} from "@app/repository"
import {app, fromApp, reader} from "@app/core" import {app, fromApp, reader} from "@app/core"
import {makeRoomPath, makeSpaceChatPath, makeChatPath, makeContentPath} from "@app/routes" import {makeRoomPath, makeSpaceChatPath, makeChatPath, makeContentPath} from "@app/routes"
import {CONTENT_KINDS, makeCommentFilter} from "@app/content" import {CONTENT_KINDS, makeCommentFilter} from "@app/content"
@ -236,10 +235,13 @@ export const latestActivityByPath = derived(
chatsById, chatsById,
fromApp($app => $app.use(Relays).index.$), fromApp($app => $app.use(Relays).index.$),
fromApp($app => $app.use(RoomLists).index.$), fromApp($app => $app.use(RoomLists).index.$),
deriveEventsByIdByUrl([ fromApp(
{kinds: [MESSAGE, ...CONTENT_KINDS]}, $app =>
makeCommentFilter(CONTENT_KINDS), $app
]), .use(Events)
.byIdByUrl([{kinds: [MESSAGE, ...CONTENT_KINDS]}, makeCommentFilter(CONTENT_KINDS)])
.$,
),
userSettingsValues, userSettingsValues,
], ],
identity, identity,

View file

@ -2,8 +2,7 @@ import {derived} from "svelte/store"
import type {Readable} from "svelte/store" import type {Readable} from "svelte/store"
import {getIdFilters, outbox} from "@welshman/util" import {getIdFilters, outbox} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {network, pinLists, router} from "@app/core" import {events, network, pinLists, router} from "@app/core"
import {deriveEvents} from "@app/repository"
export const derivePinnedEvents = (pubkey: string): Readable<TrustedEvent[]> => export const derivePinnedEvents = (pubkey: string): Readable<TrustedEvent[]> =>
derived( derived(
@ -22,7 +21,7 @@ export const derivePinnedEvents = (pubkey: string): Readable<TrustedEvent[]> =>
network.get().loadLenient({relays: $relays, filters, signal: controller.signal}), network.get().loadLenient({relays: $relays, filters, signal: controller.signal}),
) )
const unsubscribe = deriveEvents(filters).subscribe(set) const unsubscribe = events.get().desc(filters).$.subscribe(set)
return () => { return () => {
controller.abort() controller.abort()

View file

@ -3,61 +3,8 @@ import type {Unsubscriber} from "svelte/store"
import {first, on} from "@welshman/lib" import {first, on} from "@welshman/lib"
import type {Maybe} from "@welshman/lib" import type {Maybe} from "@welshman/lib"
import {sortEventsDesc} from "@welshman/util" import {sortEventsDesc} from "@welshman/util"
import type {Filter, TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import * as store from "@welshman/store" import {app} from "@app/core"
import {Network} from "@welshman/app"
import {app, fromApp} from "@app/core"
// Events
export const deriveEvent = (idOrAddress: string, relays: string[] = []) =>
fromApp($app =>
store.makeDeriveEvent({
repository: $app.repository,
includeDeleted: true,
onDerive: (filters: Filter[], hints: string[]) =>
$app.use(Network).loadLenient({filters, relays: hints}),
})(idOrAddress, relays),
)
export const deriveEventsById = (filters: Filter[] = [{}]) =>
fromApp($app => store.deriveEventsById({repository: $app.repository, filters}))
export const deriveEvents = (filters: Filter[] = [{}]) =>
store.deriveEventsDesc(deriveEventsById(filters))
export const deriveIsDeleted = (event: TrustedEvent) =>
fromApp($app => store.deriveIsDeleted($app.repository, event))
// Events on a relay
export const getEventsForUrl = (url: string, filters: Filter[] = [{}]) =>
store
.getEventsByIdForUrl({
url,
filters,
tracker: app.get().tracker,
repository: app.get().repository,
})
.values()
export const deriveEventsByIdForUrl = (url: string, filters: Filter[] = [{}]) =>
fromApp($app =>
store.deriveEventsByIdForUrl({
url,
filters,
tracker: $app.tracker,
repository: $app.repository,
}),
)
export const deriveEventsForUrl = (url: string, filters: Filter[] = [{}]) =>
store.deriveArray(deriveEventsByIdForUrl(url, filters))
export const deriveEventsByIdByUrl = (filters: Filter[] = [{}]) =>
fromApp($app =>
store.deriveEventsByIdByUrl({filters, tracker: $app.tracker, repository: $app.repository}),
)
// The most recent event held from one author. // The most recent event held from one author.

View file

@ -2,8 +2,7 @@ import {derived, readable} from "svelte/store"
import type {Readable} from "svelte/store" import type {Readable} from "svelte/store"
import {getIdFilters, getIdOrAddress} from "@welshman/util" import {getIdFilters, getIdOrAddress} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {network, roomPinLists} from "@app/core" import {events, network, roomPinLists} from "@app/core"
import {deriveEventsForUrl} from "@app/repository"
export const deriveRoomPinnedEvents = (url: string, h: string): Readable<TrustedEvent[]> => export const deriveRoomPinnedEvents = (url: string, h: string): Readable<TrustedEvent[]> =>
readable<TrustedEvent[]>([], set => { readable<TrustedEvent[]>([], set => {
@ -24,7 +23,10 @@ export const deriveRoomPinnedEvents = (url: string, h: string): Readable<Trusted
network.get().loadLenient({relays: [url], filters, signal: controller.signal}) network.get().loadLenient({relays: [url], filters, signal: controller.signal})
return deriveEventsForUrl(url, filters).subscribe($events => { return events
.get()
.forUrl(url, filters)
.$.subscribe($events => {
const byPin = new Map( const byPin = new Map(
$events.flatMap(event => [ $events.flatMap(event => [
[event.id, event], [event.id, event],

View file

@ -16,8 +16,7 @@
import EventComments from "@app/components/EventComments.svelte" import EventComments from "@app/components/EventComments.svelte"
import EventMenu from "@app/components/EventMenu.svelte" import EventMenu from "@app/components/EventMenu.svelte"
import ReadingProgress from "@app/components/ReadingProgress.svelte" import ReadingProgress from "@app/components/ReadingProgress.svelte"
import {reader} from "@app/core" import {events, reader} from "@app/core"
import {deriveEvent} from "@app/repository"
import {makeFeedContext} from "@app/feeds" import {makeFeedContext} from "@app/feeds"
import {decodeRelay} from "@app/relays" import {decodeRelay} from "@app/relays"
import {displayReadingTime} from "@app/articles" import {displayReadingTime} from "@app/articles"
@ -29,7 +28,7 @@
const {relay, address} = params const {relay, address} = params
const url = decodeRelay(relay) const url = decodeRelay(relay)
const context = makeFeedContext({relays: [url]}) const context = makeFeedContext({relays: [url]})
const event = deriveEvent(address, [url]) const event = $events.one(address, [url]).$
const article = derived(event, $event => ($event ? reader(Article)($event) : undefined)) const article = derived(event, $event => ($event ? reader(Article)($event) : undefined))
const h = $derived($article?.room()) const h = $derived($article?.room())
const topics = $derived(uniq($article?.topics() ?? [])) const topics = $derived(uniq($article?.topics() ?? []))

View file

@ -23,8 +23,7 @@
import CalendarEventPeople from "@app/components/CalendarEventPeople.svelte" import CalendarEventPeople from "@app/components/CalendarEventPeople.svelte"
import CalendarRsvp from "@app/components/CalendarRsvp.svelte" import CalendarRsvp from "@app/components/CalendarRsvp.svelte"
import {deriveRsvps, getRsvpsByStatus, makeRsvpFilter} from "@app/calendar" import {deriveRsvps, getRsvpsByStatus, makeRsvpFilter} from "@app/calendar"
import {network, reader} from "@app/core" import {events, network, reader} from "@app/core"
import {deriveEvent, deriveEvents} from "@app/repository"
import {makeFeedContext} from "@app/feeds" import {makeFeedContext} from "@app/feeds"
import {decodeRelay} from "@app/relays" import {decodeRelay} from "@app/relays"
import type {PageProps} from "./$types" import type {PageProps} from "./$types"
@ -34,10 +33,10 @@
const {relay, address} = params const {relay, address} = params
const url = decodeRelay(relay) const url = decodeRelay(relay)
const context = makeFeedContext({relays: [url]}) const context = makeFeedContext({relays: [url]})
const event = deriveEvent(address, [url]) const event = $events.one(address, [url]).$
const timeEvent = derived(event, $event => ($event ? reader(TimeEvent)($event) : undefined)) const timeEvent = derived(event, $event => ($event ? reader(TimeEvent)($event) : undefined))
const filters = $derived($event ? getCommentFiltersForRoot([$event]) : []) const filters = $derived($event ? getCommentFiltersForRoot([$event]) : [])
const replies = $derived(deriveEvents(filters)) const replies = $derived($events.desc(filters).$)
const rsvps = $derived($event ? deriveRsvps($event) : readable<TrustedEvent[]>([])) const rsvps = $derived($event ? deriveRsvps($event) : readable<TrustedEvent[]>([]))
const people = $derived(getRsvpsByStatus($rsvps)) const people = $derived(getRsvpsByStatus($rsvps))

View file

@ -10,8 +10,7 @@
import NoteCard from "@app/components/NoteCard.svelte" import NoteCard from "@app/components/NoteCard.svelte"
import ClassifiedActions from "@app/components/ClassifiedActions.svelte" import ClassifiedActions from "@app/components/ClassifiedActions.svelte"
import EventComments from "@app/components/EventComments.svelte" import EventComments from "@app/components/EventComments.svelte"
import {reader} from "@app/core" import {events, reader} from "@app/core"
import {deriveEvent} from "@app/repository"
import {makeFeedContext} from "@app/feeds" import {makeFeedContext} from "@app/feeds"
import {decodeRelay} from "@app/relays" import {decodeRelay} from "@app/relays"
import type {PageProps} from "./$types" import type {PageProps} from "./$types"
@ -21,7 +20,7 @@
const {relay, address} = params const {relay, address} = params
const url = decodeRelay(relay) const url = decodeRelay(relay)
const context = makeFeedContext({relays: [url]}) const context = makeFeedContext({relays: [url]})
const event = deriveEvent(address, [url]) const event = $events.one(address, [url]).$
const classified = derived(event, $event => ($event ? reader(Classified)($event) : undefined)) const classified = derived(event, $event => ($event ? reader(Classified)($event) : undefined))
const back = () => history.back() const back = () => history.back()

View file

@ -18,10 +18,9 @@
import GoalStatus from "@app/components/GoalStatus.svelte" import GoalStatus from "@app/components/GoalStatus.svelte"
import GoalSupporters from "@app/components/GoalSupporters.svelte" import GoalSupporters from "@app/components/GoalSupporters.svelte"
import EventComments from "@app/components/EventComments.svelte" import EventComments from "@app/components/EventComments.svelte"
import {reader} from "@app/core" import {events, reader} from "@app/core"
import {ENABLE_ZAPS} from "@app/env" import {ENABLE_ZAPS} from "@app/env"
import {deriveGoalProgress} from "@app/goals" import {deriveGoalProgress} from "@app/goals"
import {deriveEvent} from "@app/repository"
import {makeFeedContext} from "@app/feeds" import {makeFeedContext} from "@app/feeds"
import {decodeRelay} from "@app/relays" import {decodeRelay} from "@app/relays"
import type {PageProps} from "./$types" import type {PageProps} from "./$types"
@ -31,7 +30,7 @@
const {relay, id} = params const {relay, id} = params
const url = decodeRelay(relay) const url = decodeRelay(relay)
const context = makeFeedContext({relays: [url]}) const context = makeFeedContext({relays: [url]})
const event = deriveEvent(id, [url]) const event = $events.one(id, [url]).$
const goal = derived(event, $event => ($event ? reader(ZapGoal)($event) : undefined)) const goal = derived(event, $event => ($event ? reader(ZapGoal)($event) : undefined))
const progress = $derived($event ? deriveGoalProgress($event, url) : undefined) const progress = $derived($event ? deriveGoalProgress($event, url) : undefined)
const remaining = $derived($progress ? Math.max(0, $progress.target - $progress.raised) : 0) const remaining = $derived($progress ? Math.max(0, $progress.target - $progress.raised) : 0)

View file

@ -21,9 +21,8 @@
import PinAdd from "@app/components/PinAdd.svelte" import PinAdd from "@app/components/PinAdd.svelte"
import PinboardItem from "@app/components/PinboardItem.svelte" import PinboardItem from "@app/components/PinboardItem.svelte"
import PinboardEdit from "@app/components/PinboardEdit.svelte" import PinboardEdit from "@app/components/PinboardEdit.svelte"
import {app, network, reader} from "@app/core" import {app, events, network, reader} from "@app/core"
import {decodeRelay} from "@app/relays" import {decodeRelay} from "@app/relays"
import {deriveEventsForUrl} from "@app/repository"
import {makeLibraryPath} from "@app/routes" import {makeLibraryPath} from "@app/routes"
import {pushModal} from "@app/modal" import {pushModal} from "@app/modal"
import type {PageProps} from "./$types" import type {PageProps} from "./$types"
@ -36,8 +35,8 @@
let loading = $state(true) let loading = $state(true)
// A shelf belongs to the space it was published to rather than to the pubkey that signed it. // A shelf belongs to the space it was published to rather than to the pubkey that signed it.
const boards = derived(deriveEventsForUrl(url, [{kinds: [PINBOARD]}]), $events => const boards = derived($events.forUrl(url, [{kinds: [PINBOARD]}]).$, $pinboards =>
$events.map(reader(Pinboard)), $pinboards.map(reader(Pinboard)),
) )
const address = $derived($page.url.searchParams.get("board") ?? "") const address = $derived($page.url.searchParams.get("board") ?? "")

View file

@ -2,7 +2,6 @@
import {onDestroy, onMount} from "svelte" import {onDestroy, onMount} from "svelte"
import {sleep} from "@welshman/lib" import {sleep} from "@welshman/lib"
import {POLL, POLL_RESPONSE, getCommentFiltersForRoot} from "@welshman/util" import {POLL, POLL_RESPONSE, getCommentFiltersForRoot} from "@welshman/util"
import {deriveEventsAsc} from "@welshman/store"
import SortVertical from "@assets/icons/sort-vertical.svg?dataurl" import SortVertical from "@assets/icons/sort-vertical.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import PageContent from "@lib/components/PageContent.svelte" import PageContent from "@lib/components/PageContent.svelte"
@ -13,8 +12,7 @@
import NoteContent from "@app/components/NoteContent.svelte" import NoteContent from "@app/components/NoteContent.svelte"
import CommentActions from "@app/components/CommentActions.svelte" import CommentActions from "@app/components/CommentActions.svelte"
import EventReply from "@app/components/EventReply.svelte" import EventReply from "@app/components/EventReply.svelte"
import {network} from "@app/core" import {events, network} from "@app/core"
import {deriveEvent, deriveEventsById} from "@app/repository"
import {makeFeedContext} from "@app/feeds" import {makeFeedContext} from "@app/feeds"
import {decodeRelay} from "@app/relays" import {decodeRelay} from "@app/relays"
import type {PageProps} from "./$types" import type {PageProps} from "./$types"
@ -24,9 +22,9 @@
const {relay, id} = params const {relay, id} = params
const url = decodeRelay(relay) const url = decodeRelay(relay)
const context = makeFeedContext({relays: [url]}) const context = makeFeedContext({relays: [url]})
const event = deriveEvent(id, [url]) const event = $events.one(id, [url]).$
const filters = $derived($event ? getCommentFiltersForRoot([$event]) : []) const filters = $derived($event ? getCommentFiltersForRoot([$event]) : [])
const comments = $derived(deriveEventsAsc(deriveEventsById(filters))) const comments = $derived($events.asc(filters).$)
const back = () => history.back() const back = () => history.back()

View file

@ -3,7 +3,6 @@
import {page} from "$app/state" import {page} from "$app/state"
import {sleep, spec} from "@welshman/lib" import {sleep, spec} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {deriveEventsAsc} from "@welshman/store"
import {getCommentFiltersForRoot} from "@welshman/util" import {getCommentFiltersForRoot} from "@welshman/util"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl" import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl"
@ -15,8 +14,7 @@
import ThreadPost from "@app/components/ThreadPost.svelte" import ThreadPost from "@app/components/ThreadPost.svelte"
import ThreadSummaryBar from "@app/components/ThreadSummaryBar.svelte" import ThreadSummaryBar from "@app/components/ThreadSummaryBar.svelte"
import EventReply from "@app/components/EventReply.svelte" import EventReply from "@app/components/EventReply.svelte"
import {deriveEvent, deriveEventsById} from "@app/repository" import {events, network} from "@app/core"
import {network} from "@app/core"
import {makeFeedContext} from "@app/feeds" import {makeFeedContext} from "@app/feeds"
import {getChecked} from "@app/notifications" import {getChecked} from "@app/notifications"
import {decodeRelay} from "@app/relays" import {decodeRelay} from "@app/relays"
@ -29,11 +27,11 @@
const {relay, id} = params const {relay, id} = params
const url = decodeRelay(relay) const url = decodeRelay(relay)
const event = deriveEvent(id, [url]) const event = $events.one(id, [url]).$
// Rows register themselves with `related`, so the whole page's reactions load in one batch // Rows register themselves with `related`, so the whole page's reactions load in one batch
const context = makeFeedContext({relays: [url]}) const context = makeFeedContext({relays: [url]})
const filters = $derived($event ? getCommentFiltersForRoot([$event]) : []) const filters = $derived($event ? getCommentFiltersForRoot([$event]) : [])
const replies = $derived(deriveEventsAsc(deriveEventsById(filters))) const replies = $derived($events.asc(filters).$)
const scrollToTop = () => element?.scrollTo({top: 0, behavior: "smooth"}) const scrollToTop = () => element?.scrollTo({top: 0, behavior: "smooth"})