Replace the single DomainObject/EncryptableList classes with a read/write split that removes the optional-event ambiguity: - base.ts: EventReader<P> (static kind; fromEvent(event, signer?) eagerly computes a generic `plain`, validates leniently, throws-or-passes; lazy method accessors; group/protect/expires + extraTags carry-over; builder()) and EventBuilder<P> (chainable setters, buildTags/buildContent, validate-on-emit). - List.ts: ListReader/ListBuilder for NIP-51 lists (decrypt-on-read into `plain`, re-encrypt-on-emit, tag mutators). - Every kind converted to a <Noun> reader + <Noun>Builder pair; membership ops split into per-kind reader/builder pairs over a shared abstract base. 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:
@@ -1,80 +1,82 @@
|
||||
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,
|
||||
})
|
||||
import {HANDLER_RECOMMENDATION, getAddressTags, getAddressTagValues} from "@welshman/util"
|
||||
import {EventReader, EventBuilder} from "./base.js"
|
||||
|
||||
// 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()
|
||||
export class HandlerRecommendation extends EventReader {
|
||||
static kind = HANDLER_RECOMMENDATION
|
||||
|
||||
protected normalizeValues(values: Partial<HandlerRecommendationValues> = {}) {
|
||||
return makeHandlerRecommendationValues(values)
|
||||
}
|
||||
|
||||
protected parseEvent(event: TrustedEvent): Partial<HandlerRecommendationValues> {
|
||||
return {
|
||||
identifier: getIdentifier(event) || "",
|
||||
addresses: getAddressTags(event.tags),
|
||||
protected validate() {
|
||||
if (!this.identifier()) {
|
||||
throw new Error("HandlerRecommendation requires a d tag")
|
||||
}
|
||||
}
|
||||
|
||||
identifier() {
|
||||
return this.values.identifier
|
||||
protected reservedTagKeys() {
|
||||
return ["d", "a"]
|
||||
}
|
||||
|
||||
// Raw `a` tags: ["a", address, relay?, platform?].
|
||||
addressTags() {
|
||||
return getAddressTags(this.event.tags)
|
||||
}
|
||||
|
||||
addresses() {
|
||||
return getAddressTagValues(this.values.addresses)
|
||||
return getAddressTagValues(this.event.tags)
|
||||
}
|
||||
|
||||
// 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]
|
||||
const tags = this.addressTags()
|
||||
const tag = tags.find(t => last(t) === "web") || tags[0]
|
||||
|
||||
return tag?.[1]
|
||||
}
|
||||
|
||||
builder() {
|
||||
const builder = new HandlerRecommendationBuilder(this.identifier() || "")
|
||||
|
||||
builder.addressTags = this.addressTags()
|
||||
|
||||
return this.seedBuilder(builder)
|
||||
}
|
||||
}
|
||||
|
||||
export class HandlerRecommendationBuilder extends EventBuilder {
|
||||
static kind = HANDLER_RECOMMENDATION
|
||||
|
||||
// Raw `a` tags: ["a", address, relay?, platform?].
|
||||
addressTags: string[][] = []
|
||||
|
||||
constructor(public identifier: string) {
|
||||
super()
|
||||
}
|
||||
|
||||
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 || ""],
|
||||
]
|
||||
if (!this.addressTags.some(t => t[1] === address)) {
|
||||
this.addressTags = [...this.addressTags, ["a", address, relay || "", platform || ""]]
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
removeRecommendation(address: string) {
|
||||
this.values.addresses = this.values.addresses.filter(t => t[1] !== address)
|
||||
this.addressTags = this.addressTags.filter(t => t[1] !== address)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
async toTemplate(): Promise<EventTemplate> {
|
||||
return {
|
||||
kind: this.kind,
|
||||
tags: [["d", this.values.identifier], ...this.values.addresses],
|
||||
content: "",
|
||||
protected validate() {
|
||||
if (!this.identifier) {
|
||||
throw new Error("HandlerRecommendation requires a d identifier")
|
||||
}
|
||||
}
|
||||
|
||||
protected buildTags() {
|
||||
return [["d", this.identifier], ...this.addressTags]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user