Add domain object classes for nostr event types
tests / tests (push) Failing after 5m6s

Build out @welshman/domain on top of the DomainObject/EncryptableList base
patterns, porting domain-object use cases from @welshman/util and flotilla
that weren't yet represented.

New classes:
- Relay lists: RelayList (NIP-65 read/write markers), Blocked/Search/Messaging
  relay lists, RelaySet (NIP-51 30002 named set)
- Server lists: Blossom, FileServer
- NIP-51 lists: Follow, Pin, Bookmark, Community, Channel, Room, Feed, Topic,
  Emoji
- Zaps: ZapRequest, ZapReceipt, ZapGoal
- NIP-89 handlers: Handler, HandlerRecommendation
- Rooms/groups (NIP-29): RoomMeta, RoomAdmins, RoomMembers, RoomMembershipOp,
  Room create/delete/join/leave, RoomCreatePermission, RelayMembers,
  RelayMembershipOp, Relay join/leave/invite
- Content: Poll, PollResponse, Thread, Comment, Classified, CalendarEvent,
  Report, Feed, Settings

Also fix unfinished accessors in Profile, method-call bugs in MuteList, and
correct RelayList.set{Read,Write}Relays to preserve a relay's complementary
read/write capability instead of dropping modeless entries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V67tPYdvh1qCkjEBhJGZUR
This commit is contained in:
2026-06-18 21:16:27 +00:00
parent 925f540640
commit 99f5233e05
47 changed files with 3072 additions and 9 deletions
@@ -0,0 +1,80 @@
import {last} from "@welshman/lib"
import {HANDLER_RECOMMENDATION, getIdentifier, getAddressTags, getAddressTagValues} from "@welshman/util"
import type {EventTemplate, TrustedEvent} from "@welshman/util"
import {DomainObject} from "./base.js"
export type HandlerRecommendationValues = {
// The recommended kind, stored in the `d` tag.
identifier: string
// Raw `a` tags: ["a", address, relay?, platform?].
addresses: string[][]
}
export const makeHandlerRecommendationValues = (
values: Partial<HandlerRecommendationValues> = {},
): HandlerRecommendationValues => ({
identifier: "",
addresses: [],
...values,
})
// NIP-89 kind-31989 handler recommendation. Addressable (the `d` tag holds the
// recommended kind), tags-only with empty content. Each entry is a raw `a` tag
// pointing at a kind-31990 handler, optionally carrying a relay hint and a
// trailing platform marker (e.g. "web").
export class HandlerRecommendation extends DomainObject<HandlerRecommendationValues> {
readonly kind = HANDLER_RECOMMENDATION
values = makeHandlerRecommendationValues()
protected normalizeValues(values: Partial<HandlerRecommendationValues> = {}) {
return makeHandlerRecommendationValues(values)
}
protected parseEvent(event: TrustedEvent): Partial<HandlerRecommendationValues> {
return {
identifier: getIdentifier(event) || "",
addresses: getAddressTags(event.tags),
}
}
identifier() {
return this.values.identifier
}
addresses() {
return getAddressTagValues(this.values.addresses)
}
// Prefer the recommendation marked as a "web" handler, otherwise fall back to
// the first recommendation.
handlerAddress() {
const tag = this.values.addresses.find(t => last(t) === "web") || this.values.addresses[0]
return tag?.[1]
}
addRecommendation(address: string, relay?: string, platform?: string) {
if (!this.values.addresses.some(t => t[1] === address)) {
this.values.addresses = [
...this.values.addresses,
["a", address, relay || "", platform || ""],
]
}
return this
}
removeRecommendation(address: string) {
this.values.addresses = this.values.addresses.filter(t => t[1] !== address)
return this
}
async toTemplate(): Promise<EventTemplate> {
return {
kind: this.kind,
tags: [["d", this.values.identifier], ...this.values.addresses],
content: "",
}
}
}