# Research: Kinds ## Topic Summary The kinds chapter should explain what kinds are without going into detail on any kind in particular (that's saved for domain chapters). Instead, the chapter should create some kind of factory/registry/enum that can be used to add behavior to a given kind — validation, event template factories, interfaces for decryption, methods for converting an event of a particular kind into a domain-specific struct, etc. Applesauce's factory pattern is preferred over welshman's ad-hoc utilities approach. ## Philosophy From building-nostr, several key principles inform this chapter: **Numbers Over Names:** Event kinds are 16-bit integers. Using numbers instead of names is deliberate — in a distributed system built on signed data, names can't change. Numbers are meaningless, which allows subjective interpretations while keeping meaning intact. This frees implementers to assign their own terms based on understanding. **Kind Ranges Encode Behavior:** The protocol defines ranges with storage/replacement semantics: - Regular (1-9999, with exceptions): standard events stored by relays - Replaceable (0, 3, 10000-19999): only latest per (pubkey, kind) kept - Ephemeral (20000-29999): not stored, just broadcast - Addressable (30000-39999): latest per (pubkey, kind, d-tag) kept The author criticizes this design as coupling kind numbers with behavior — these policies are orthogonal to content type and "should have belonged in behavior tags." **More Kinds, Less Ambiguity:** Prefer creating specific event kinds over overloading existing ones. "Overloading a single kind for multiple purposes is always a recipe for disaster." **Implementation-First:** Nostr takes an implementation-first approach. It's only through implementation that what is actually needed can be understood. ## Reference Implementation Analysis ### applesauce The most sophisticated factory pattern found. Uses a layered architecture: **EventFactory base class** extends `Promise` for fluent chaining. Core capabilities: - `EventFactory.fromKind(kind)` — create from kind number - `EventFactory.fromEvent(event)` — create from existing event (modify) - `.content()`, `.created()`, `.meta()` — base operations - `.modifyPublicTags()`, `.modifyHiddenTags()` — tag manipulation - `.encryptedContent()` — NIP-44 encryption - `.as(signer)` — set signer, propagated through chain - `.stamp()`, `.sign()` — finalize event **Typed Factory subclasses** per kind: ```typescript export class NoteFactory extends EventFactory { static create(content?: string): NoteFactory static reply(parent: NostrEvent, content?: string): NoteFactory text(content, options?) mention(pubkey) subject(subject) addHashtag(hashtag) } ``` **Dual system for create vs consume:** - **Factories** — for creating/modifying events (builder pattern) - **Casts** (`EventCast>`) — for consuming events, providing typed properties, validation, and observable streams for relationships **List factory hierarchy** for NIP-51: - `ListFactory` → `NIP51RelayListFactory` / `NIP51UserListFactory` / `NIP51ItemListFactory` - Concrete: `BookmarkSetFactory extends NIP51ItemListFactory` **Validation** via type guard functions: ```typescript function isValidComment(event): event is CommentEvent { return event.kind === COMMENT_KIND && hasRoot(event) && hasReply(event); } ``` **No centralized registry** — factories distributed across modules, aggregated via index.ts exports. ### ndk Uses class-based kind wrappers with inheritance: **NDKKind enum** with 200+ named variants. Each kind gets a dedicated wrapper class extending `NDKEvent`: ```typescript export class NDKArticle extends NDKEvent { static kind = NDKKind.Article; static kinds = [NDKKind.Article]; // supports multi-kind classes static from(event: NDKEvent): NDKArticle get title(): string | undefined set title(title: string | undefined) } ``` **Multi-kind classes:** `NDKVideo` handles kinds 21, 34235, 34236, 22. `NDKRepost` handles 6 and 16. **40+ kind wrapper files** in `core/src/events/kinds/`. Kind-aware behavior methods on base `NDKEvent`: `isReplaceable()`, `isEphemeral()`, `isParamReplaceable()`. ### nostr-gadgets Functional/storage-oriented approach: **Higher-order factory functions:** ```typescript export const loadFollowsList = makeListFetcher(3, RELAYS, itemsFromTags(...)) export const loadFollowSets = makeSetFetcher(30000, itemsFromTags(...)) ``` **Generic fetcher factories** take kind number + processor function, return typed fetchers. Storage interface returns different shapes based on kind classification (addressable vs replaceable). ### nostrlib Go library with multi-layered kind awareness: **`Kind` type** is `uint16` with categorization methods: `IsRegular()`, `IsReplaceable()`, `IsEphemeral()`, `IsAddressable()`. **Factory map pattern** for domain objects: ```go var moderationActionFactories = map[nostr.Kind]func(nostr.Event) (Action, error){ nostr.KindSimpleGroupPutUser: func(evt nostr.Event) (Action, error) { ... }, } func PrepareModerationAction(evt nostr.Event) (Action, error) { factory, ok := moderationActionFactories[evt.Kind] if !ok { return nil, fmt.Errorf("unsupported kind %d", evt.Kind) } return factory(evt) } ``` **Schema-based validation** from YAML specs per kind (content format, required tags, d-tag rules). **Generic list/set patterns** with `fetchGenericList[V, I](kind, index, parseTag, cache)`. **Dataloader registries** — index-based arrays for 18 replaceable and 4 addressable kinds. ### nostr-tools Foundational kind system: **Const + typeof pattern:** ```typescript export const ShortTextNote = 1; export type ShortTextNote = typeof ShortTextNote; ``` **Classification functions:** `classifyKind()`, `isRegularKind()`, `isReplaceableKind()`, `isEphemeralKind()`, `isAddressableKind()`. **Type guard:** `isKind(event, kind): event is NostrEvent & { kind: T }`. 65+ kind constants. No factory pattern — this is the lowest-level library. ### rust-nostr Most comprehensive Rust implementation: **Macro-generated `Kind` enum** with 130+ named variants plus `Custom(u16)` fallback: ```rust pub enum Kind { Metadata, TextNote, ContactList, ..., Custom(u16) } ``` **Kind range constants:** ```rust pub const REPLACEABLE_RANGE: Range = 10_000..20_000; pub const EPHEMERAL_RANGE: Range = 20_000..30_000; pub const ADDRESSABLE_RANGE: Range = 30_000..40_000; ``` **Behavior methods:** `is_regular()`, `is_replaceable()`, `is_ephemeral()`, `is_addressable()`. **EventBuilder** with 140+ kind-specific factory methods: ```rust pub fn text_note(content) -> Self pub fn metadata(metadata) -> Self pub fn job_request(kind) -> Result // validates kind range ``` **Coordinate validation** enforces kind constraints (replaceable vs addressable, d-tag rules). **No central registry** — behavior statically encoded in kind ranges and builder methods. ### welshman Ad-hoc utilities approach: **Kind constants** in centralized `Kinds.ts` (60+ constants). **Classification predicates:** `isRegularKind()`, `isPlainReplaceableKind()`, `isParameterizedReplaceableKind()`, `isEphemeralKind()`, `isDVMKind()`. **Domain types per kind** with read/create/edit functions: - `readProfile(event) -> PublishedProfile` - `createProfile(profile) -> EventTemplate` - `editProfile(profile) -> EventTemplate` - `readRoomMeta(event) -> PublishedRoomMeta` (validates kind in reader) - `readList(event) -> PublishedList` (generic across list kinds) **Store integration:** `deriveItemsByKey({ eventToItem: readProfile, filters: [{kinds: [PROFILE]}] })` **Encryptable wrapper** for events with encrypted content (lists with private tags). Pattern: each kind gets constants + types + read/create/edit functions + store wiring. Scattered across files rather than unified in a registry. ## Common Patterns 1. **Kind as integer wrapper** — all implementations use u16/number with named constants 2. **Range-based classification** — universal: regular/replaceable/ephemeral/addressable predicates 3. **Factory methods for creation** — builder/factory pattern to construct events of specific kinds 4. **Parser functions for consumption** — event → domain struct conversion 5. **Validation at boundaries** — kind checks when parsing events, kind range validation for coordinates 6. **No centralized registry** — most implementations distribute behavior across modules rather than maintaining a central kind→behavior map **Divergences:** - applesauce uses class hierarchy with fluent builder; welshman uses standalone functions - rust-nostr uses macro-generated enum; Go uses typed constants - NDK couples kind behavior to event subclasses; others keep them separate - Only nostrlib uses external schema-based validation ## Considerations for Our Implementation 1. **Trait-based factory pattern** — Rust traits map well to the factory/cast dual pattern from applesauce. A `KindHandler` trait could define validation, parsing, and creation methods. 2. **Kind enum with Custom fallback** — Follow rust-nostr's `Kind` enum pattern with named variants + `Custom(u16)`. This gives compile-time safety for known kinds while remaining extensible. 3. **Range classification as methods** — `Kind::is_replaceable()` etc. are universally needed and straightforward to implement. 4. **Separate create vs parse** — The applesauce pattern of factories (create) vs casts (parse) maps to Rust traits: one trait for building `EventTemplate` from domain data, another for extracting domain data from `Event`. 5. **Registry for extensibility** — A `HashMap>` or similar registry would let domain chapters register their handlers without modifying the kinds chapter. Alternatively, use static dispatch with an enum if the set of kinds is known at compile time. 6. **Literate programming flow** — The chapter should introduce kinds conceptually, define the `Kind` type, implement classification methods, then build the trait/registry infrastructure that later chapters will use. Keep actual kind implementations (profiles, lists, etc.) for domain chapters. 7. **Dependencies** — This chapter depends on the events chapter (for `Event`, `EventTemplate` types). It should not depend on any domain-specific types.